Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React multiple contexts

Tags:

reactjs

I am using functions which are passed down through context.

ChildComponent.contextType = SomeContext; 

Now I use this.context.someFunction();. This works.

How can I do this if I need functions from two different parent components?

like image 367
ATOzTOA Avatar asked Nov 16 '18 22:11

ATOzTOA


People also ask

Can you have multiple context in React?

To keep context re-rendering fast, React needs to make each context consumer a separate node in the tree. If two or more context values are often used together, you might want to consider creating your own render prop component that provides both.

How do you combine context providers for cleaner React code?

({ children }) => <>{children}</>, ); }; With this, we can now just pass an array of context providers and they will be combined from left to right.


2 Answers

You can still use function-as-a-child consumer nodes with the 16.3 Context API, which is what the React documentation suggests doing:

// Theme context, default to light theme const ThemeContext = React.createContext('light');  // Signed-in user context const UserContext = React.createContext({   name: 'Guest', });  class App extends React.Component {   render() {     const {signedInUser, theme} = this.props;      // App component that provides initial context values     return (       <ThemeContext.Provider value={theme}>         <UserContext.Provider value={signedInUser}>           <Layout />         </UserContext.Provider>       </ThemeContext.Provider>     );   } }  function Layout() {   return (     <div>       <Sidebar />       <Content />     </div>   ); }  // A component may consume multiple contexts function Content() {   return (     <ThemeContext.Consumer>       {theme => (         <UserContext.Consumer>           {user => (             <ProfilePage user={user} theme={theme} />           )}         </UserContext.Consumer>       )}     </ThemeContext.Consumer>   ); } 

To use functions in context in your component you'd typically wrap your component in a HOC so the context is passed in as props:

export const withThemeContext = Component => (   props => (     <ThemeContext.Consumer>       {context => <Component themeContext={context} {...props} />}     </ThemeContext.Consumer>   ) )  const YourComponent = ({ themeContext, ...props }) => {   themeContext.someFunction()   return (<div>Hi Mom!</div>) }  export default withThemeContext(YourComponent) 

If you're running React 16.8+ you can also use hooks to do this more cleanly without using HOCs:

import React, { useContext } from "react"  const YourComponent = props => {   const theme = useContext(ThemeContext)   const user = useContext(UserContext) } 

Or, if you consume these contexts a lot, you can even make a custom hook to simplify further:

const useTheme = () => useContext(ThemeContext) const useUser = () => useContext(UserContext)  const YourComponent = props => {   const theme = useTheme()   const user = useUser() } 
like image 174
coreyward Avatar answered Sep 20 '22 08:09

coreyward


Another solution is to create a separate context providing the other contexts:

import React, { createContext, memo, useContext } from "react"; import isEqual from "react-fast-compare";  export const MultiContext = createContext(null); MultiContext.displayName = "MultiContext";  export const MultiContextProvider = memo(   function({ map, children }) {     const contextMap = {};     for (const i in map) {       contextMap[i] = useContext(map[i]);     }      return (       <MultiContext.Provider value={contextMap}>         {children}       </MultiContext.Provider>     );   },   (prevProps, nextProps) => isEqual(prevProps.children, nextProps.children) );  MultiContextProvider.displayName = "MultiContextProvider"; 

Example usage:

class DemoConsumer extends React.Component {   static contextType = MultiContext;    render() {     return JSON.stringify({       someValue: this.context.SomeContext.someValue,       otherValue: this.context.OtherContext.otherValue,     });   } }  function App() {   return (     <MultiContextProvider map={{ SomeContext, OtherContext }}>       <MultiContextDemoClassConsumer />     </MultiContextProvider>   ); } 

Demo:

const {   createContext,   memo,   useContext,   useState,   useEffect, } = React;  const MultiContext = createContext(null); MultiContext.displayName = "MultiContext";  const MultiContextProvider = memo(   function({ map, children }) {     console.log("render provider");     const contextMap = {};     for (const i in map) {       contextMap[i] = useContext(map[i]);     }      return (       <MultiContext.Provider value={contextMap}>         {children}       </MultiContext.Provider>     );   },   (prevProps, nextProps) => isEqual(prevProps.children, nextProps.children) ); MultiContextProvider.displayName = "MultiContextProvider";  const initialMinutes = new Date().getMinutes(); const MinutesContext = createContext(initialMinutes); MinutesContext.displayName = "MinutesContext";  const IncrementContext = createContext(0); IncrementContext.displayName = "IncrementContext";  class MultiContextDemoClassConsumer extends React.Component {   static contextType = MultiContext;    render() {     return JSON.stringify(this.context);   } }  const multiContextMap = { MinutesContext, IncrementContext }; function App() {   const forceUpdate = useForceUpdate();    const [minutes, setMinutes] = useState(initialMinutes);   useEffect(() => {     const timeoutId = setInterval(() => {       // console.log('set minutes')       setMinutes(new Date().getMinutes());     }, 1000);     return () => {       clearInterval(timeoutId);     };   }, [setMinutes]);    const [increment, setIncrement] = useState(0);    console.log("render app");    return (     <MinutesContext.Provider value={minutes}>       <IncrementContext.Provider value={increment}>         <MultiContextProvider map={multiContextMap}>           <MultiContextDemoClassConsumer />         </MultiContextProvider>         <button onClick={() => setIncrement(i => i + 1)}>Increment</button>         <button onClick={forceUpdate}>Force Update</button>       </IncrementContext.Provider>     </MinutesContext.Provider>   ); }  ReactDOM.render(<App />, document.getElementById("root"));
<script type="module">   import React from 'https://dev.jspm.io/react@16';   import ReactDOM from 'https://dev.jspm.io/react-dom@16';   import useForceUpdate from 'https://dev.jspm.io/[email protected]';   import isEqual from 'https://dev.jspm.io/[email protected]';   window.React = React;   window.ReactDOM = ReactDOM;   window.useForceUpdate = useForceUpdate.default;   window.isEqual = isEqual; </script> <div id="root"></div>
like image 20
Dimitar Nestorov Avatar answered Sep 24 '22 08:09

Dimitar Nestorov