Search code examples
reactjsreact-hooksreact-context

I can't map usestate from AppContexet.Consumer


I send a usestate through AppContexet.Provider to AppContexet.Consumer. I can see at the components - the data arrived - but when I try to map the state, I get an error. What am I doing wrong?

This is the parent:

import {useState} from 'react'
import Ex53Child from './Ex53Child'
import AppContexet from './Appcontext'
import React, {Component} from 'react';

const Ex53perent = ()=>
{
   const [name,setName] = useState('')
   const [age,setAge] = useState()
   const [users,setUsers] = useState ([])
   const [user,setUser] = useState ({name : '' , age : ''})

   return(<AppContexet.Provider value = {{...users}}>
   <div>
       <h1> Ex53perent comp </h1>
       Name: <input type = "text" onChange = {e=>setUser({...user ,name : e.target.value})}/><br/>
       Age : <input type = "text" onChange = {e=>setUser({...user ,age : e.target.value})}/><br/>
       <input type = "button" value ="Add" onClick ={e=>setUsers([...users,user])}/>
       <Ex53Child/>
      
        </div>
        </AppContexet.Provider>
        )
}
export default Ex53perent;

This is the child :

import {useState} from 'react'
import AppContexet from './Appcontext'
import Ex53GrenChild from './Ex53GrenChild'

const Ex53Child = ()=>
{
    const [myUesr,setMyUser] = useState([])
  
    return(<AppContexet.Consumer>

        {
            dataContext =>
            (
                <div>
                    <h1> Ex53Child comp </h1>
                    
                    {
                        dataContext.users.map((item,index)=>
                        {
                            return<il key = {index}>
                                <li>{item.name}</li>
                                <li>{item.age}</li>
                            </il>
                        })
                    }
                    

                    <Ex53GrenChild/>
                
                </div>
            )
        }
        
        </AppContexet.Consumer>
        )
}
export default Ex53Child;

And this is the appcontext file:

import React from 'react'

const AppContexet = React.createContext();

export default AppContexet;

A single value works just fine, but I can't map the array from some reason.


Solution

  • Issue

    The users state is an array:

    const [users, setUsers] = useState([]);
    

    And you are spreading the array into an object:

    <AppContexet.Provider value={{ ...users }}>
      ...
    </AppContexet.Provider>
    

    This makes the context value an object where the array indices are now object keys and the array values are object values.

    Solutions

    Convert context value to array

    You could keep it this way, and then you'll need to convert the context value back to an array in the child:

    <AppContexet.Consumer>
      {dataContext => (
        <div>
          <h1>Ex53Child comp</h1>
          {Object.values(dataContext).map((item, index) => {
              return (
                <il key={index}>
                  <li>{item.name}</li>
                  <li>{item.age}</li>
                </il>
              );
            })
          }
          <Ex53GrenChild/>
        </div>
      )}
    </AppContexet.Consumer>
    

    Fix context value, keep it an array

    It's better to just keep the users and context values consistently an array. Don't spread the users state into the context value.

    <AppContexet.Provider value={{ users }}>
      ...
    </AppContexet.Provider>
    

    Now's the context value is an object with a single users property that is the users state.

    <AppContexet.Consumer>
      {dataContext => (
        <div>
          <h1>Ex53Child comp</h1>
          {dataContext.users.map((item, index) => {
              return (
                <il key={index}>
                  <li>{item.name}</li>
                  <li>{item.age}</li>
                </il>
              );
            })
          }
          <Ex53GrenChild/>
        </div>
      )}
    </AppContexet.Consumer>
    

    Use useContext hook

    Since Ex53Child is a function component it's a bit silly to use the AppContexet.Consumer component render function. Use the useContext hook instead.

    const Ex53Child = () => {
      const [myUesr, setMyUser] = useState([]);
      const { users } = useContext(AppContexet);
       
      return (
        <div>
          <h1>Ex53Child comp</h1>
          {users.map((item, index) => (
            <il key={index}>
              <li>{item.name}</li>
              <li>{item.age}</li>
            </il>
          ))}
          <Ex53GrenChild />
        </div>
      );
    }
    

    Just pass users as a prop

    Additionally, since Ex53perent is directly rendering Ex53Child, using the context is unnecessary complexity when the users state could just simply be passed as a prop.

    const Ex53perent = () => {
      const [name, setName] = useState('');
      const [age, setAge] = useState();
      const [users, setUsers] = useState([]);
      const [user, setUser] = useState({ name: '', age: '' });
       
      return (
        <div>
          <h1> Ex53perent comp </h1>
          Name:{" "}
          <input
            type="text"
            onChange={e => setUser({ ...user, name: e.target.value })}
          />
          <br/>
          Age:{" "}
          <input
            type="text"
            onChange={e => setUser({ ...user, age: e.target.value })}
          />
          <br/>
          <input
            type="button"
            value="Add"
            onClick={e => setUsers([...users, user])}
          />
          <Ex53Child users={users} /> // <-- pass users as prop to child
        </div>
      );
    };
    

    ...

    const Ex53Child = ({ users }) => { // <-- access users from props
      const [myUesr, setMyUser] = useState([]);
       
      return (
        <div>
          <h1>Ex53Child comp</h1>
          {users.map((item, index) => (
            <il key={index}>
              <li>{item.name}</li>
              <li>{item.age}</li>
            </il>
          ))}
          <Ex53GrenChild />
        </div>
      );
    }