Skip to content

Latest commit

 

History

History
2122 lines (1629 loc) · 67.9 KB

README.md

File metadata and controls

2122 lines (1629 loc) · 67.9 KB

Eventbrite React & JSX Coding Style Guide

Eventbrite’s ESLint guidelines to ensure consistency in React and JSX code.

Table of Contents

  1. What is React?
  2. What is JSX?
  3. General rules
  4. Component files
  5. Component class
  6. Component organization
  7. Component reference naming
  8. Component propTypes
  9. Helper components
  10. Component methods
  11. JSX wrapping
  12. JSX alignment
  13. JSX attribute values
  14. React key prop
  15. Event handlers
  16. render()
  17. State
  18. Dangerous props
  19. Refs
  20. Mounting
  21. Context
  22. Testing

What is React?

React is a JavaScript-centric UI library. It abstracts away the DOM, giving a simpler programming model and better performance. It can also render on the server using Node, and can even power native apps using React Native. React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding.

For more info, see Getting started with React.

⬆ back to top

What is JSX?

JSX is a JavaScript syntax extension that looks similar to XML. We use it with React because it is a concise and familiar syntax for defining tree structures with attributes.

For more info, see JSX in Depth.

⬆ back to top

General rules

Always use JSX syntax (don't use React.createElement):

// good
render() {
    return (
        <Component value="foo">
            <ChildComponent />
        </Component>
    );
}

// bad (why torture yourself with React.createElement?)
render() {
    return React.createElement(
        Component,
        { value: "foo" },
        React.createElement(ChildComponent, null)
    );
}

⬆ back to top

Component files

  • Use PascalCase for React component names, e.g. TextInput
  • Use .js extension for React components
  • Use the component name for filenames. E.g., TextInput.js

⬆ back to top

Component class

Class style

Prefer ES6 classes over React.createClass (eslint: react/prefer-es6-class):

// good
export default class MainComponent extends React.PureComponent {

}

// bad (uses React.createClass)
const MainComponent = React.createClass({

});
export default MainComponent

NOTE: There is a common practice to use stateless/pure functions over ES6 classes for components without any internal state (eslint: react/prefer-stateless-function), but we are choosing to stick with ES6 classes for a few reasons:

  • When the component does more than just render props, such as attaching callback handlers, the stateless function can become unnecessarily complex with nested functions
  • propTypes are defined within the ES6 class, but have to be defined as additional properties outside of a stateless function
  • Using ES6 classes for the main/default component help differentiate it from helper components

⬆ back to top

PureComponent vs. Component

Using PureComponent is preferred over Component because React provides optimizations in how it checks for changes. Instead of doing a deep comparison of what has changed from the previous state of the component, PureComponent implements a shouldComponentUpdate that performs simple equality checks for props and state.

// good
export default class MainComponent extends React.PureComponent {

}

// bad (uses React.Component)
export default class MainComponent extends React.Component {

}

NOTE: In rare cases where you are using changing context or deeply nested/mutated objects, PureComponent may not detect the changes you need.

displayName

Do not use displayName for naming components. Instead, name the class expression. React will infer the displayName from the reference name:

// good
export default class TextInput extends React.PureComponent {
}

// ok (uses class expression assigned to a named const reference)
const TextInput = class extends React.PureComponent {
};

// bad (missing name of `class` expression)
export default class extends React.PureComponent {
}

// bad (uses `displayName` instead of `class` name)
export default class extends React.PureComponent {
    static displayName = 'TextInput';
}

Component organization

Export only one component per file as the default (eslint: react/no-multi-comp)

// MainComponent.js

// good
export default class MainComponent extends React.PureComponent {

}


// bad (exports multiple components)
export class MainComponent extends React.PureComponent {

}
export class OtherComponent extends React.PureComponent {

}

⬆ back to top

Component reference naming

Use PascalCase for React components and camelCase for their instances (eslint: react/jsx-pascal-case).

Components:

// good
import TextInput from './atoms/TextInput';

// bad (uses camelCase for component reference)
import textInput from './atoms/TextInput';

Component instances:

// good
let emailField = (<TextInput name="email" />);

// bad (uses PascalCase for component instances)
let EmailField = (<TextInput name="email" />);

⬆ back to top

Component propTypes

Defining propTypes

Use static class property syntax to define propTypes and defaultProps:

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        type: React.PropTypes.string,
        defaultValue: React.PropTypes.string
    }

    static defaultProps = {
        type: 'text',
        defaultValue: ''
    }
}

// bad (adds `propTypes` & `defaultProps` after class definition)
const TextInput = class extends React.PureComponent {

};

TextInput.propTypes = {
    type: React.PropTypes.string,
    defaultValue: React.PropTypes.string
};
TextInput.defaultProps = {
    type: 'text',
    defaultValue: ''
};

export default TextInput;

NOTE: Static class properties are not a part of the ES2015 specification and are a in the midst of the ECMAScript proposal approval process. Currently they are sitting in Stage 3. For all proposed features, check out ECMAScript proposals.

⬆ back to top

Props naming

Use camelCase for propTypes (eslint: camelcase):

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        type: React.PropTypes.string,
        defaultValue: React.PropTypes.string,
        onChange: React.PropTypes.func,
        onFocus: React.PropTypes.func,
        onBlur: React.PropTypes.func
    }
}

// bad (uses non-camelCase)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        type: React.PropTypes.string,
        default_value: React.PropTypes.string,
        OnChange: React.PropTypes.func,
        On_Focus: React.PropTypes.func,
        on_Blur: React.PropTypes.func
    }
}

⬆ back to top

Required props

Don't declare any of the propTypes as required if they are included in defaultProps:

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func.isRequired,
        type: React.PropTypes.string,
        required: React.PropTypes.bool,
        defaultValue: React.PropTypes.string,
        role: React.PropTypes.string
    }

    static defaultProps = {
        type: 'text',
        defaultValue: ''
    }
}

// bad (`type` is marked as required even though it's defaulted &
// `required` is marked as required even though it's boolean w/ `false` default)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func.isRequired,
        type: React.PropTypes.string.isRequired,
        required: React.PropTypes.bool.isRequired,
        defaultValue: React.PropTypes.string,
        role: React.PropTypes.string
    }

    static defaultProps = {
        type: 'text',
        defaultValue: ''
    }
}

⬆ back to top

