7

Im trying to pass and update a state with useContext;

App.js

import Home from './components/Home'
const UserContext = createContext();

function App() {
  const [name, setName] = useState('Name');

  return (
      <UserContext.Provider value={{name, setName}}>
        <Home/>
      </UserContext.Provider>
  );
}

export default App;

Home.js

import UserContext from '../../App'

function Home() {
    const user = useContext(UserContext);

    return (
        <>
        <label>Your name:</label>
        <input type='text' onChange={e => user.setName(e.target.value)} />
        <p>{user.name}</p>
        </>
    )
}

export default Home;

Im getting this error

TypeError: Cannot read properties of undefined (reading 'name');

How is the correct way to pass state between components with useContext?

Sasquatch
  • 87
  • 1
  • 2
  • 7
  • The code you have posted does not match the error you are receiving. Firstly, it would not compile since you do not export the `UserContext`. Which means you cannot import it into your `Home` Component. Once you have done that, your app does work. I've copy pasted your code into a Create-react-app and it works. – Marco Oct 02 '21 at 20:33
  • How do I export context? – Sasquatch Oct 02 '21 at 20:54

2 Answers2

11

You need to export your UserContext, so it can be imported in the components that need it:

export const UserContext = React.createContext();

function App() {
  const [name, setName] = useState('Name');

  return (
    <UserContext.Provider value={{ name, setName }}>
      <Home />
    </UserContext.Provider>
  );
}

Afterwards you can import it in your App component:

import { UserContext } '../../App'

function Home() {
    const user = useContext(UserContext);

    return (
        <>
            <label>Your name:</label>
            <input type='text' onChange={e => user.setName(e.target.value)} />
            <p>{user.name}</p>
        </>
    )
}
Marco
  • 22,856
  • 9
  • 75
  • 124
6

1. Setting parent state for dynamic context

Firstly, in order to have a dynamic context which can be passed to the consumers, I'll use the parent's state. This ensures that I've a single source of truth going forth. For example, my parent App will look like this:

const App = () => {
  const [name, setName] = useState("John");
  const value = { name, setName };

  return (
   ...
  );
};

The name is stored in the state. We will pass both name and the setter function setName via context later.

2. Creating a context

Next, I created a name context like this:

// set the defaults
const NameContext = React.createContext({
  name: "John",
  setName: () => {}
});

Here I'm setting the defaults for name ('John') and a setName function which will be sent by the context provider to the consumer(s). These are only defaults and I'll provide their values when using the provider component in the parent App.

3. Creating a context consumer

In order to have the name switcher set the name and also showing, it should have the access to the name setter function via context. It can look something like this:

const NameSwitcher = () => {
const { name, setName } = useContext(NameContext);
 return (
    <label>Your name:</label><br />
    <input type='text' onChange={e => setName(e.target.value)} />
    <p>{name}</p>
  );
};

Here I'm just setting the name to input value but you may have your own logic to set name for this.

4. Wrapping the consumer in a provider

Now I'll render my name switcher component in a NameContext.Provider and pass in the values which have to be sent via context to any level deeper. Here's how my parent App look like:

const App = () => {
   const [name, setName] = useState("John");
   const value = { name, setName };

   return (
    <Name.Provider value={value}>
      <NameSwitcher />
    </Name.Provider>
   );
};
Tabish Adnan
  • 104
  • 4