Just trying to get my head around easy-peasy and typescript.
Got any recommendations for how to do initial store setup from a fetch call? (i.e. page loads and makes a fetch call to get initial data to fill the store with) - note not considering server rendering atm...
I was looking at just kicking off a fetch and doing a store.dispatch from outside the react rendering in my index.ts or using useEffect() hook from within a component, both couldn't get the typings right. Before I went too deep I wondered what the "easy-peasy" way would be?
I suppose possibly my question is simply - how do I run a "store action" in a useEffect() hook? or indeed outside of a React component (therefore outside of the StoreProvider scope)?
I'm not sure if I understand your question clearly @nickmeldrum but you can execute any store action outside of react context with:
js
const actions = store.getActions();
actions.yourModel.yourAction(payload)
Thanks for the response @mkuklis - that is indeed helpful.
Can you also help me understand how I might execute a store action within a useEffect() hook?
btw one of my confusions was that I was still trying to store.dispatch() this action - (i am coming from naked redux.)
After some playing about I see that you can just execute an action like this without having to dispatch it. Is that right? - (feel free to just point me at a documentation page - I did read through the typescript tutorial and api reference, but possibly missed an important bit!)
Oh yes! Good to see you getting involved here @nickmeldrum 馃榾
After some playing about I see that you can just execute an action like this without having to dispatch it.
Yep, this is a fundamental part of the API. By calling an Easy Peasy action all the dispatching is handled for you.
In terms of your problem. There are a couple of solutions.
Imagine we had the following store:
const store = createStore({
data: null,
setInitialState: action((state, payload) => {
state.data = payload;
}),
fetchInitialState: thunk(async (actions) => {
const data = await fetchData();
actions.setInitialState(data);
})
});
Below are a few of the possible solutions you could employ.
Use an async thunk to preload the data prior to render
You can leverage the Promise returned by the thunk to wait for it's resolution prior to rendering your application.
store.getActions().fetchInitialState().then(() => {
// Data loaded 馃帀
ReactDOM.render(
<StoreProvider store={store}>
<App />
</StoreProvider>,
rootEl
)
})
This is great as you can be sure the minimum data for your application is ready prior to render. The downside is that you can't pre-render any part of your application, and depending on the performance characteristics of the data hydration this could create a negative perceived performance for the user.
Utilise react lifecyle to fetch initial state
In this option we use a "componentDidMount"-like effect to fire the thunk.
function App() {
const data = useStoreState(state => state.data);
const fetchInitialState = useStoreActions(actions => actions.fetchInitialState);
useEffect(() => {
fetchInitialState(); // 馃憟 dispatch
}, [])
return (
<>
<Header />
{/* We will conditionally load the Main component based on data being avail */}
{data && <Main />}
<Footer />
</>
)
}
This provides a more robust user experience as they will see the <Header /> and <Footer /> components being rendered whilst we wait for the data to be fetched. A much nicer user experience, however, it can be far more complicated to pull off a partial rendering solution like this as you may need to be performing nested pre-condition checks etc.
Option 1 may be the preferable solution until you can actually identify that there is a negative performance perception going on.
Hope this helps.
I have to say, having spend years on naked redux solutions and recently done a couple of MobX projects, I am loving how you have maintained the power and robustness of redux while removing so much boiler plate and making such a good api surface.
Thanks also for your detailed reply - it really helps solidify a few things for me. I think some of my confusion is because I'm trying to grok hooks at the same time as groking easy-peasy!
With option 1:
whats the benefit of putting the initial fetch as a thunk action? why not just a "regular function in a regular module" - which can then use store.getActions() in it's returned promise to dispatch the setInitialState() method?
i.e. something like (pseudo code):
const getInitialData = () => {
fetch('url').then(data => {
store.getActions().setInitialState(data);
});
}
re option 2 (componentdidmount style): thanks sooooo much - this example really helps me understand both easy-peasy AND hooks 馃槢
(btw, re your comment: "The downside is that you can't pre-render any part of your application"):
my option 1 method actually has me doing the initial ReactDOM.render() and then kicking off the initialFetch() method.
this means that I still get to pre-render an initial "data-less" state while doing the fetch. The reason I might do this at a page/app level instead of in a component is if the data I am loading is specific to the page you are on instead of the component - multiple components using shared data for instance.
I would be interested in your thoughts about this!
I have to say, having spend years on naked redux solutions and recently done a couple of MobX projects, I am loving how you have maintained the power and robustness of redux while removing so much boiler plate and making such a good api surface.
馃檶
whats the benefit of putting the initial fetch as a thunk action? why not just a "regular function in a regular module" - which can then use store.getActions() in it's returned promise to dispatch the setInitialState() method?
Matter of preference really. Some people prefer to encapsulate all API interaction in thunks, but your way is completely okay too. I don't have a strong preference either way personally - I tend to do what feels right on a per project basis.
re option 2 (componentdidmount style): thanks sooooo much - this example really helps me understand both easy-peasy AND hooks 馃槢
Do look at the hooks courses on EggHead, also 100% read the official docs. There are some strangeness around hooks but after using them for a while you will start to realise their value.
Also the React Hot Loader replacement (React Fast Refresh) depends on hooks based components to work well IIRC. So your overall dev experience will be boosted by committing.
my option 1 method actually has me doing the initial ReactDOM.render() and then kicking off the initialFetch() method.
this means that I still get to pre-render an initial "data-less" state while doing the fetch. The reason I might do this at a page/app level instead of in a component is if the data I am loading is specific to the page you are on instead of the component - multiple components using shared data for instance.
Absolutely no problem with this. It's kind of a blend between the two options I proposed. As long as you handle the case where the data may not yet be available then it's all good. 馃憤
awesome sauce, thanks for the advice 馃挴
Most helpful comment
Oh yes! Good to see you getting involved here @nickmeldrum 馃榾
Yep, this is a fundamental part of the API. By calling an Easy Peasy action all the dispatching is handled for you.
In terms of your problem. There are a couple of solutions.
Imagine we had the following store:
Below are a few of the possible solutions you could employ.
Use an async thunk to preload the data prior to render
You can leverage the
Promisereturned by the thunk to wait for it's resolution prior to rendering your application.This is great as you can be sure the minimum data for your application is ready prior to render. The downside is that you can't pre-render any part of your application, and depending on the performance characteristics of the data hydration this could create a negative perceived performance for the user.
Utilise react lifecyle to fetch initial state
In this option we use a "componentDidMount"-like effect to fire the thunk.
This provides a more robust user experience as they will see the
<Header />and<Footer />components being rendered whilst we wait for the data to be fetched. A much nicer user experience, however, it can be far more complicated to pull off a partial rendering solution like this as you may need to be performing nested pre-condition checks etc.Option 1 may be the preferable solution until you can actually identify that there is a negative performance perception going on.
Hope this helps.