propTypes Ordering

Define required propTypes first, so that minimum props required to use this component are immediately obvious:

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        role: React.PropTypes.string.isRequired,
        onChange: React.PropTypes.func.isRequired,

        type: React.PropTypes.string,
        required: React.PropTypes.bool,
        defaultValue: React.PropTypes.string
    }

    static defaultProps = {
        type: 'text',
        defaultValue: '',
    }
}

// bad (required props are not first)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        type: React.PropTypes.string,
        required: React.PropTypes.bool,
        defaultValue: React.PropTypes.string,
        role: React.PropTypes.string.isRequired,
        onChange: React.PropTypes.func.isRequired
    }

    static defaultProps = {
        type: 'text',
        defaultValue: '',
    }
}

⬆ back to top

Boolean propTypes

Name boolean propTypes for a component so that their default value is false. This way, omitting a boolean value in the JSX using the component is the same as specifying the boolean value as false. This means that you may need to name a prop negatively so that its default value will be false.

Avoid declaring boolean propTypes as required. Instead, declare the default value (which should be false) in defaultProps. A client of the component shouldn't have to specify false in the JSX for a prop that can just be defaulted to false.

Use descriptives to name boolean propTypes representing toggle states in the component. Ideally these adjectives begin with is or has. Use actions (verbs) to name boolean propTypes that represent whether an action should happen within the component.

// good
export default class Banner extends React.PureComponent {
    static propTypes = {
        hideIcon: React.PropTypes.bool,
        isActive: React.PropTypes.bool,
        showError: React.PropTypes.bool
    }

    static defaultProps = {
        hideIcon: false,
        isActive: false,
        showError: false
    }
}

// bad (icon-related prop is mis-named such that default is true)
export default class Banner extends React.PureComponent {
    static propTypes = {
        showIcon: React.PropTypes.bool,
        isActive: React.PropTypes.bool,
        showError: React.PropTypes.bool
    }

    static defaultProps = {
        showIcon: true,
        isActive: false,
        showError: false
    }
}

// bad (boolean prop type is declared as required)
export default class Banner extends React.PureComponent {
    static propTypes = {
        hideIcon: React.PropTypes.bool.isRequired,
        isActive: React.PropTypes.bool,
        showError: React.PropTypes.bool
    }

    static defaultProps = {
        isActive: false,
        showError: false
    }
}

// bad (props are ambiguously named)
export default class Banner extends React.PureComponent {
    static propTypes = {
        icon: React.PropTypes.bool,
        active: React.PropTypes.bool,
        error: React.PropTypes.bool
    }

    static defaultProps = {
        icon: false,
        active: false,
        error: false
    }
}

⬆ back to top

Vague propTypes

Don't use the vague prop types, React.PropTypes.any, React.PropTypes.array, and React.PropTypes.object; instead be more explicit using, React.PropTypes.oneOfType, React.PropTypes.arrayOf, and React.PropTypes.shape (eslint react/forbid-prop-types):

// good
export default class Candidate extends React.PureComponent {
    static propTypes = {
        id: React.PropTypes.oneOfType([
            React.PropTypes.number,
            React.PropTypes.string
        ]),
        names: React.PropTypes.arrayOf(
            React.PropTypes.string
        ),
        person: React.PropTypes.shape({
            name: React.PropTypes.string,
            age: React.PropTypes.number
        })
    }
}

// bad (uses vague prop types)
export default class Candidate extends React.PureComponent {
    static propTypes = {
        id: React.PropTypes.any,
        names: React.PropTypes.array,
        person: React.PropTypes.object
    }
}

⬆ back to top

className prop

Avoid defining a className prop for a component because its intent can be ambiguous. Will it overwrite the className set on the component's top-level node or be merged with it?

Ideally, a parent component does not control a child component’s styling, so don’t specify a child's className prop. The child component should be 100% in charge of its visual display.

However, you may need to control the layout/positioning of a child component within a parent component. If so, wrap the child component in a <div> or <span> that the parent does control, and add the positioning styling to that wrapper node.

In the limited cases where the wrapper node solution doesn't work, the child component can expose a __containerClassName prop, which the parent can specify to add layout-based CSS class(es) to. The prop begins with dunder (__) to indicate that it is an exceptional case, so that it can easily be spotted in code review.

// good (parent uses wrapper `<div>` to position child)

// Child.js
export default class Child extends React.PureComponent {
    // code for Child component
}

// Parent.js
export default class Parent extends React.PureComponent {
    render() {
        return (
            <div className="parent">
                <h2 className="parent__heading">Parent</h2>

                <div className="parent__child">
                    <Child />
                </div>
            </div>
        );
    }
}


// less-than-ideal, but acceptable
// (parent uses `__containerClassName` prop to position child)

// Child.js
export default class Child extends React.PureComponent {
    static propTypes = {
        // other propTypes
        __containerClassName: React.PropTypes.string
    }

    render() {
        let {__containerClassName} = this.props;
        let className = 'child';

        return (
            <div className=`${className} ${__containerClassName}`>

            </div>
        );
    }
}

// Parent.js
export default class Parent extends React.PureComponent {
    render() {
        return (
            <div className="parent">
                <h2 className="parent__heading">Parent</h2>

                <Child __containerClassName="parent__child" />
            </div>
        );
    }
}


// bad (parent specifies VISUAL STYLING with `__containerClassName` prop)

// Child.js
export default class Child extends React.PureComponent {
    static propTypes = {
        // other propTypes
        __containerClassName: React.PropTypes.string
    }

    render() {
        let {__containerClassName} = this.props;
        let className = 'child';

        return (
            <div className=`${className} ${__containerClassName}`>

            </div>
        );
    }
}

// Parent.js
export default class Parent extends React.PureComponent {
    render() {
        return (
            <div className="parent">
                <h2 className="parent__heading">Parent</h2>

                <Child __containerClassName="text--red" />
            </div>
        );
    }
}


// bad (child defines `className` prop instead of `__containerClassName`)

// Child.js
export default class Child extends React.PureComponent {
    static propTypes = {
        // other propTypes
        className: React.PropTypes.string
    }

    render() {
        let {propsClassName} = this.props;
        let className = 'child';

        return (
            <div className=`${className} ${propsClassName}`>

            </div>
        );
    }
}

