A few things every new React developer should know (part 1)
My colleagues and I set out to build JavaScript static analysis tool DeepScan which aims to support React well in addition to common JavaScript errors.
We have developed dozens of React validation rules by gathering common pitfalls and feedback from React developers. Among them, I will share a few rules that new React developers are easy to make mistakes.
Let’s start part one.
React is a decent JavaScript UI library that Facebook leads the development and marketing.
It’s getting a lot of popularity with features such as component structure, state management separated from DOM, fast rendering, and JavaScript-centric implementation. But it’s also a bit difficult to front-end developers who are accustomed to direct handling of DOM and working with separate HTML.
In this article, I explain a few difficult things for new React developers with the examples from open source project wp-calypso.
1. Typo of React API
Developers often make a typo in the React component’s lifecycle method or PropTypes.
Lifecycle methods
React component has lifecycle methods that are invoked when the component is created or a state change occurs.
componentWillMount()
componentDidMount()
componentWillUnmount()
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
componentDidUpdate()
render()
But these names are a bit long and camelCased, so it’s easy to make a typo.
Let’s see an example: sync-reader-follows/index.js
import { Component } from 'react';
class SyncReaderFollows extends Component {
check() {
if ( this.props.shouldSync ) {
this.props.requestFollows();
}
}
componentDidMount() {
this.check();
}
componentDidUpate() {
this.check();
}
render() {
return null;
}
}
The developer intented to call check()
in componentDidUpdate
after state has changed, but it is not called because of a typo componentDidUpate
.
PropTypes
React’s PropTypes allows to specify the types of properties (whether they are required, the type of values, etc.) that a component has.
import React from 'react';
import PropTypes from 'prop-types';
export default class Item extends React.Component {
static propTypes = {
id: PropTypes.number.isRequired,
comment: PropTypes.string,
}
}
PropTypes is defined as camelCased propTypes
(see above), but developers sometimes use it as PropTypes
(see below example google-plus-share-preview/index.js).
import React, { PureComponent, PropTypes } from 'react';
import { localize } from 'i18n-calypso';
import { truncateArticleContent } from '../helpers';
export class GooglePlusSharePreview extends PureComponent {
static PropTypes = {
articleUrl: PropTypes.string,
externalProfilePicture: PropTypes.string,
externalProfileUrl: PropTypes.string,
externalName: PropTypes.string,
imageUrl: PropTypes.string,
message: PropTypes.string,
};
}
PropTypes check for GooglePlusSharePreview
component is not performed because of a typo PropTypes
.
DeepScan Rule
DeepScan’s REACT_API_TYPO rule can prevent developers from making this mistake by finding these typos. Also, developers can easily fix the code by using the suggested method name.
2. Incorrect return value from render()
React’s render
function returns a DOM tree (React element) to display in the UI. When there is no information to display, it can return null
or false
. An error occurs if any other value is returned.
Let’s see an example: plugin-site-update-indicator/index.jsx
var React = require( 'react' );
module.exports = React.createClass( {
render: function() {
if ( ! this.props.site || ! this.props.plugin ) {
return;
}
if ( this.props.site.canUpdateFiles &&
( ( this.props.site.plugin.update && ! this.props.site.plugin.update.recentlyUpdated ) || this.isUpdating() ) ) {
if ( ! this.props.expanded ) {
/* eslint-disable wpcalypso/jsx-gridicon-size */
return <Gridicon icon="sync" size={ 20 } />;
/* eslint-enable wpcalypso/jsx-gridicon-size */
}
return this.renderUpdate();
}
return null;
}
} );
The developer would have checked props
and returned immediately because there is nothing to display.
However, render
function of React can only return React element, null
, and false
. So when undefined
is returned, an exception will be thrown and lifecycle methods to be executed after the rendering will not be invoked.
It’s easy to get confused, right?
DeepScan Rule
DeepScan’s BAD_RENDER_RETURN_VALUE rule can prevent developers from making this mistake by checking the return value of render()
and suggesting the proper values.
3. Invalid event handler function
Event handler in React should be a function object, but a developer might make a mistake to use an invalid one.
Let’s see an example: edit-team-member-form/index.jsx
import React, { Component } from 'react';
const EditUserForm = React.createClass( {
recordFieldFocus( fieldId ) {
analytics.ga.recordEvent( 'People', 'Focused on field on User Edit', 'Field', fieldId );
},
handleChange( event ) {
this.setState( {
[ event.target.name ]: event.target.value
} );
},
renderField( fieldId ) {
let returnField = null;
switch ( fieldId ) {
case 'roles':
returnField = (
<RoleSelect
id="roles"
name="roles"
key="roles"
siteId={ this.props.siteId }
value={ this.state.roles }
onChange={ this.handleChange }
onFocus={ this.recordFieldFocus( 'roles' ) }
/>
);
break;
}
}
} );
You need to assign a function as onFocus
event handler.
But the result of calling recordFieldFocus()
which does not have return
statement comes to be undefined
. As a result, event handler is not specified correctly and Google Analytics will not track the focus events.
In this case, you can fix the problem as follows:
ES5 bind
function:
onFocus={ this.recordFieldFocus.bind( this, 'roles' ) }
ES6 arrow function:
onFocus={ () => this.recordFieldFocus( 'roles' ) }
However, since the above approaches create a new function for each render, there can be a performance issue. It is recommended to bind the handler once in the constructor or use separate component.
DeepScan Rule
DeepScan’s MISSING_RETURN_VALUE rule can prevent developers from making this mistake by checking the return value of a function. Since the message tells the location of the event handler function, the developer can immediately check the function causing the problem.
4. Incorrect property in DOM element
React’s DOM element uses camelCased property names apart from the existing DOM property and attribute names.
Two typical examples are:
- React requires you to use
className
to specify the class - React’s click event handler is
onClick
thanonclick
. It seems to be particularly confusing to existing frontend developers who are familiar with HTML that is case insensitive.
Let’s see an example: importer/error-pane.js
import React, { PropTypes } from 'react';
export default React.createClass( {
getImportError: function() {
return this.translate(
'%(errorDescription)sTry again or contact support.', {
args: {
errorDescription: this.props.description
},
components: {
a: <a href="#" onclick={ this.retryImport }/>,
br: <br />,
cs: <a href="#" onclick={ this.contactSupport } />
}
}
);
}
} );
In the above code, click event handler will not be executed.
In the open source project react-native-macos, we can find an error that uses frameborder
property instead of correct frameBorder
property.
var React = require('React');
var Modal = React.createClass({
render: function() {
return (
<div>
<div className="modal">
<div className="modal-content">
<button className="modal-button-close">×</button>
<div className="center">
<iframe className="simulator" src={url} width="256" height="550" frameborder="0" scrolling="no"></iframe>
<p>Powered by <a target="_blank" href="https://appetize.io">appetize.io</a></p>
</div>
</div>
</div>
<div className="modal-backdrop" />
</div>
);
}
});
DeepScan Rule
DeepScan’s BAD_UNKNOWN_PROP rule can prevent developers from making this mistake by checking the property name and providing the proper one.
5. Direct use of the return value from render()
You might use the ReactComponent
instance returned by the ReactDOM.render
function.
This is not recommended because rendering can happen asynchronously in the future version of React and the ReactComponent
instance might not be available as the return value.
In the open source project react-starter-kit below, the return value of render
function is used but this value may be undefined
. So a desired initialization may not be executed.
Let’s see an example: src/client.js
import ReactDOM from 'react-dom';
let appInstance;
async function onLocationChange(location, action) {
appInstance = ReactDOM.render(
<App context={context}>{route.component}</App>,
container,
() => onRenderComplete(route, location),
);
}
if (appInstance) {
// Force-update the whole tree, including components that refuse to update
deepForceUpdate(appInstance);
}
In this case you need to get the instance through the ref
callback function.
function cb(instance) {
}
async function onLocationChange(location, action) {
ReactDOM.render(
<App context={context} ref={cb}>{route.component}</App>,
container,
() => onRenderComplete(route, location),
);
}
DeepScan Rule
DeepScan’s ASYNC_RENDER_RETURN_VALUE rule can prevent developers from making this mistake by checking the direct use of return value from render()
.
6. Invalid stopping of an event propagation
In HTML, event propagation and default behavior will be aborted when the event handler function returns false
.
However, React uses a separate event system and you must explicitly call stopPropagation()
or preventDefault()
on the React event object to stop event propagation or prevent the default behavior.
In the open source project browser-laptop below, onMaximizeClick
event handler uses the old-fashioned way returning false
. So event propagation is not stopped.
Let’s see an example: navigation/windowCaptionButtons.js
const React = require('react')
class WindowCaptionButtons extends ImmutableComponent {
onMaximizeClick (e) {
if (isFullScreen()) {
// If full screen, toggle full screen status and restore window (make smaller)
windowActions.shouldExitFullScreen(getCurrentWindowId())
if (isMaximized()) windowActions.shouldUnmaximize(getCurrentWindowId())
return false
}
return (!isMaximized()) ? windowActions.shouldMaximize(getCurrentWindowId()) : windowActions.shouldUnmaximize(getCurrentWindowId())
}
render () {
const props = { tabIndex: -1 }
return <div>
<div className='container'>
<button
{...props}
onClick={this.onMaximizeClick}
title={locale.translation(this.maximizeTitle)}>
</button>
</div>
</div>
}
}
DeepScan Rule
DeepScan’s BAD_EVENT_HANDLER_RETURN_FALSE rule can prevent developers from making this mistake by checking whether false
is returned from an event handler.
7. JavaScript comment in JSX
In JSX, developers should attend to using JavaScript comments such as //
or /* */
.
If the comment is recognized as a text node of a component, it will be visible on the browser screen. A comment should be enclosed in curly braces like {/* */}
.
In open source project belle, the comment itself is displayed. Oops!
Let’s see an example: components/RatingPlayground.js
import React from 'react';
export default React.createClass({
render() {
return (
<div>
<h2>Rating</h2>
<Card>
<Button onClick={ this._updateRatingToThree }>Update Rating to value 3</Button>
//onUpdate should not be called for valueLink
<h3>ValueLink</h3>
</Card>
</div>
);
}
});
In this case, write as the following:
{/* onUpdate should not be called for valueLink */}
<h3>ValueLink</h3>
In the opposite, when you really need “//” literally like <h3>// a is a double slash.</h3>
, it is good to write like <h3>{"// is a double slash."}</h3>
for clarity.
DeepScan Rule
DeepScan’s BAD_JSX_COMMENT rule can prevent developers from making this mistake by checking JavaScript comments in JSX.
8. Checking length property directly
A child element as undefined
, null
, true
or false
is not rendered. Therefore, developers use JSX expression like cond && <div>…</div>
when they want to render something selectively.
However, this is not applied for the numeric value 0. For example, if array
is empty in array.length && <div>…</div>
, 0 is displayed on the screen.
In this case, consider the following:
- Use comparison expression explicitly like
array.length > 0
- Add an empty string as OR condition like
array.length && <div>…</div> || ''
The open source project react-native-macos below checks length
property directly, so 0 will be displayed when this.props.params
is empty.
Let’s see an example: layout/AutodocsLayout.js
var React = require('React');
var Method = React.createClass({
render: function() {
return (
<div className="prop">
<Header level={4} className="methodTitle" toSlug={this.props.name}>
{this.props.modifiers && this.props.modifiers.length && <span className="methodType">
{this.props.modifiers.join(' ') + ' '}
</span> || ''}
{this.props.name}
<span className="methodType">
({this.props.params && this.props.params.length && this.props.params
.map((param) => {
var res = param.name;
res += param.optional ? '?' : '';
return res;
})
.join(', ')})
{this.props.returns && ': ' + this.renderTypehint(this.props.returns.type)}
</span>
</Header>
</div>
);
}
});
I’m just glad to see a mindful developer fixed a problem by appending || ''
condition. (Check this commit)
DeepScan Rule
DeepScan’s BAD_LENGTH_CHECK rule can prevent developers from making this mistake by checking the direct use of length
property.
Thanks for reading!
You can check the above codes by directly pasting it on this Demo page.
I’ll continue to share what will be helpful in React development. If you have feedback or suggestions, you can find me here:
- Official Website: https://deepscan.io
- Email: support@deepscan.io
Resources
Check out additional starting resources for React: