dcsan
dcsan

Reputation: 12275

how to call update react context state / or call methods from non-react classes?

I have a react context which is being used as a provider across multiple components.

I want to modify that state and update the UI from a normal method totally outside of React components. ie NOT a functional component or class, just a normal piece of JS code.

From what I can see the provider/context can only be accessed from within a render method or React.FC

I can pass a function in but it seems only from some type of React component that is sandwiched between <Context.Provider>

and useContext only lights up once the relevant item has been passed into a render loop.

Is there a way I can create some type of store that I can just call a setState() method on its data, but where such updates would be reactive in the UI?

[edit] The reason I want this is cos I have an external API call that gives me a long running callback. I don't see a need to wrap that API up in all kinds of react stuff as I want that module to be portable (eg to server side) and just interact with app state to update the UI display.

Upvotes: 1

Views: 2421

Answers (2)

HMR
HMR

Reputation: 39270

I assume you are asking this because you don't use a state manager like redux where you could dispatch an action from anywhere to reset state.

One option; as you already mentioned; is to create your own store and store provider. Maybe something like this will work for you:

const store = (initialState => {
  let value = initialState;
  let listeners = [];
  const getState = () => value;
  const setState = fn => {
    value = fn(value);
    listeners.forEach(l => l(value));
  };
  const subscribe = listener => {
    listeners.push(listener);
    return () =>
      (listeners = listeners.filter(f => f !== listener));
  };
  return { getState, setState, subscribe };
})({ counter: 1 }); //pass initial state to IIFE

const Store = React.createContext();

function Provider({ store, children }) {
  const [state, setState] = React.useState(
    store.getState()
  );
  React.useEffect(
    () =>
      store.subscribe(() => {
        const lastState = store.getState();
        //if a lot of setState calls are made synchronously
        //  do not update dom but let it batch update state
        //  before triggering a render
        Promise.resolve().then(() => {
          if (lastState === store.getState()) {
            setState(store.getState());
          }
        });
      }),
    [store]
  );
  return (
    <Store.Provider value={state}>
      {children}
    </Store.Provider>
  );
}

function App() {
  const state = React.useContext(Store);
  return <div>{state.counter}</div>;
}

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

//call store setState from anywhere
setInterval(
  () =>
    store.setState(state => ({
      ...state,
      counter: state.counter + 1,
    })),
  1000
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 1

Amin Paks
Amin Paks

Reputation: 276

As far as I can tell you still need to opt into the React API if you wanna build the UI with React.

Here what you need to do:

  • Need a context to share the value across your component tree
  • Need a Provider component at the root
  • Need to subscribe to the external API to receive the new values and set that to your context
  • Use the context consumer to get the shared value

Here is an example how this could work

Upvotes: 1

Related Questions