// Parent.js
export default class Parent extends React.PureComponent {
    render() {
        return (
            <div className="parent">
                <h2 className="parent__heading">Parent</h2>

                <Child className="parent__child" />
            </div>
        );
    }
}

By the way, instead of concatenating className strings yourself (as done in the examples above), use the classnames library.

⬆ back to top

Helper components

When a component contains either a lot of markup or significant logic describing its markup, use helper components to keep render() as small as possible. Use stateless functions instead of class declarations for these helper components. Because they are only useful to the main component and only exist to keep render() lean, don’t place these helper components in their own files, nor export them within the main component.

Let's look at a simple example. Say you have a global footer containing a section with links to important top-level pages, another section with links for SEO (😐), a section of links to all of your top-level domains (e.g., .com, .co.uk, etc.), and a final section of links to all of your social media accounts.

If you are using helper components properly, this will look like:

// good (clean render w/ help of helper components)

// using arrow functions for stateless functions
const SiteLinks = () => (
    <ul className="global-footer__site-links">
        <li><a href="/about">About</a></li>
        <li><a href="/blog">Blog</a></li>
        <li><a href="/help">Help</a></li>
        <li><a href="/careers">Careers</a></li>
    </ul>
);

// arrow function takes in props object
const SeoLinks = (props) => {
    let linkItems = props.links.map((link) => (
        <li><a key={link.url} href={link.url}>{link.label}</a></li>
    ));

    return (
        <ul className="global-footer__seo-links">
            {linkItems}
        </ul>
    );
};

// arrow function immediately destructures props object into
// `allTlds` and `currentTld`
const DomainLinks = ({allTlds, currentTld}) => {
    // use destructuring to immediately pull out `allTlds` & `currentTld`

    // filter out the current tld and then map to <li>
    let domainItems = allTlds.filter((tldInfo) => tldInfo.tld !== currentTld)
        .map(({tld, url, label}) => (
            <li><a key={tld} href={url}>{label}</a></li>
        ));

    return (
        <ul className="global-footer__domain-links">
            {domainItems}
        </ul>
    );
};

// arrow function immediately destructures props object into `links`
const SocialLinks = ({links}) => {
    // Return null to indicate you want nothing rendered
    // Returning `undefined` will cause a render error
    if (!links) {
        return null;
    }

    let socialItems = links.map(({url, label}) => (
        <li><a key={url} href={url}>{label}</a></li>
    ));

    return (
        <ul className="global-footer__social-links">
            {socialItems}
        </ul>
    );
};

export default class GlobalFooter extends React.PureComponent {
    static propType = {
        allTlds: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                tld: React.PropTypes.string.isRequired,
                url: React.PropTypes.string.isRequired,
                label: React.PropTypes.string.isRequired
            })
        ).isRequired,
        currentTld: React.PropTypes.string.isRequired,
        seoLinks: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                url: React.PropTypes.string.isRequired,
                label: React.PropTypes.string.isRequired
            })
        ).isRequired,

        // socialLinks are optional
        socialLinks: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                url: React.PropTypes.string.isRequired,
                label: React.PropTypes.string.isRequired
            })
        )
    }

    render() {
        let {allTlds, currentTld, seoLinks, socialLinks} = this.props;

        return (
            <div className="global-footer">
                <SiteLinks />
                <SeoLinks links={seoLinks} />
                <DomainLinks allTlds={allTlds} currentTld={currentTld} />
                <SocialLinks links={socialLinks} />
            </div>
        );
    }
}

As you can see, with this best practice, the render() of GlobalFooter is really clean. It's immediately obvious that the global footer consists of site, SEO, domain and social links. The GlobalFooter is composed of these helper components in true React style. Furthermore, if more content is needed for a given section, it's easy for the developer to know where to add code, and render() of GlobalFooter doesn't need to grow.

Let's take a look at the "bad" approach:

// bad (longer, less maintainable render)
export default class GlobalFooter extends React.PureComponent {
    static propType = {
        allTlds: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                tld: React.PropTypes.string.isRequired,
                url: React.PropTypes.string.isRequired,
                label: React.PropTypes.string.isRequired
            })
        ).isRequired,
        currentTld: React.PropTypes.string.isRequired,
        seoLinks: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                url: React.PropTypes.string.isRequired,
                label: React.PropTypes.string.isRequired
            })
        ).isRequired,

        // socialLinks are optional
        socialLinks: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                url: React.PropTypes.string.isRequired,
                label: React.PropTypes.string.isRequired
            })
        )
    }

    render() {
        let {allTlds, currentTld, seoLinks, socialLinks} = this.props;
        let seoItems = seoLinks.map((link) => (
            <li><a key={link.url} href={link.url}>{link.label}</a></li>
        ));
        let domainItems = allTlds.filter((tldInfo) => tldInfo.tld !== currentTld)
            .map(({tld, url, label}) => (
                <li><a key={tld} href={url}>{label}</a></li>
            ));
        let socialItems;

        if (socialLinks) {
            socialItems = socialLinks.map(({url, label}) => (
                <li><a key={url} href={url}>{label}</a></li>
            ));
        }

        return (
            <div className="global-footer">
                <ul className="global-footer__site-links">
                    <li><a href="/about">About</a></li>
                    <li><a href="/blog">Blog</a></li>
                    <li><a href="/help">Help</a></li>
                    <li><a href="/careers">Careers</a></li>
                </ul>
                <ul className="global-footer__seo-links">
                    {seoItems}
                </ul>
                <ul className="global-footer__domain-links">
                    {domainItems}
                </ul>
                <ul className="global-footer__social-links">
                    {socialItems}
                </ul>
            </div>
        );
    }
}

So why is this code "bad" when it looks like less code? In its current state, there's actually nothing inherently wrong with the code. If the GlobalFooter were to remain in this state forever, then this code would be just fine. The use of BEM-style CSS classes plus separating logic from of the JSX make render() pretty readable in its current state.

However, as we all know, code has entropy; over time it'll change as functionality is added and removed. If the markup becomes more than a simple unordered list, or the logic determining what should be rendered becomes more complicated, having everything mixed together in render() will slowly become unwieldy. You’d need to refactor the code, but with everything intertwined, that could prove to be a big challenge.

Better to start the code off on the right foot.

⬆ back to top

Component methods

Private helper methods

