How to change React context programmatically?
Asked Answered
C

2

21

I'm trying to use the new React context to hold data about the logged-in user.

To do that, I create a context in a file called LoggedUserContext.js:

import React from 'react';


export const LoggedUserContext = React.createContext(
  );

And sure enough, now I can get access to said context in other components using consumers, as I do here for example:

  <LoggedUserContext.Consumer>
       {user => (
       (LoggedUserContext.name) ? LoggedUserContext.name : 'Choose a user or create one';
       )}
   </LoggedUserContext.Consumer>

But obviously, for this system to be useful I need to modify my context after login, so it can hold the user's data. I'm making a call to a REST API using axios, and I need to assign the retrieved data to my context:

axios.get(`${SERVER_URL}/users/${this.state.id}`).then(response => { /*What should I do here?*/});

I see no way to do that in React's documentation, but they even mention that holding info of a logged in user is one of the use cases they had in mind for contexts:

Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language. For example, in the code below we manually thread through a “theme” prop in order to style the Button component:

So how can I do it?

Conceive answered 3/4, 2018 at 12:19 Comment(0)
C
17

In order to use Context, you need a Provider which takes a value, and that value could come from the state of the component and be updated

for instance

class App extends React.Component {
   state = {
      isAuth: false;
   }
   componentDidMount() {
      APIcall().then((res) => { this.setState({isAuth: res}) // update isAuth })
   }
   render() {
       <LoggedUserContext.Provider value={this.state.isAuth}>
           <Child />
       </LoggedUserContext.Provider>
   }
}

The section about dynamic context explains it

Cecilacecile answered 3/4, 2018 at 12:32 Comment(3)
Same question as to the other answer: So if I log the user in a component that's 2-3 levels deep in the app, and I need my context to be consumed in the whole app,then I still need to somehow raise the user data all the way up to the app state so that I can set it?Conceive
Yes if you need context to be used inside your whole app then you need a provider at top level and lift the state upCecilacecile
Might be a semi-stupid question here, but what if a malicious user just manipulates the state from dev tools or something? (The API would obviously be protected, but nontheless, I'm curious).Donne
I
5

Wrap your consuming component in a provider component:

import React from 'react';

const SERVER_URL = 'http://some_url.com';

const LoggedUserContext = React.createContext();

class App extends React.Component {
    state = {
        user: null,
        id: 123
    }
    componentDidMount() {
        axios.get(`${SERVER_URL}/users/${this.state.id}`).then(response => { 
            const user = response.data.user; // I can only guess here
            this.setState({user});
        });
    }
    render() {
        return (
            <LoggedUserContext.Provider value={this.state.user}>
                <LoggedUserContext.Consumer>
                    {user => (
                        (user.name) ? user.name : 'Choose a user or create one';
                    )}
                </LoggedUserContext.Consumer>
            </LoggedUserContext.Provider>
        );
    }
}

I gave a complete example to make it even clearer (untested). See the docs for an example with better component composition.

Improvement answered 3/4, 2018 at 12:35 Comment(4)
So if I log the user in a component that's 2-3 levels deep in the app, and I need that context to be consumed in the whole app,then I still need to somehow raise the user data all the way up to the app state so that I can set it?Conceive
Yes, the context will only be reachable inside the providing component, not outside. So if you need that data in all of the app, you should think about wrapping your app component or similar.Improvement
What if I wrap the whole app inside a provider and then had to update it's value from a subcomponent? Lets say, after user fill and submit a form?Phototypography
in that case you would need to pass event handlers down to that component. but it does sound like your scenario is not really what context is made for - user input typically is local state. otherwise - consider a library like redux for that.Improvement

© 2022 - 2024 — McMap. All rights reserved.