I understand React Context is useful for sharing state across a deep component tree without having to do prop drilling. However, I can't find a good way to share the same context values across multiple separate component trees. The app I work in isn't a true React front-end; rather, it has a few React trees sprinkled throughout the page. I'd like to share state among them using context. I know Redux can handle this, but I'm trying to avoid using that if possible.
// counter-context.jsx
import React, { useState, createContext } from 'react';
const CounterContext = createContext();
const CounterContextProvider = ({ children }) => {
const [counter, setCounter] = useState(0);
function increment() {
setCounter(counter + 1);
}
return (
<CounterContext.Provider value={{ counter, increment }}>
{children}
</CounterContext.Provider>
);
};
export { CounterContextProvider, CounterContext };
// FirstComponent.jsx
import React, { useContext } from 'react';
import { render } from 'react-dom';
import { CounterContextProvider, CounterContext } from './CounterContext';
const FirstComponent = () => {
const { counter } = useContext(CounterContext);
return <p>First component: {counter}</p>;
};
render(
<CounterContextProvider>
<FirstComponent />
</CounterContextProvider>,
document.getElementById('first-component')
);
// SecondComponent.jsx
import React, { useContext } from 'react';
import { render } from 'react-dom';
import CounterContext from './CounterContext';
const SecondComponent = () => {
const { counter } = useContext(CounterContext);
return <p>Second component: {counter}</p>;
};
render(
<CounterContextProvider>
<SecondComponent />
</CounterContextProvider>,
document.getElementById('second-component')
);
If I add a button in FirstComponent that invokes increment, I want the updated counter value to also reflect in SecondComponent. But given a new instance of CounterContextProvider is created, I'm not sure how to accomplish that. Is there a relatively clean way, or am I stuck with Redux?
The two components can be rendered using react Portal so that they both fall under the same Parent.Now there will be one provider that can be used by both the components
As Hari Abinesh mentioned, portals is a way to solve this problem, if your use case is not too complex.
Docs: https://reactjs.org/docs/portals.html
// counter-context.jsx
import React, { useState, createContext } from 'react';
const CounterContext = createContext();
const CounterContextProvider = ({ children }) => {
const [counter, setCounter] = useState(0);
function increment() {
setCounter(counter + 1);
}
return (
<CounterContext.Provider value={{ counter, increment }}>
{children}
</CounterContext.Provider>
);
};
export { CounterContextProvider, CounterContext };
// FirstComponent.jsx
import React, { useContext } from 'react';
import { render, createPortal } from 'react-dom';
import { CounterContextProvider, CounterContext } from './CounterContext';
const FirstComponent = () => {
const { counter } = useContext(CounterContext);
const portalElement = document.getElementById('span-portal')
return (
<React.Fragment>
<p>First component: {counter}</p>
{createPortal(counter, portalElement)}
</React.Fragment>
);
};
render(
<CounterContextProvider>
<FirstComponent />
</CounterContextProvider>,
document.getElementById('first-component')
);
// SecondComponent.jsx
import React from 'react';
import { render } from 'react-dom';
const SecondComponent = () => {
return <p>Second component: <span id='span-portal'></span> </p>;
};
// At this point, you might not even need context at all if it's only going
// to be rendered from one component.
render(
<SecondComponent />,
document.getElementById('second-component')
);
Another way is to use react-relink. It is much simpler than Redux, designed to work across different React component trees and you don't need to wrap your components in any <Provider>s. That is, if you're willing to resort to more than just built-in React solutions.
// counter-context.jsx -> counter-source.jsx
import { createSource, useRelinkValue } from 'react-relink';
export const CounterSource = createSource({
default: 1,
})
export function useCounterValue() {
return useRelinkValue(CounterSource);
}
export function incrementCounter() {
CounterSource.set(c => c + 1)
}
// FirstComponent.jsx
import React from 'react';
import { render } from 'react-dom';
import { useCounterValue } from './counter-source';
const FirstComponent = () => {
const counter = useCounterValue();
return <p>First component: {counter}</p>;
};
render(
<FirstComponent />,
document.getElementById('first-component')
);
// SecondComponent.jsx
import React from 'react';
import { render } from 'react-dom';
import { useCounterValue } from './counter-source';
const SecondComponent = () => {
const counter = useCounterValue();
return <p>Second component: {counter}</p>;
};
render(
<SecondComponent />,
document.getElementById('second-component')
);
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