JavaScript doesn't (yet) have a mechanism for declaring a method as private, which would only allow the class to call the method. As a quick signal that a React component method helper is private, prefix it with an underscore (_):

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func
    }

    _handleChange(e) {
        if (this.props.onChange) {
            this.props.onChange(e.target.value);
        }
    }

    render() {
        return (
            <input type="text" onChange={this._handleChange.bind(this)} />
        );
    }
}

// bad (private method does not start with `_`)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func
    }

    handleChange(e) {
        if (this.props.onChange) {
            this.props.onChange(e.target.value);
        }
    }

    render() {
        return (
            <input type="text" onChange={this.handleChange.bind(this)} />
        );
    }
}

⬆ back to top

Method ordering

To make methods within a React component easy to find, order them as follows (eslint react/sort-comp):

  1. propTypes
  2. contextTypes
  3. childContextTypes
  4. defaultProps
  5. state
  6. getChildContext
  7. componentWillMount
  8. componentDidMount
  9. componentWillReceiveProps
  10. shouldComponentUpdate
  11. componentWillUpdate
  12. componentDidUpdate
  13. componentWillUnmount
  14. event handlers (like _handleChange)
  15. getters called from render (like _getType())
  16. helper render methods (like _renderSubHeading())
  17. render

⬆ back to top

JSX Wrapping

Always wrap JSX tags in parentheses as a signal that we're transitioning from vanilla JavaScript to JSX (eslint: react/wrap-multilines):

// good (multi-line JSX wrapped in parentheses)
render() {
    return (
        <Component value="foo">
            <ChildComponent />
        </Component>
    );
}

// good (single-line JSX is also wrapped in parentheses)
let content = (<div>Content</div>);

// bad (missing wrapping parentheses for multi-line)
render() {
    return <Component value="foo">
        <ChildComponent />
    </Component>;
}

// bad (missing wrapping parentheses for single-line)
let content = <div>Content</div>

⬆ back to top

JSX alignment

Single-line

When a component has three props or less with no content, the tag can be on a single line (eslint: react/jsx-max-props-per-line):

// good
<TextInput type="email" name="email" />

// not-so-good (attributes are on the long side)
<TextInput type="email" name="really-long-email-name" placeholder="Enter in your email here please" />

// bad (more than 3 attributes)
<TextInput type="email" name="email" id="email" tabIndex="0" />

⬆ back to top

Multi-line

However, if the props are too long or there are more than three props, the JSX attributes should each be on their own line (eslint: react/jsx-first-prop-new-line):

// good
<TextInput
    type="email"
    name="email"
    placeholder="Enter in your email"
/>

// bad (first attribute is on the same line)
<TextInput type="email"
    name="email"
    placeholder="Enter in your email"
/>

// bad (two attributes on the same line when others are multi-line)
<TextInput
    type="email" name="email"
    placeholder="Enter in your email"
/>

⬆ back to top

Indentation

Indent JSX attributes four spaces (eslint: react/jsx-indent):

// good
<TextInput
    type="email"
    name="email"
    id="email"
    placeholder="Enter in your email"
/>

// bad (attributes aren't indented 4 spaces)
<TextInput
type="email"
name="email"
id="email"
placeholder="Enter in your email"
/>

⬆ back to top

Closing bracket

Align the closing bracket with the opening tag (eslint: react/jsx-closing-bracket-location):

// good
<TextInput
    type="email"
    name="email"
    id="email"
    placeholder="Enter in your email"
/>
<Button type="submit">
    Go!
</Button>

// bad (self-closing isn't aligned with opening tag)
<TextInput
    type="email"
    name="email"
    id="email"
    placeholder="Enter in your email" />

// bad (closing tag isn't aligned with opening tag)
<Button type="submit">
    Go!</Button>

⬆ back to top

Self-closing

If the component has no content, the JSX tag should be self-closing (eslint: react/self-closing-comp) with a space before the self-closing tag (eslint: react/jsx-space-before-closing):

// good
<TextInput type="email" name="email" htmlFor="email" />

// bad (missing space before self-closing tag)
<TextInput type="email" name="email" htmlFor="email"/>

// bad (too much spacing before self-closing tag)
<TextInput type="email" name="email" htmlFor="email"      />

// bad (not self-closing w/ no content)
<TextInput type="email" name="email" htmlFor="email"></TextInput>

⬆ back to top

JSX attribute values

Quoting

Always use double quotes (") for JSX attribute values (eslint: jsx-quotes):

// good
<TextInput type="email" name="email" htmlFor="email" />

// bad (uses single quotes instead of double quotes)
<TextInput type='email' name='email' htmlFor='email' />

⬆ back to top

Boolean values

A true prop value must be explicitly specified as the attribute value (eslint react/jsx-boolean-value):

// good
<TextInput type="email" required={true} />

// bad (missing explicit `true` value)
<TextInput type="email" required />

⬆ back to top

Curly brace padding

When passing a variable to a prop, do not pad the curly braces or equal signs with spaces (eslint: react/jsx-curly-spacing) & react/jsx-equals-spacing):

// good
<TextInput defaultValue={value} />

// bad (padding within curly braces)
<TextInput defaultValue={ value } />

// bad (padding around equals)
<TextInput defaultValue = {value} />

⬆ back to top

children prop

Do not pass the special children prop to a component using a JSX attribute. Instead, pass the children in the contents of the JSX tag:

// good
render() {
    return (
        <Parent color="yellow">
            <Child />
        </Parent>
    );
}


// bad (passes child via the `children` JSX attribute)
render() {
    let child = (<Child />);

    return (
        <Parent color="yellow" children={child} />
    );
}

For more on the special children prop, see: Multiple Components: Children.

⬆ back to top

React key prop

Mandatory key prop

When rendering an array of React components, specify the key prop for each component in the array to aid React's Virtual DOM diffing algorithm (eslint: react/jsx-key):

// good
export default class NamesList extends React.PureComponent {
    static propTypes = {
        names: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                id: React.PropTypes.string,
                name: React.PropTypes.string
            })
        ).isRequired
    }

    render() {
        let {names} = this.props;
        let nameItems = names.map(({id, name}) => (
            <NameItem key={id} name={name} />
        ));

        return (
            <div>{nameItems}</div>
        );
    }
}

