How to change the value of a Context with useContext?
Asked Answered
S

2

157

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>
Sefton answered 17/2, 2019 at 23:31 Comment(0)
D
156

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>
Dignify answered 18/2, 2019 at 0:6 Comment(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?Sefton
@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 with a render prop that you mentioned.Dignify
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?Nicole
So how does one prevent constant re-rendering when using the useState hook for the variable that is handed in to the provider?Palpable
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.Rappee
The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<null>' - since we've set createContext with null, I can't really pass the data to value props.Cot
I am getting this error Property 'accountInfo' does not exist on type '{}'.ts(2339)Defoe
This one answer explains Content like no other resource on the internet! Kudos.Apollonian
So for a login page to set the login context values we have to pass the callbacks down from the root App component as values? That would mean login logic would not be in the login component which feels a bit odd.Enyedy
This create a cyclic import, which is a warning.Notornis
O
43

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" });
};
Offhand answered 15/4, 2020 at 8:24 Comment(3)
"Invalid hook call. Hooks can only be called inside of the body of a function component. " from the hookSchnurr
@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 :)Vilayet
This doesn't work. You always get the default value of the settings.Risteau

© 2022 - 2024 — McMap. All rights reserved.