79

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
  • 4,701
  • 5
  • 32
  • 63
Randy Burgess
  • 3,842
  • 5
  • 35
  • 57

2 Answers2

87

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
  • 83,208
  • 13
  • 152
  • 148
  • 5
    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
  • 3
    @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
  • 3
    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
16

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"});
};
mohamed khaled
  • 161
  • 1
  • 3
  • 1
    "Invalid hook call. Hooks can only be called inside of the body of a function component. " from the hook – Kevin Danikowski Jul 27 '20 at 15:43
  • @ZiiM Instead of making changes by editing this post....you can post that changes as an answer...I think that the original intent of the post should be preserved **:)** – Anurag Dabas Apr 15 '21 at 18:28