Change values in React Context from a nested class component

Viewed 1575

I have this userContext file:

import React from 'react'

const userContext = React.createContext({ loggedInUser: {} })

export { userContext }

I provide correctly a value in App:

import { userContext } from './../contexts/UserContext'

class App extends Component {
           //...
    
  componentDidMount = () => {
    this.authServices
      .isLoggedIn()
      .then(response => this.setState({ loggedInUser: response.data })
      .catch(() => this.setUser(undefined))
  }

    
        return (
          <>
            <userContext.Provider value={this.state.loggedInUser}>
              <Navigation/>
            </userContext.Provider>
          </>
        )
    }

And can access that value in any nested component such as in Navigation:

/* ... */
<userContext.Consumer>
    {value => value && <h1>Hi! {value.username}</h1>}
</userContext.Consumer>
/* ... */

How can I change from these nested components such as Navigation the value from the loggedInUser property in the context?

I don't find the way to attach methods to the context and use them, for example, when user logs out in the Navigation component:

/* ... */

logOut = () => {
    this.authService
        .logout()
        .then(() => /* modify here the loggedInUser from the context */ )

/* ... */

Please note this is a stateful component and I need a non-Hooks solution.

2 Answers

First you need to create a function that changes the user data and pass it in a context so child components can leverage it.

updateUserData = (userInfo) => {
  this.setState({ loggedInUser: userInfo })
}

After that you can pass that function in the context:

<userContext.Provider value={{
  loggedInUser: this.state.loggedInUser,
  updateUserData: this.updateUserData
}}>
  <Navigation/>
</userContext.Provider>

Since you are accessing context Provider the old way you will need to attach the static contextType = userContext; on top of your class to be able to access context value like this outside of your render method:

const { loggedInUser, updateUserData } = this.context;

Now in your logOut access the updateUserData like above and pass it the new userData.

Here is more info about contextType: https://reactjs.org/docs/context.html#classcontexttype

You need to modify your context file and set your method in the context file itself as shown below:

export const UserContext = createContext();

class UserContextProvider extends Component {
    state={
       loggedInUser: {}
    }
  /* ---Logout method here also --- */

   logOut = () => {
     this.authService
    .logout()
    .then(() => {} /* modify here the loggedInUser from the context */ )
   }
    render() {
        return (
            <UserContext.Provider value={{...state},logout:{this.logout}>
                {this.props.children}
            </UserContext.Provider>
        )
    }
}

export default UserContextProvider;

You also need to updated your App.js file by wrapping so that {this.props.children} works fine in context file:

import UserContextProvider from './userContext.js'; // import as per your path

<UserContextProvider>
  <div className="App">
      <Navigation/>
  </div>
</UserContextProvider>

And your Consumer file is modified as follows:

<userContext.Consumer>
   >{(context)=>{
        const {loggedInUser,logout} = context;
         return(
           <div>
             // Your JSX as per your condition
           </div>
        )


</userContext.Consumer>

You can visit my github Context API Project to learn more about Context-API: You will learn context creation and different ways to consume the context also with and without useContext.

https://github.com/emmeiwhite/Context-API

Related