Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Access React Context outside of render function

I am developing a new app using the new React Context API instead of Redux, and before, with Redux, when I needed to get a list of users for example, I simply call in componentDidMount my action, but now with React Context, my actions live inside my Consumer which is inside my render function, which means that every time my render function is called, it will call my action to get my users list and that is not good because I will be doing a lot of unecessary requests.

So, how I can call only one time my action, like in componentDidMount instead of calling in render?

Just to exemplify, look at this code:

Let's suppose that I am wrapping all my Providers in one component, like this:

import React from 'react';  import UserProvider from './UserProvider'; import PostProvider from './PostProvider';  export default class Provider extends React.Component {   render(){     return(       <UserProvider>         <PostProvider>           {this.props.children}         </PostProvider>       </UserProvider>     )   } } 

Then I put this Provider component wrapping all my app, like this:

import React from 'react'; import Provider from './providers/Provider'; import { Router } from './Router';  export default class App extends React.Component {   render() {     const Component = Router();     return(       <Provider>         <Component />       </Provider>     )   } } 

Now, at my users view for example, it will be something like this:

import React from 'react'; import UserContext from '../contexts/UserContext';  export default class Users extends React.Component {   render(){     return(       <UserContext.Consumer>         {({getUsers, users}) => {           getUsers();           return(             <h1>Users</h1>             <ul>               {users.map(user) => (                 <li>{user.name}</li>               )}             </ul>           )         }}       </UserContext.Consumer>     )   } } 

What I want is this:

import React from 'react'; import UserContext from '../contexts/UserContext';  export default class Users extends React.Component {   componentDidMount(){     this.props.getUsers();   }    render(){     return(       <UserContext.Consumer>         {({users}) => {           getUsers();           return(             <h1>Users</h1>             <ul>               {users.map(user) => (                 <li>{user.name}</li>               )}             </ul>           )         }}       </UserContext.Consumer>     )   } } 

But ofcourse that the example above don't work because the getUsers don't live in my Users view props. What is the right way to do it if this is possible at all?

like image 295
Gustavo Mendonça Avatar asked Apr 13 '18 05:04

Gustavo Mendonça


People also ask

Where is useContext used in React?

React Context is a way to manage state globally. It can be used together with the useState Hook to share state between deeply nested components more easily than with useState alone.

Can we use useContext in class component?

The simple way to access the context values is by wrapping the child component in the Consumer for Class component and for the functional component we can access context with the help of useContext method of React. From there, we can access the context value as props.


2 Answers

EDIT: With the introduction of react-hooks in v16.8.0, you can use context in functional components by making use of useContext hook

const Users = () => {     const contextValue = useContext(UserContext);     // rest logic here } 

EDIT: From version 16.6.0 onwards. You can make use of context in lifecycle method using this.context like

class Users extends React.Component {   componentDidMount() {     let value = this.context;     /* perform a side-effect at mount using the value of UserContext */   }   componentDidUpdate() {     let value = this.context;     /* ... */   }   componentWillUnmount() {     let value = this.context;     /* ... */   }   render() {     let value = this.context;     /* render something based on the value of UserContext */   } } Users.contextType = UserContext; // This part is important to access context values 

Prior to version 16.6.0, you could do it in the following manner

In order to use Context in your lifecyle method, you would write your component like

class Users extends React.Component {   componentDidMount(){     this.props.getUsers();   }    render(){     const { users } = this.props;     return(              <h1>Users</h1>             <ul>               {users.map(user) => (                 <li>{user.name}</li>               )}             </ul>     )   } } export default props => ( <UserContext.Consumer>         {({users, getUsers}) => {            return <Users {...props} users={users} getUsers={getUsers} />         }}       </UserContext.Consumer> ) 

Generally you would maintain one context in your App and it makes sense to package the above login in an HOC so as to reuse it. You can write it like

import UserContext from 'path/to/UserContext';  const withUserContext = Component => {   return props => {     return (       <UserContext.Consumer>         {({users, getUsers}) => {           return <Component {...props} users={users} getUsers={getUsers} />;         }}       </UserContext.Consumer>     );   }; }; 

and then you can use it like

export default withUserContext(User); 
like image 192
Shubham Khatri Avatar answered Sep 21 '22 21:09

Shubham Khatri


Ok, I found a way to do this with a limitation. With the with-context library I managed to insert all my consumer data into my component props.

But, to insert more than one consumer into the same component is complicated to do, you have to create mixed consumers with this library, which makes not elegant the code and non productive.

The link to this library: https://github.com/SunHuawei/with-context

EDIT: Actually you don't need to use the multi context api that with-context provide, in fact, you can use the simple api and make a decorator for each of your context and if you want to use more than one consumer in you component, just declare above your class as much decorators as you want!

like image 45
Gustavo Mendonça Avatar answered Sep 25 '22 21:09

Gustavo Mendonça