10 Feb 2019, 13:44

Don't over-use useContext: props are still a thing

Share

With the introduction of hooks in React, many things have been made easier, among others the use of context. One common inconvenience on using context so far was the need of using a wrapper element, if the context value was needed in a lifecycle hook. This pattern can be found in many projects:

class App extends React.Component {
    componentDidMount() {
        fetchUser(this.props.userId)
    }
    […]
}

export default () => (
    <DataContext.Consumer>
        {values => <App userId={values.userId} />}
    </DataContext.Consumer>
)

The same functionality can now be easily accomplished with the useContext hook:

const App = () => {
    const { userId } = useContext(DataContext);
    useEffect(() => {
        fetchUser(userId);
    }, [])
    […]
}

That’s really nice, and will be used quite often. In fact, it might be used too often, because using context is so easy now. It’s tempting to use it also in nested components, because it does not take a lot of effort to access context data. So maybe you’ll soon find code like this in your project:

const UserImage = () => {
  const { userId } = useContext(DataContext);
  return <img title={`Image of user ${userId}`} />;
};

It seems as easy as that. No worries about props, just use the context you know your application provides. However, this code has some problems:

  • The parent component does not have any control over the child component
  • It’s hard to test: Even to snapshot-test this simple component, you have to wrap it with a <DataContext.Provider>
  • It depends on context. Since react strives to be as context-free as possible, like all functional oriented software does, it’s an antipattern to depend on context when not needed
  • The component tree is cluttered with <Context.Consumer/ > nodes.

A simple and applicable rule seems to be to use useContext on components preferably high up in the tree, and rely more on passing props than on context. This rule is not new, but will be even more important because of the simple application of context.

const UserPage = () => {
  const { userId } = useContext(DataContext);
  return <UserImage userId={userId} />;
};

const UserImage = ({ userId }) => <img src={`Image of user ${userId}`} />;

Of course there will be edge cases, and you might use context on deeply nested components rather than drill props. But the rule of thumb should be: props first, then context.

comments powered by Disqus