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.

A typo for ‘propTypes’
A typo for ‘componentDidUpdate’

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.

Incorrect return value from render()

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.

A specified event handler is not a function

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 than onclick. 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.

‘onclick’ is invalid
‘frameborder’ is invalid

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().

Do not use the return value from render() directly

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.

Do not return false to stop event propagation

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.

JavaScript comment 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)

The problem is fixed

DeepScan Rule

DeepScan’s BAD_LENGTH_CHECK rule can prevent developers from making this mistake by checking the direct use of length property.

Do not check length property directly

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:

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.