I use eslint to lint ES2015 + React projects. One problem I encounter with eslint is that it does not support the spread operator
. I realized that I had to install babel-eslint
to make this work. I have to modify .eslintrc
to make it work with babel-eslint
:
{
"parser": "babel-eslint",
...
}
Then, I run eslint and got this error:
Error: Cannot find module 'estraverse-fb'
It turns out that if you are using eslint 2.0, you have to use babel-eslint 6.x.x. The detail is explained in this thread.
I was learning eslint's react/jsx options and stumbled upon this rule react/jsx-no-bind
:
A bind call or arrow function in a JSX prop will create a brand new function on every single render. This is bad for performance, as it will result in the garbage collector being invoked way more than is necessary.
The following patterns are considered warnings:
<div onClick={this._handleClick.bind(this)}></div>
<div onClick={() => console.log('Hello!'))}></div>
The following patterns are not considered warnings:
<div onClick={this._handleClick}></div>
When writting React using the ES6 class syntax like this:
class MyClass extends React.Component {
constructor(){
super()
}
}
Two questions may arise:
- Is it necessary to call
super()
insideconstructor
? - What is the difference between callling
super()
andsuper(props)
?
Answer #1
Always call
super()
if you have a constructor and don't worry about it if you don't have a constructor
Calling super()
is necessary only if you need to have a constructor
. Take a look at this code:
class MyClass extends React.Component {
render(){
return <div>Hello { this.props.world }</div>;
}
}
The code above is perfectly valid. You don't have to call super()
for every react component you create. However, if there is a constructor in your code, then you MUST call super:
class MyClass extends React.Component {
constructor(){
console.log(this) //Error: 'this' is not allowed before super()
}
}
The reason why this
cannot be allowed before super()
is because this
is uninitialized if super() is not called [1]
You may think you can get away with an empty constructor without callling super()
:
class MyClass extends React.Component {
constructor(){} // Error: missing super() call in constructor
}
ES6 class constructors MUST call super
if they are subclasses. Thus, you have to call super()
as long as you have a constructor. (But a subclass does not have to have a constructor)
Answer #2
Call
super(props)
only if you want to accessthis.props
inside the constructor. React automatically set it for you if you want to access it anywhere else.
The effect of passing props
when calling super()
allows you to access this.props
in the constructor:
class MyClass extends React.Component{
constructor(props){
super();
console.log(this.props); // this.props is undefined
}
}
To fix it:
class MyClass extends React.Component{
constructor(props){
super(props);
console.log(this.props); // prints out whatever is inside props
}
}
There is no need to pass props
into the constructor if you want to use it in other places. Because React automatically set it for you [2]
class MyClass extends React.Component{
render(){
// There is no need to call `super(props)` or even having a constructor
// this.props is automatically set for you by React
// not just in render but another where else other than the constructor
console.log(this.props); // it works!
}
}
TL;DR
For development, use cheap-module-eval-source-map
. For production, use cheap-module-source-map
.
Disclaimer: The following result is based on a small project. The result may vary depending on the size of your project.
Webpack currently provides seven options to produce a source map. It is nice that it gives so many choices to developers, but it also downright horrible for beginners.
As a beginner, I find the offical document impossible to understand. So I decided to test it out myself. I am testing all of the options on a project with the following attributes:
- 18 .js files (modules if you prefer) along with libraries they referenced are bundled into a
bundle.js
file - The
bundle.js
file is minified to680 KB
- I left a bug in the code on purpose so that I can see what's the differences between the source map options. The bug is on line
#37
in a file namedCreateLogForm.js
- I wrote my code in ES6 and JSX and used
babel
to transpile it back toES5
- I tested the error in Chrome's debug tools:
- I verfied the
line number
and thefile name
where the error orginated - I clicked on the linked error file and see if the original source is displayed
- I verfied the
source-map
I am going to start out with this one because this option produces exactly what you would expect, a source map. After the bundling process, I got a bundle.js.map
file:
-
Size:
5.44 MB
- Bundle Time: 50949 ms (This is how long it took to pack the bundle. There are fonts, images and css being emitted by webpack, so this is not how long it took to produce the source map file. This is the result of running this operation once. Thus, it should not be interpreted as the average running time of the operation.)
-
Bug Display:
- File name displayed correctly
- Line number displayed correctly (the line number matches the original source file's line number)
- The file referenced by the error:
./~/react-dnd/~/invariant/browser.js
which is completely wrong
cheap-source-map
A SourceMap without column-mappings. SourceMaps from loaders are not used.
No idea what that means, but this time:
-
Size:
698 B
- Bundle Time: 46098 ms
-
Bug Display:
- File name displayed correctly
- Line number displayed incorrectly (the line number matches the transpiled file's line number instead of the original)
- The file referenced by the error:
./~/react-dnd/~/lodash/lang/isFunction.js
which is completely wrong
cheap-module-source-map
A SourceMap without column-mappings. SourceMaps from loaders are simplified to a single mapping per line.
No idea what it is talking about:
-
Size:
308 B
- Bundle time: 56611 ms
-
Bug Display:
- File name displayed correctly
- Line number displayed correctly
- The file referenced by the error:
./~/react-dnd/~/dnd-core/lib/index.js
which is completely wrong
Note: This is the best option for production because:
- Both
bundle.js
andbundle.js.map
are smallest - The correct file name and line number are provided
eval
Each module is executed with eval and //@ sourceURL
No .map
file emitted and the bundle.js
file size is increased to 2.34 MB
:
- Size: N/A
- Bundle Time: 33879 ms
-
Bug Display:
- File name displayed correctly
- Line number displayed incorrectly (the line number matches the transpiled file's line number instead of the original)
- The file referenced by the error: The transpiled code of the original source is displayed
eval-source-map
Each module is executed with eval and a SourceMap is added as DataUrl to the eval
No .map
file emitted and the bundle.js
file size is increased to 5.83 MB
:
- Size: N/A
- Bundle Time: 34257 ms
-
Bug Display:
- File name displayed correctly
- Line number displayed correctly (
CreateLogForm.js?b337:37
I don't know what does theb337
portion stands for though) - The file referenced by the error: The original source code is displayed
cheap-eval-source-map
No .map
file emitted and the bundle.js
file size is increased to 5.8 MB
:
- Size: N/A
- Bundle Time: 40663 ms
-
Bug Display:
- File name displayed correctly
- Line number displayed incorrectly (the line number matches the transpiled file's line number instead of the original)
- The file referenced by the error: The transpiled code of the original source is displayed
cheap-module-eval-source-map
No .map
file emitted and the bundle.js
file size is increased to 5.74 MB
:
- Size: N/A
- Bundle Time: 40663 ms
-
Bug Display:
- File name displayed correctly
- Line number displayed correctly (
CreateLogForm.js?b337:37
I don't know what does theb337
portion stands for though) - The file referenced by the error: The original source code is displayed
Note: This is the best option for development because it is the smallest option that shows the correct line number.
handleChange = (e) => {
this.setState({[e.target.name]: e.target.value}, () => {
console.log(e) // <- e is an event with all of its properties set to null
})
}
The code above prints an event object with nulls set to all of its properties. So you cannot access e.target.value
or e.target.name
as you normally would. This issue is explained in this thread:
Events are pooled so we wipe out their values. If you want to use the event outside the current event loop then you need to call ev.persist() which will pull that event out of the pooling.
I think what that means is that React resues a set of event objects instead of creating new event objects everytime an event comes up. persist()
prevents it from cleaning:
handleChange = (e) => {
e.persist()
this.setState({[e.target.name]: e.target.value}, () => {
console.log(e) // <- now you can see all of the property values correctly
})
}
Two small issues I run into when dealing with OneToOneField and iterating through a model's fields.
One to One Fields
class A(Model):
...
class B(Model):
a = models.OneToOneField(A, primary_key=True)
When models are defined like above, you can grab a reference to B this way:
a_instance = A.objects.get(id=1)
b_instance = a_instance.b
Since B's primary key equals to A's primary key, when Django's ORM creating B in the database, it will not create an 'id' column. Instead, a column named 'A_id' is created. Thus, when ever you want to access B's primary key, you cannot do:
b_instance.id # AttributeError: 'b_instance' object has no attribute 'id'
You have to use b_instance.pk
instead.
Model's _meta field
I once had a task which needs to determine if any field within a model instance is empty. To do it, I need a way to loop through all of the fields defined in the instance first:
# _meta allows you to access the fields defined in the model instance
for field in model_instace._meta.fields:
...
Here is the code for determining if a model instance is empty:
def is_model_instance_empty(model_instance):
# find all of the field's name that are not foreign keys or one to one fields
# many to many relationship should be added to make it complete
field_names = [field.name for field in model_instance._meta.fields if (field.get_internal_type() != 'OneToOneField' and field.get_internal_type() !='ForeignKey')]
counter = 0
for field_name in field_names:
if not model_instance.__dict__[field_name]:
counter += 1
return counter == len(field_names)
Django's formset can be difficult to understand, so I will try to explain it in a more understanbable way here.
What is it used for?
Let's say you are building a website which keeps track of users' daily activities. They can logon to your site and fill out a form with the following information:
- Date and time
- Activity name
- Duration
Multiple activities can be recorded throughout a day. When users start to record for a new day, he should see an empty form with the aforementioned fields and an add button below the form to add more activities:
Date and Time: ___________________
Activity Name: ___________________
Duration: ___________________
[ + Add More Activity ]
In this case, you cannot predict how many activities users will add beforehand. Thus, you cannot create a form to prepare for it. This is when Formset comes in. Formset allows you to add/remove forms on the fly (with the help of javascript which I will talk about in detail later).
How it works?
Before getting into the implementation details, it is important to understand how formset works. The best way to learn it is to think out how you would implement it by hand.
Requirements
CRUD is a good starting point to define what needs to be accomplished:
- Forms need to be created (i.e. create new form data that do not exist in the database)
- Forms need to be read (i.e. fill forms with existing data and display them to the user)
- Forms need to be updated (i.e. fetch existing data and save changes made to them)
- Forms need to be deleted (i.e. delete an existing form)
To accomplish any of these tasks, we need a way to keep track of forms. Thus, an ID is needed for each form. Also, when a form is deleted, we need a way to detect that. Thus, a form needs to be marked as deleted or not deleted.
How does Django do it?
To manage which form is which and mark them as deleted when neccessary, Django insert hidden input fields into the rendered HTML to help itself keeping track of the forms. Django inserts the following hidden data:
<input id="id_form_set-TOTAL_FORMS" name="form_set-TOTAL_FORMS" type="hidden" value="1">
TOTAL_FORMS: the total number of forms in this formset
value: 1 means this formset contains 1 form
<input id="id_form_set-INITIAL_FORMS" name="form_set-INITIAL_FORMS" type="hidden" value="1">
INITIAL_FORMS: the number of forms in the formset that were pre-filled
value: 1 means 1 form within this formset has been pre-filled with data
<input id="id_form_set-MIN_NUM_FORMS" name="form_set-MIN_NUM_FORMS" type="hidden" value="0">
MIN_NUM_FORMS: the minimum number of forms within the formset
value: the minimum number, but you normally do not need to set this
<input id="id_form_set-MAX_NUM_FORMS" name="form_set-MAX_NUM_FORMS" type="hidden" value="1000">
MAX_NUM_FORMS: the maximum number of forms a formset may contain
value: the maximum number allowed
All of these hidden data, are added by the ManagementForm. You don't need to read about ManagementForm at the moment, just knowing which componment within Django is responsible for these is enough at this point.
To keep track of each form, Django also inserts hidden primary keys that are associated with existing forms:
<input id="id_form_set-0-id" name="form_set-0-id" type="hidden" value="4">
value 4 means the primary key of this form is 4
To keep track of which form has been deleted, Django also inserts a hidden field:
<input type="hidden" name="form_set-0-DELETE" id="id_form_set-0-DELETE" value="on">
value: on means this form is marked as deleted. When there is no value, it means the form is not deleted
The can_delete parameter is responsible for marking forms as deleted. I will explain this parameter later.
This post is long enough for a short read. I will explain more details about formset in the next blog post.
When writing a Django project, it happens often that mulitple apps will be included. Let me use an example:
Project
- Account
- Journal
In this example, I created a Django project that contains two apps. The Account
app handles user registration and login. The Journal
app allows users to write journals and save it to the database. Here is the what the urls look like:
#ROOT_URLCONF
urlpatterns = [
url(r'^account/', include('Account.urls', namespace='account')),
url(r'^journal/', include('Journal.urls', namespace='journal')), #This namespace name is used later, so just remember we have given everything under journal/ a name
]
This above file is what the ROOT_URLCONF
points to. Inside the Note
app, the urls look like this:
urlpatterns = [
url(r'^(?P<id>[0-9]{4})/$', FormView.as_view(), name = 'detail'),
]
So each journal has a 4 digit id. When a journal is access, it's url may look like this: www.mynote.com/note/1231/
Let's say user John
bookmarked a journal written by another person. He wants to comment on it. When John tries to access that journal www.mynote.com/note/1231/
, he is redirected to the login page. In the login page's view handler, a redirect should be made to Journal ID 1231
once authentication is passed:
def view_handler(request):
# authentication passed
return redirect(reverse('detail', kwargs={'id', '1231'}))
The reverse(...)
statement is not going to work in this case. Because the view_handler belongs to the Account
app. It does not know about the urls inside the Journal
app. To be able to redirect to the detail page of the Journal
app:
reverse('journal:detail', kwargs={'id', '1231'})
So the format for reversing urls that belong to other apps is:
reverse('namespace:name', args, kwargs)
There are some pitfalls when you need to create and login users manually in Django. Let's create a user first:
def view_handler(request):
username = request.POST.get('username', None)
password = request.POST.get('username', None)
Note that request.POST.get('username', None)
should be used instead of request.POST['username']
. If the later is used, you will get this error:
MultiValueDictKeyError
Once the username and password are extracted, let's create the user
User.objects.create(username=username, password=password, email=email) # DON'T DO THIS
The above code is wrong. Because when create
is used instead of create_user
, the user's password is not hashed. You will see the user's password is stored in clear text in the database, which is not the right thing to do.
So you should use the following instead:
User.objects.create_user(username=username, password=password, email=None)
What if you want to test if the user you are about to create has already existed:
user, created = User.objects.get_or_create(username=username, email=None)
if created:
user.set_password(password) # This line will hash the password
user.save() #DO NOT FORGET THIS LINE
get_or_create
will get the existing user or create a new one. Two values are returned, an user object and a boolean flag created
indicating whether if the user created is a new one (i.e. created = True) or an existing one (i.e. created = False)
It is import to not forget including user.save()
in the end. Because set_password
does NOT save the password to the database.
Login
Now a user has been created successfully, the next step is to login.
user = authenticate(username=email, password=password)
login(request, user)
authenticate()
only sets user.backend
to whatever authentication backend Django uses. So the code above is equivlent to:
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
Django's documentation recommends the first way of doing it. However, there is an use case for the second approach. When you want to login an user without a password:
username = self.request.GET.get('username')
user = User.objects.get(username=username)
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
The is used when security isn't an issue but you still want to distinguish between who's who on your site.
So to sum up the code above, here is the view_handler that manually create and login an user:
def view_handler(request, *args, **kwargs):
email = request.POST.get('email', None)
password = request.POST.get('password', None)
if email and password:
user, created = User.objects.get_or_create(username=email,
email=email)
if created:
user.set_password(password)
user.save()
user = authenticate(username=email, password=password)
login(request, user)
return HttpResponseRedirect('where_ever_should_be_redirect_to')
else:
# return error or redirect to login page again
Saw this example on MDN:
var str = 'rawr';
var searchFor = 'a';
// this is alternative way of typing if (-1*str.indexOf('a') <= -1)
if (~str.indexOf(searchFor)) {
// searchFor is in the string
} else {
// searchFor is not in the string
}
According to the documentation,
Bitwise NOTing any number x yields -(x + 1). For example, ~5 yields -6.
So if a character exists within a string, indexOf
will return the first occurance of that character's index which will either be an 0 or any positive nubmer.
Thus, the tilde operator will turn them into:
~0 === -1
~1 === -2
~2 === -3
...
Any none zero number is considered a true
in JS, so:
1 == true
-1 == true
Thus, if str.indexOf()
returns any number other than -1
, ~str.indexOf()
equals to a negative number, which will be considered True
.
In the case of -1
is returns (i.e. the character does not exist in the string):
~(-1) === 0
0 == false
Thus, the else
branch is executed.