Importing React Through the Ages

Before we get started, here's a brief look of valid (today) ways to import React and use the useState hook:

// global
window.React.useState()

// CommonJS
const React = require('react')
React.useState()

// ESModules default import
import React from 'react'
React.useState()

// ESModules named import
import {useState} from 'react'
useState()

// ESModules namespace import
import * as React from 'react'
React.useState()

Below I'll explain where each of these mechanisms came from and why I prefer the last one.


Before the dawn of time

I started writing React code back in the React.createClass days. Here's how we did things back then:

var React = require('react')

var Counter = React.createClass({
  propTypes: {
    initialCount: React.PropTypes.number.isRequired,
    step: React.PropTypes.number,
  },
  getDefaultProps: function () {
    return {step: 1}
  },
  getInitialState: function () {
    var initialCount = this.props.hasOwnProperty('initialCount')
      ? this.props.initialCount
      : 0
    return {count: initialCount}
  },

  changeCount: function (change) {
    this.setState(function (previousState) {
      return {count: previousState.count + change}
    })
  }

  increment: function () {
    this.changeCount(this.props.step)
  },

  decrement: function () {
    this.changeCount(-this.props.step)
  },

  render: function () {
    return (
      <div>
        <div>Current Count: {this.state.count}</div>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.increment}>+</button>
      </div>
    )
  },
})

Yup, var, React.createClass, require, function. Good times.


Classes and Modules

Eventually, we got ES6 (and later) which included modules, classes, and some other syntax niceties:

import React from 'react'

class Counter extends React.Component {
  state = {count: this.props.initialCount ?? 0}
  changeCount() {
    this.setState(({count}) => ({count + change}))
  }
  increment = () => this.changeCount(this.props.step)
  decrement = () => this.changeCount(-this.props.step)
  render() {
    return (
      <div>
        <div>Current Count: {this.state.count}</div>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.increment}>+</button>
      </div>
    )
  }
}

This is when the "how should I import React" question started popping up. A lot of people preferred doing this:

import React, {Component} from 'react'

class Counter extends Component {
  state = {count: this.props.initialCount ?? 0}
  changeCount() {
    this.setState(({count}) => ({count + change}))
  }
  increment = () => this.changeCount(this.props.step)
  decrement = () => this.changeCount(-this.props.step)
  render() {
    return (
      <div>
        <div>Current Count: {this.state.count}</div>
        <button onClick={this.decrement}>-</button>
        <button onClick={this.increment}>+</button>
      </div>
    )
  }
}

Normally this sort of thing isn't even a question. You just do what the library exposes. But React never actually exposed ESModules. It exposes itself as either a global variable called React or a CommonJS module which is the React object which has Component on it (along with other things). But because of the way the code is compiled, both approaches technically work and neither is technically "wrong."

One other note... Looking at the code above you might wonder why we can't change that to:

- import React, {Component} from 'react'
+ import {Component} from 'react'

The reason you need React in there is because (at the time) JSX was compiled to use React:

- <button onClick={this.increment}>+</button>
+ React.createElement('button', {onClick: this.increment}, '+')

So if you use JSX, you need to have React imported as well.


It wasn't long after this that function components became a thing. Even though at the time these couldn't actually be used to manage state or side-effects, they became very popular. I (and many others) got very accustomed to refactoring from class to function and back again (many people just decided it was easier to use class components all the time).


For me, I preferred using function components as much as possible and this is likely the reason that I preferred using import React from 'react' and React.Component rather than import React, {Component} from 'react'. I didn't like having to update my import statement any time I refactored from class components to function components and vice-versa. Oh, and I know that IDEs/editors like VSCode and WebStorm have automatic import features, but I never found those to work very well (I ran into stuff like this all the time).


Oh, and one other interesting fact. If you were using TypeScript instead of Babel, then you'd actually be required to do import * as React from 'react' unless you enabled allowSyntheticDefaultImports.


The Age of Hooks

Then hooks took the scene and the way I wrote components evolved again:

import React from 'react'

function Counter({initialCount = 0, step}) {
  const [count, setCount] = React.useState(initialCount)
  const decrement = () => setCount((c) => c - step)
  const increment = () => setCount((c) => c + step)
  return (
    <div>
      <div>Current Count: {count}</div>
      <button onClick={decrement}>-</button>
      <button onClick={increment}>+</button>
    </div>
  )
}

This brought up another question on how to import React. We had two ways to use hooks:

import React from 'react'
// ...
const [count, setCount] = React.useState(initialCount)

// alternatively:
import React, {useState} from 'react'
// ...
const [count, setCount] = useState(initialCount)

So again, should we do named imports, or just reference things directly on React? Again, for me, I prefer to not have to update my imports every time I add/remove hooks from my files (and again, I don't trust IDE auto-import), so I prefer React.useState over useState.


The new JSX transform and the future of React + ESM

Finally, React 17 was released with basically no real breaking changes but an announcement of great import (pun intended): There's a new JSX transform that means you no longer need to import React to transform JSX. So now you can write:

function App() {
  return <h1>Hello World</h1>
}

And this will get compiled to:

// Inserted by a compiler (don't import it yourself!)
import {jsx as _jsx} from 'react/jsx-runtime'

function App() {
  return _jsx('h1', {children: 'Hello world'})
}

So the import happens automatically now! That's great, but that also means that if you want to migrate to use this new capability, you'll need to remove any import React from 'react' that's just for JSX. Luckily the React team made a script to do this automatically, but they had to make a decision on how to handle situations where you're using hooks. You have two options:

import * as React from 'react'
const [count, setCount] = React.useState(initialCount)

// or
import {useState} from 'react'
const [count, setCount] = useState(initialCount)

Both of these work today, are technically correct, and will continue to work into the future when React finally exposes an official ESModules build.


The React team decided to go with the named imports approach. I disagree with this decision for reasons mentioned above (having to update my import all the time). So for me, I'm now using the import * as React from 'react' which is a mouthful for my hands to type, so I have a snippet:

// snippets/javascript.json
{
  "import React": {
    "prefix": "ir",
    "body": ["import * as React from 'react'\n"]
  },
}

So here's the current state of things:


For me, I'm sticking with import * as React from 'react' so I don't have to worry about my imports. And that's my overly long blog post answer for a very common question I get. Hope it was helpful!



Source: Paper.li

0 comments

Recent Posts

See All