149

Using the useContext hook with React 16.8+ works well. You can create a component, use the hook, and utilize the context values without any issues.

What I'm not certain about is how to apply changes to the Context Provider values.

1) Is the useContext hook strictly a means of consuming the context values?

2) Is there a recommended way, using React hooks, to update values from the child component, which will then trigger component re-rendering for any components using the useContext hook with this context?

const ThemeContext = React.createContext({
  style: 'light',
  visible: true
});

function Content() {
  const { style, visible } = React.useContext(ThemeContext);

  const handleClick = () => {
    // change the context values to
    // style: 'dark'
    // visible: false
  }

  return (
    <div>
      <p>
        The theme is <em>{style}</em> and state of visibility is 
        <em> {visible.toString()}</em>
      </p>
      <button onClick={handleClick}>Change Theme</button>
    </div>
  )
};

function App() {
  return <Content />
};

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.2/umd/react-dom.production.min.js"></script>
Flip
  • 6,233
  • 7
  • 46
  • 75
Randy Burgess
  • 4,835
  • 6
  • 41
  • 59

2 Answers2

150

How to update context with hooks is discussed in the How to avoid passing callbacks down? part of the Hooks FAQ.

The argument passed to createContext will only be the default value if the component that uses useContext has no Provider above it further up the tree. You could instead create a Provider that supplies the style and visibility as well as functions to toggle them.

Example

const { createContext, useContext, useState } = React;

const ThemeContext = createContext(null);

function Content() {
  const { style, visible, toggleStyle, toggleVisible } = useContext(
    ThemeContext
  );

  return (
    <div>
      <p>
        The theme is <em>{style}</em> and state of visibility is
        <em> {visible.toString()}</em>
      </p>
      <button onClick={toggleStyle}>Change Theme</button>
      <button onClick={toggleVisible}>Change Visibility</button>
    </div>
  );
}

function App() {
  const [style, setStyle] = useState("light");
  const [visible, setVisible] = useState(true);

  function toggleStyle() {
    setStyle(style => (style === "light" ? "dark" : "light"));
  }
  function toggleVisible() {
    setVisible(visible => !visible);
  }

  return (
    <ThemeContext.Provider
      value={{ style, visible, toggleStyle, toggleVisible }}
    >
      <Content />
    </ThemeContext.Provider>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 10
    So is it correct to say that the only value of `useContext` hook is that it allows you to avoid wrapping a component with a `Context.Consumer` parent and passing the context value via a function call to the rendered child? – Randy Burgess Feb 18 '19 at 01:48
  • 4
    @RandyBurgess Yes, that's right. Creating context with hooks works the same as before, it's just that you consume it with the `useContext` hook rather than a [`Context.Consumer`](https://reactjs.org/docs/context.html#contextconsumer) with a render prop that you mentioned. – Tholle Feb 18 '19 at 08:54
  • 4
    Won't setting `value` to an object like this re-render all consumers every time the Provider re-renders, per [the caveats section of the context documentation](https://reactjs.org/docs/context.html#caveats)? – neurodynamic Apr 27 '20 at 03:41
  • 1
    So how does one prevent constant re-rendering when using the `useState` hook for the variable that is handed in to the provider? – Bombe Jun 06 '20 at 10:02
  • Hello, so we have to pass the states functions to the provider as props in App.js. Is there a way to put these functions in an other file (service or store) and import it ? It seems complicated because the useState functions and the functions that call them (toggleVisible for example) must be in the component that render the context provider. We cant import it from an other component. – Getzel Aug 16 '20 at 18:54
  • The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps' - since we've set createContext with null, I can't really pass the data to value props. – doobean Sep 11 '20 at 18:44
  • I am getting this error `Property 'accountInfo' does not exist on type '{}'.ts(2339)` – Aravin Jun 05 '22 at 17:00
  • This one answer explains Content like no other resource on the internet! Kudos. – Rutwick Gangurde Aug 08 '23 at 19:19
42

You can use this approach, no matter how many nested components do you have it will work fine.

// Settings Context - src/context/Settings
import React, { useState } from "react";

const SettingsContext = React.createContext();

const defaultSettings = {
  theme: "light",
};

export const SettingsProvider = ({ children, settings }) => {
  const [currentSettings, setCurrentSettings] = useState(
    settings || defaultSettings
  );

  const saveSettings = (values) => {
   setCurrentSettings(values)
  };

  return (
    <SettingsContext.Provider
      value={{ settings: currentSettings, saveSettings }}
    >
      {children}
    </SettingsContext.Provider>
  );
};

export const SettingsConsumer = SettingsContext.Consumer;

export default SettingsContext;
// Settings Hook - src/hooks/useSettings
import { useContext } from "react";
import SettingsContext from "src/context/SettingsContext";

export default () => {
  const context = useContext(SettingsContext);

  return context;
};
// src/index
ReactDOM.render(
  <SettingsProvider settings={settings}>
    <App />
  </SettingsProvider>,
  document.getElementById("root")
);
// Any component do you want to toggle the theme from
// Example: src/components/Navbar
const { settings, saveSettings } = useSettings();

const handleToggleTheme = () => {
  saveSettings({ theme: "light" });
};
Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Mohamed Shaaban
  • 529
  • 4
  • 5