// bad (fails to specify `key` prop in loop)
export default class NamesList extends React.PureComponent {
    static propTypes = {
        names: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                id: React.PropTypes.string,
                name: React.PropTypes.string
            })
        ).isRequired
    }

    render() {
        let {names} = this.props;
        let nameItems = names.map(({id, name}) => (
            <NameItem name={name} />
        ));

        return (
            <div>{nameItems}</div>
        );
    }
}

Failing to specify key yields both an ESLint error and a React warning message in the console. It is truly critical for performance. For more info, see: Multiple Components | Dynamic Children.

⬆ back to top

Index as key

Avoid passing the loop iteration index as the key prop, as this ends up confusing React's Virtual DOM diffing algorithm. Instead, always use an id or something else that uniquely identifies each item in the array:

// good
export default class NamesList extends React.PureComponent {
    static propTypes = {
        names: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                id: React.PropTypes.string,
                name: React.PropTypes.string
            })
        ).isRequired
    }

    render() {
        let {names} = this.props;
        let nameItems = names.map(({id, name}) => (
            <NameItem key={id} name={name} />
        ));

        return (
            <div>{nameItems}</div>
        );
    }
}

// bad (uses array index as a key)
export default class NamesList extends React.PureComponent {
    static propTypes = {
        names: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                id: React.PropTypes.string,
                name: React.PropTypes.string
            })
        ).isRequired
    }

    render() {
        let {names} = this.props;
        let nameItems = names.map(({name}, nameIndex) => (
            <NameItem key={nameIndex} name={name} />
        ));

        return (
            <div>{nameItems}</div>
        );
    }
}

Keep in mind that the data uniquely identifies each item in the array could be the item itself in the case of an array of strings or numbers. For more info, see: Index as key is an anti-pattern.

⬆ back to top

Event handlers

Event handler naming

React doesn't support two-way binding. Parent components can update their children by passing props. Child components can communicate with their parent by calling callback functions (also passed by parents to children via props). Prefix the name of the props with on and the internal method handler with _handle (eslint: react/jsx-handler-names):

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func
    }

    _handleChange(e) {
        if (this.props.onChange) {
            this.props.onChange(e.target.value);
        }
    }

    render() {
        return (
            <input type="text" onChange={this._handleChange.bind(this)} />
        );
    }
}

// bad (callback prop is not prefixed with `on`)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        // this should be named `onChange`
        change: React.PropTypes.func
    }

    _handleChange(e) {
        if (this.props.change) {
            this.props.change(e.target.value);
        }
    }

    render() {
        return (
            <input type="text" onChange={this._handleChange.bind(this)} />
        );
    }
}

// bad (event handler is not prefixed with `_handle`)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func
    }

    _onChange(e) {
        if (this.props.onChange) {
            this.props.onChange(e.target.value);
        }
    }

    render() {
        return (
            <input type="text" onChange={this._onChange.bind(this)} />
        );
    }
}

⬆ back to top

Semantic event handler names

When you define an event handler prop that is wrapping a DOM event, name your prop semantically without tying it to the underlying DOM event that triggers it.

For example, say you have a pagination component (Pagination) that contains several child Button components for each page (1, 2, 3, etc.). When a Button is clicked, in the _handleClick handler within the Pagination, the component’s state is updated to reflect the current page.

The component also wants to notify the parent component in _handleClick that the page has changed. Name that prop something semantic, like onPageChange, instead of onPageClick. This way, if the DOM interaction that triggers the page changes (such as a hover), the event handler name doesn't have to change as well.

// good (uses semantic onPageChange event handler name)
export default class Pagination React.PureComponent {
    static propTypes = {
        onPageChange: React.PropTypes.func
    }
    state = {
        page: 1
    }

    _handlePageClick(e, page) {
        this.setState({page});

        let {onPageChange} = this.props;

        if (onPageChange) {
            onPageChange(page);
        }
    }

    render() {
        let buttons = [1, 2, 3, 4, 5].map((page) => (
            <Button key={page} onClick={this._handlePageClick.bind(this, page)} />
        ));

        return (
            <div>{buttons}</div>
        );
    }
}

// bad (event handler name is DOM-specific)
export default class Pagination React.PureComponent {
    static propTypes = {
        onPageClick: React.PropTypes.func
    }
    state = {
        page: 1
    }

    _handlePageClick(e, page) {
        this.setState({page});

        let {onPageClick} = this.props;

        if (onPageClick) {
            onPageClick(page);
        }
    }

    render() {
        let buttons = [1, 2, 3, 4, 5].map((page) => (
            <Button key={page} onClick={this._handlePageClick.bind(this, page)} />
        ));

        return (
            <div>{buttons}</div>
        );
    }
}

⬆ back to top

DOM event handling

When handling a DOM event that will be passed to the parent via a callback, avoid passing the entire DOM event object. Instead, narrow the component's API by passing only the minimal data required.

If you pass the entire DOM event object:

  • It's a leaky interface. The parent now has access to event.target (among other properties), which, in turn gives the parent access to DOM nodes that it should not access. At worst, the parent can manipulate or even remove those DOM nodes.
  • It's a poor interface. Instead of directly receiving the required data, the parent now has to navigate within the event object to get the data it wants.
  • It's a fragile interface. If you later want to change how the event is triggered, maybe by adding another type of DOM event that can also trigger it, a parent may now have to check the type of event object it receives

As a result, this means that you must always handle DOM events it within the component even if it's just a wrapper. Otherwise the event object will still be implicitly returned:

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func,
        onBlur: React.PropTypes.func
    }

    _handleChange(e) {
        if (this.props.onChange) {
            // only the value is passed back
            this.props.onChange(e.target.value);
        }
    }

    // blur is explicitly handled even though it's a basic wrapper
    _handleBlur() {
        if (this.props.onBlur) {
            this.props.onBlur();
        }
    }

    render() {
        return (
            <input
                type="text"
                onChange={this._handleChange.bind(this)}
                onBlur={this._handleBlur.bind(this)}
            />
        );
    }
}

// bad (_handleChange passes entire event object back)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func,
        onBlur: React.PropTypes.func
    }

    _handleChange(e) {
        if (this.props.onChange) {
            this.props.onChange(e);
        }
    }

    _handleBlur() {
        if (this.props.onBlur) {
            this.props.onBlur();
        }
    }

    render() {
        return (
            <input
                type="text"
                onChange={this._handleChange.bind(this)}
                onBlur={this._handleBlur.bind(this)}
            />
        );
    }
}

