Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to share React context state across separately mounted component trees

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?

like image 650
Andrew Gibson Avatar asked Jan 30 '26 01:01

Andrew Gibson


2 Answers

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

like image 130
Hari Abinesh Avatar answered Feb 01 '26 15:02

Hari Abinesh


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')
);
like image 31
GlyphCat Avatar answered Feb 01 '26 17:02

GlyphCat



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!