Reputation: 12275
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
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
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:
Here is an example how this could work
Upvotes: 1