// bad (blur event isn't wrapped, which implicitly passed back event object)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func,
        onBlur: React.PropTypes.func
    }

    _handleChange(e) {
        if (this.props.onChange) {
            this.props.onChange(e.target.value);
        }
    }

    render() {
        return (
            <input
                type="text"
                onChange={this._handleChange.bind(this)}
                onBlur={this.props.onBlur}
            />
        );
    }
}

⬆ back to top

Event handling in loops

If you pass event handlers to child components created in a loop, chances are you will need a way to uniquely identify which child component caused the event handler to occur. Pass additional arguments to Function.prototype.bind to include the unique identifier:

// shared data
const TEAMS = {
    'warriors': {
        name: 'Golden State Warriors',
        url: 'http://www.nba.com/warriors'
    },
    '49ers': {
        name: 'San Francisco 49ers',
        url: 'http://www.49ers.com'
    },
    'raiders': {
        name: 'Oakland Raiders',
        url: 'http://www.raiders.com'
    },
    'giants': {
        name: 'San Francisco Giants',
        url: 'http://sanfrancisco.giants.mlb.com'
    },
    'athletics': {
        name: 'Oakland Athletics',
        url: 'http://oakland.athletics.mlb.com'
    },
    'sharks': {
        name: 'San Jose Sharks',
        url: 'http://sharks.nhl.com'
    }
};


// good
export default class TeamPicker extends React.PureComponent {
    _handleTeamClick(teamId) {
        location.href = TEAMS[teamId].url;
    }

    render() {
        let teamButtons = Object.keys(TEAMS).map((teamId) => (
            <button onClick={this._handleTeamClick.bind(this, teamId)} key={teamId}>
                {TEAMS[team].name}
            </button>
        ));

        return (
            <div>
                <h2>Pick your team</h2>
                <div>{teamButtons}</div>
            </div>
        );
    }
}


// bad (stores the teamId in the DOM `data-teamId`
// in order to retrieve it onClick)
export default class TeamPicker extends React.PureComponent {
    _handleTeamClick(e) {
        let teamId = e.target.dataset.teamId;

        location.href = TEAMS[teamId].url;
    }

    render() {
        let teamButtons = Object.keys(TEAMS).map((teamId) => (
            <button data-teamId={teamId} onClick={this._handleTeamClick.bind(this)} key={teamId}>
                {TEAMS[team].name}
            </button>
        ));

        return (
            <div>
                <h2>Pick your team</h2>
                <div>{teamButtons}</div>
            </div>
        );
    }
}

While the two examples above look very similar, the differences are very important.

In the good example, we pass the teamId to the .bind() method such that when the onClick handler is invoked, the teamId is the first parameter in _handleTeamClick. This is accomplished with: this._handleTeamClick.bind(this, teamId).

This prevents the need to store the identifier in the DOM using the data-teamId attribute in the bad example. The reason that this is "bad" is because the code now has to unnecessarily touch the DOM in order to retrieve teamId, when JavaScript already has the value.

In this contrived example, the performance difference between good and bad is likely negligible, but at scale, a lot of unnecessary DOM accesses can really slow down an app. Always adhering to this good practice should prevent the need to go hunting for performance bottlenecks in an existing large app.

⬆ back to top

Updating state

If an event handler needs to both update its internal state AND call a prop callback, update the internal state first before calling the callback:

// good
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func
    }

    state = {value: ''}

    _handleChange(e) {
        let value = e.target.value;

        // update state before calling `onChange` prop callback
        this.setState({value});

        if (this.props.onChange) {
            // only the value is passed back
            this.props.onChange(value);
        }
    }

    render() {
        return (
            <input
                type="text"
                value={this.state.value}
                onChange={this._handleChange.bind(this)}
            />
        );
    }
}

// bad (calls `setState` after calling `onChange` prop callback)
export default class TextInput extends React.PureComponent {
    static propTypes = {
        onChange: React.PropTypes.func
    }

    state = {value: ''}

    _handleChange(e) {
        let value = e.target.value;

        if (this.props.onChange) {
            // only the value is passed back
            this.props.onChange(value);
        }

        // don't setState after calling props callbacks!
        this.setState({value});
    }

    render() {
        return (
            <input
                type="text"
                value={this.state.value}
                onChange={this._handleChange.bind(this)}
            />
        );
    }
}

The vast majority of the time the order of setting the state versus calling props callbacks will not make any difference. But it is a good practice to do all the time in case you run into the unique situation where setting state afterwards can have adverse consequences.

In the example above, the TextInput component has no control of what happens when it calls this.props.onChange. The parent's onChange could do a lot of work that could occupy the main execution thread. And since JavaScript is single-threaded, it could be "a while" until execution returns to TextInput so that it could update its state via this.setState({value}). In a nutshell, we can trust what React is doing with setState, but not necessarily the callback props.

Jump down to the State section below for more on handling state.

⬆ back to top

render()

In addition to render() being the last method in a component (see Method ordering), we have additional best practices...

Logic and JSX

React and JSX supporting logic and markup in the same file allows for substantial complexity in markup generation over other traditional templating languages (like handlebars). But with that increased complexity can come a decrease in readability.

In order to maximize both complexity and readability, we suggest keeping all logic out of JSX, except variable references and method calls. Expressions, particularly ternary expressions, should be stored in variables outside of JSX.

// good
render() {
    let {includeHeader} = this.props;
    let buttons = [1, 2, 3, 4, 5].map((page) => (
        <Button key={page} onClick={this._handlePageClick.bind(this, page)} />
    ));
    let header;

    if (includeHeader) {
        header = (<h2>Pagination</h2>);
    }

    return (
        <div>
            {header}
            {buttons}
        </div>
    );
}

// bad (expressions in JSX)
render() {
    let {includeHeader} = this.props;

    return (
        <div>
            {includeHeader ? (<h2>Pagination</h2>) : null}
            {[1, 2, 3, 4, 5].map((page) => (
                <Button key={page} onClick={this._handlePageClick.bind(this, page)} />
            ))}
        </div>
    );
}

The above "bad" example doesn't seem so bad right? But as we know, code tends to grow over time. If we add more expressions, add more markup to the header, or the map gets more more logic, the code will become unwieldy. Setting up this guideline, even in the most simple examples, helps set the code along the right path.

