Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useContext only works in stateless functional component

I'm trying to get to grips with the new useContext function in React. Works great in stateless functionality components. For example:

import React from 'react';
import LocaleContext from '../LocaleContext';

const Link = ({ text, url }) => {
  const locale = useContext(LocaleContext);

  return (
    <a href={`/${locale}/${url}`}>
      {text}
    </a>
  );
};

export default Link;

I also want to use useContext in stateful components, and even non React functions, but when I do so, I get the following error:

Hooks can only be called inside the body of a function component.

The message seems simple enough to understand, but is this really true? I can only use it in a stateless functional component? If so, it seems kind of pointless to me, because it's super easy to use a simple HOC or the traditional method of:

<Locale Consumer>
  {locale => (
    ...
  )}
</LocaleConsumer>

So what gives here? I have the latest version of every package in my project. Not sure if it matters but I'm developing a NextJS site here.

like image 392
CaribouCode Avatar asked Feb 14 '19 17:02

CaribouCode


People also ask

Can we use useContext in function?

React Hooks introduced a number of useful functions, one of which is useContext. It allows you to consume a Context in a function component without using a Consumer directly. This helps to cut down on unnecessary nesting in your components' JSX, making them easier to read.

Can useContext be used in class component?

Can useContext be used in class component? The simple way to access the context values is by wrapping the child component in the Consumer for Class component and for the functional component we can access context with the help of useContext method of React. From there, we can access the context value as props.

What happens if you use useContext without Provider?

In other words, If you don't wrap your components with Context. Provider they won't get re-rendered when the someValues in createContext(someValues) changes. You will get the initial value that you set only in the first render.

Where can I use useContext?

“useContext” hook is used to create common data that can be accessed throughout the component hierarchy without passing the props down manually to each level. Context defined will be available to all the child components without involving “props”.


1 Answers

The problem is what the error says. React hooks aren't available in class components. Due to differences between class components and function components, hooks cannot be used with the former.

As the documentation says,

Hooks let you use more of React’s features without classes. Conceptually, React components have always been closer to functions. Hooks embrace functions, but without sacrificing the practical spirit of React. Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.

Hooks are supposed to address common use cases that are specific to class components which couldn't be previously implemented with stateless functional components alone. Functional components aren't stateless since React 16.8 and are allowed to have a state and trigger own updates.

As for useContext hook,

When the provider updates, this Hook will trigger a rerender with the latest context value.

It would be messed up in class component due to difference between functional and class components. Component function is called each time the component is rendered:

const Foo = props => {

  const context = useContext(Context);
  // use context
}

There's no place in class component that would behave the same way except render function. And if lifecycle-specific tasks go to render function, this means that a class was a wrong choice, and class component needs to be refactored to a function. A counterpart to useContext in class components is contextType, which is currently restricted to single context.

For multiple contexts it's still required to receive them through context Consumer inside render, or as props from higher-order component wrapper:

const contextsHOC = (contexts = {}) => Comp => (
  props => {
    const contextProps = {};
    for (const prop in contexts) {
       // eslint-disable-next-line react-hooks/exhaustive-deps
       contextProps[prop] = React.useContext(contexts[prop]);
    }
    return <Comp {...props} {...contextProps}/>;
  }
);

@contextsHOC({ bar: BarContext, baz: BazContext });
export default class FooComponent extends Component {
  // contexts are mapped to this.props.bar and this.props.baz
  ...
}

// or

class FooComponent extends Component { ... }
export default contextsHOC({ ... })(FooComponent);

Passing contexts as props allows for additional optimization with PureComponent or shouldComponentUpdate.

like image 95
Estus Flask Avatar answered Sep 29 '22 08:09

Estus Flask