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?
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.
({ children }) => <>{children}</>, ); }; With this, we can now just pass an array of context providers and they will be combined from left to right.
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() }
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With