See Helper components for another way to help keep render() lean.

⬆ back to top

Hiding elements

With React's optimized re-rendering via its Virtual DOM abstraction, you should never need to hide elements with CSS (except maybe with some sophisticated CSS animations). Instead, don't render the element when it shouldn't be visible, and render it when it should:

// good
export default class Togglr extends React.PureComponent {
    state = {visible: false}

    _handleToggle() {
        this.setState({
            visible: !this.state.visible
        });
    }

    render() {
        let {visible} = this.state;
        let message;

        if (visible) {
            message = (
                <p>This message is toggled on/off with React not CSS!</p>
            );
        }

        return (
            <div>
                <Button click={this._handleToggle.bind(this)}>Toggle!</Button>
                {message}
            </div>
        );
    }
}

// bad (uses CSS to hide element instead of not rendering)
export default class Togglr extends React.PureComponent {
    state = {visible: false}

    _handleToggle() {
        this.setState({
            visible: !this.state.visible
        });
    }

    render() {
        let {visible} = this.state;
        let messageClassName;

        if (!visible) {
            messageClassName = 'hidden';
        }

        return (
            <div>
                <Button click={this._handleToggle.bind(this)}>Toggle!</Button>
                <p className={messageClassName}>
                    This message is toggled on/off with CSS 🙁!
                </p>
            </div>
        );
    }
}

Friendly reminder: If you want an entire component to be conditionally rendered, the component must return null. Returning undefined will be an error.

⬆ back to top

State

There are two ways of maintaining data in a React component: props and state.

Props are used by a component's parent to configure the component and are immutable within the component.

State is internal to the component so that it can maintain data that will change over time. Whenever the state changes (via setState), the component will re-render. A component's state should not be manipulated outside of it.

Initializing

We rely on the Class fields proposal for initialize state over assigning this.state in the constructor so that it's more declarative. We don't use getInitialState which was the only option in ES5.

// good
export default class Togglr extends React.PureComponent {
    state = {visible: false}

    // rest of the component
}

// bad (assigns `this.state` unnecessarily in constructor)
export default class Togglr extends React.PureComponent {
    constructor(props, context) {
        super(props, context);
        this.state = {visible: false};
    }

    // rest of the component
}

// bad (uses ES5 `getInitialState`)
export default class Togglr extends React.PureComponent {
    getInitialState() {
        return {visible: false};
    }

    // rest of the component
}

⬆ back to top

Defaulting from props

In general, using props to generate state is an anti-pattern because it results in duplicate "sources of truth" (see Props in getInitialState as an anti-pattern). But if your props is properly named to indicate that it's only used as seed data for the component's internally-controlled state, it's no longer an anti-pattern.

We tend to prefix these types of props with default* to match the defaultValue prop React uses for input elements. initial* is also a good prefix.

When defaulting from props, using the declarative class field no longer works as the props are not available in static scope. The only option is to initialize within the constructor:

// good
export default class Togglr extends React.PureComponent {
    constructor(props, context) {
        super(prop, context);
        this.state = {visible: props.defaultVisible};
    }

    // rest of the component
}

// bad (confusingly-named prop)
export default class Togglr extends React.PureComponent {
    constructor(props, context) {
        super(prop, context);
        this.state = {visible: props.visible};
    }

    // rest of the component
}

In the "bad" example, both props and state have a property called visible, which is very confusing. Should you use this.props.visible or this.state.visible. The one in props cannot change, while the one in state can. Naming the prop defaultVisible (as shown in the "good" example) makes things clearer.

As a reminder, setting the state in the constructor should only be used when defaulting from props.

⬆ back to top

Resetting

The most common use case for resetting state is a form that when submitted should return to its original default values. Resetting the state is as simple as setting it to the same object used to initialize it. To keep your code DRY, store the initial state in a constant so it can be used both for initialization and reset:

// good
const INITIAL_STATE = {
    name: '',
    message: ''
}

export default ContactForm extends React.PureComponent {
    state = INITIAL_STATE

    _handleFormSubmit() {
        // code for submitting form

        // reset form (from const)
        this.setState(INITIAL_STATE);
    }

    render() {
        // render form w/ inputs
    }
}

// bad (duplicate initial state)
export default ContactForm extends React.PureComponent {
    state = {name: '', message: ''}

    _handleFormSubmit() {
        // code for submitting form

        // reset form
        this.setState({
            name: '',
            message: ''
        });
    }

    render() {
        // render form w/ inputs
    }
}

In ES5, we could've just called getInitialState to reset the form state, but since we're using the declarative approach, we need to store the initial state object in a const.

⬆ back to top

DOM state

Sometimes you will need to render different markup based on the presence of certain DOM APIs such as SVG support, touch support, CSS animation support, etc.

You may be tempted to use state to maintain the presence of the combination of DOM APIs, but since the value will not be changing over time, state is not the appropriate place for this data. Further, each component instance has its own unique state, and the presence of the DOM APIs will not change between instances. Therefore storing this data in state would also be redundant.

Instead use a private static variable to maintain this data:

// good
import React from 'react';

// maintain a private that stores the combination of the
// DOM APIs
let SUPPORTS_FANCINESS;

const SvgLoading = () => {
    // code for an SVG loading display
};

const FallbackLoading = () => {
    // code for a fallback/image loading display
};

export default class Loading extends React.PureComponent {
    _supportsFanciness() {
        if (SUPPORTS_FANCINESS === undefined && /* determine presence of DOM APIs */) {
            SUPPORTS_FANCINESS = true;
        }

        return SUPPORTS_FANCINESS;
    }

    render() {
        let loadingComponent = this._supportsFanciness
            ? (<SvgLoading />)
            : (<FallbackLoading />);

        return (
            <div>
                {loadingComponent}
            </div>
        );
    }
}


// bad (stores API state in `state`)
import React from 'react';

const SvgLoading = () => {
    // code for an SVG loading display
};

const FallbackLoading = () => {
    // code for a fallback/image loading display
};

export default class Loading extends React.PureComponent {
    state = {
        supportsFanciness: false
    }

    componentDidMount() {
        if (/* determine presence of DOM APIs */) {
            this.setState({
                supportsFanciness: true
            })
        }
    }

    render() {
        let {supportsFanciness} = this.state;
        let loadingComponent = supportsFanciness
            ? (<SvgLoading />)
            : (<FallbackLoading />);

        return (
            <div>
                {loadingComponent}
            </div>
        );
    }
}

Setting state in componentDidMount is a poor practice in general because it can result in unnecessary double calls to render(): the initial render and then the subsequent render as a result of setState. In fact we use the react/no-did-mount-set-state ESLint rule to prevent this.

However, in the "good" example, by storing SUPPORTS_FANCINESS in a private static variable, once the first component tries to render, the value will be calculated and subsequently cached. And we still only have one render.

⬆ back to top

Dangerous props

Avoid using dangerouslySetInnerHTML (eslint: react/no-danger). This will through an ESLint warning instead of an error.

By default all text passed as content to HTML nodes are sanitized by React, preventing exploits. React is safe by default. However, if your text contains HTML you would like rendered, the HTML will be encoded instead of rendered.

Just like with refs, using dangerouslySetInnerHTML is something that should be used sparingly. When it is truly needed, you can temporarily disable rule:

export default class RawContainer extends React.PureComponent {
    render() {
        let innerHTML = {__html: '<span>Safe HTML</span>'};

        return (
            {/* eslint-disable react/no-danger */}
            <div dangerouslySetInnerHTML={innerHTML} />
            {/* eslint-enable react/no-danger */}
        );
    }
}

Once again, this will be a clear signal in code reviews that a special exception is happening.

For more info on dangerouslySetInnerHTML, see: Dangerously Set innerHTML.

⬆ back to top

Refs

Avoid using React refs. Both callback-style (eslint: react/jsx-no-bind) and string (eslint: react/no-string-refs) refs are prohibited by default.

React refs are a way to get references to underlying DOM nodes after the component has been mounted. This is needed when:

  • You need to pass a reference to a DOM node to a non-React library (such as jQuery)
  • You need to manually control a DOM node (such as to call .focus() on an input)
  • You need to manually inspect a DOM node (such as retrieve its dimensions)

Generally when refs are used within React, it can be rewritten to use state that will cause a re-render. This is a preferred approach because it keeps the code within the optimizations of React's Virtual DOM. Causing ESLint errors when React refs are used, strongly encourages the developer to rethink the approach.

However, if refs are needed, use callback-style refs to store a instance reference to it:

export default class RefContainer extends React.PureComponent {
    render() {
        let refCallback = (input) => {
            // stores a reference to the input on the component instance
            // to be used later
            this._input = input;
        };

        return (
            <input type="text" ref={refCallback} />
        );
    }
}

For more info on React refs, see Refs to Components.

⬆ back to top

Mounting

React provides abstractions that free you from touching the DOM directly in most cases, but sometimes you simply need to interact with the DOM API as we saw in the previous section on refs.

If you need to add event handlers to the window or document, there is no React component to which you can add an event handler. You're essentially breaking the default compositional nature of React, but reaching out to the global objects. But sometimes that is unavoidable.

Let's take an example where you would like to make a fetch API request and also listen to the resize event on the window. You would need to make use of the componentDidMount & componentWillUnmount lifecycle methods:

export default class App extends React.PureComponent {
    state = {
        items: [],
        isSmall: true // mobile-first default
    }

    componentDidMount() {
        // Here in `componentDidMount` we *know* the DOM exists so we can safely
        // access DOM APIs

        // When the component has mounted we can make an AJAX request to get our
        // data
        fetch('/get-data')
            .then((resp) => resp.json())
            .then((responseData) => {
                this.setState({
                    items: responseData
                })
            });

        // store a reference to the handler we'll pass to resize so that it can
        // be detached later
        this._resizeHandler = () => {
            this._handleResizeViewPort();
        };

        // actually attach the handler to be called onResize
        // NOTE: You would probably want to throttle the resize event as it
        // fires frequently
        window.addEventListener('resize', this._resizeHandler);

        // handle the initial state before the first resize
        this._handleResizeViewPort();
    }

    componentWillUnmount() {
        // detach resize handler when the component is removed from the DOM
        window.removeEventListener('resize', this._resizeHandler);
    }

    _handleResizeViewPort() {
        let windowWidth = getWindowWidth();

        // update the state based on the window width
        this.setState({
            isSmall: windowWidth < 800
        });
    }

    render() {
        let {items, isSmall} = this.state;
        let filteredItems = items;

        // only use the first 10 items if the window is small
        if (isSmall) {
            filteredItems = filteredItems.slice(0, 10);
        }

        return (
            <List items={filteredItems} />
        );
    }
}

In the (contrived) example above, we want to limit the number of items we pass to the <List /> component based upon the window size. And we receive those items from making an AJAX call (using the new Fetch API).

We have to wait until the DOM exists before we can make the AJAX call or inquire the size of the window. When the component is constructed and when render() is called, we are not guaranteed that the DOM exists (both are called, for instance, during server-side rendering as well). However, when the componentDidMount lifecycle method is called, we are guaranteed that the DOM exists and the component's DOM nodes have been rendered.

Finally, in componentWillUnmount we detach the handler we added. If the component gets removed from the DOM, we no longer need to continue watching on the resize of the window, so we need to explicitly detach the handler otherwise we'll introduce a memory leak.

As a reminder, componentDidMount is only for providing a hook to interact directly for the DOM. You should not use it as a delayed hook to generate state that could have been calculated in the constructor.

For more on these lifecycle methods and others: Component Specs and Lifecycle.

⬆ back to top

Context

Context is the mechanism within React to pass information automatically down through the component tree. Don't use it! 😀 If you want to use it in order to "avoid typing", still don't use it! Context in React is the equivalent of global variables in a program; using context makes it harder to track the flow of data through your React components. Instead, be explicit and specifically pass props down the component tree.

You should never need to define context for components that are in the middle of the component hierarchy. If context does need to be defined, it'll be defined at the top-level container App in order to provide truly App-global data. The best use cases for context are the top-level App wanting to implicitly pass down the current language/locale, information about the logged in user, a theme, etc through the component tree.

When you do use context, avoid using it directly when defining, reading or updating a context property. The context API is still considered experimental and likely to change in future releases. Instead wrap your use of the component API in a higher-order component, so when the API does change it'll be easier to upgrade the single higher-order component versus all of the individual uses.

For information on context, see: React Context.

⬆ back to top

Testing

See Eventbrite React Testing Best Practices

⬆ back to top