In this article, we are going to use Mobx with the root store pattern and Next.js framework to do a server-side rendering of pages. If you haven't,...
For further actions, you may consider blocking this person and/or reporting abuse
Thanks Ivan , the article inspired me
Thanks Ivan.
You are doing the following:
This would mean the child store needs a hydrate method. What would that look like?
Imagine the store is a
todo
list. If you were to load the data in the browser you would hit the nextjs API endpoint viafetch
and load thetodo's
. However, since we are first rendering the data in the backend, we should get that data directly from thetodo
database, hydrate the store, so when the page is sent to the browser it already contains all the data, as a consequence if you try to load the page with javascript disabled, it will still show all thetodos
. As for thehydrate
method, it should accept an object (an array of todo's) in this example, and to whatever it needs to do depending on the business logic. In this case, the hydrate method would probably have the same logic as the method that is calling thefetch
when loading from the browser, that is because they would probably handle identical data.Thanks, but theres some issues i have noticed with this approach for me.
1 . App bundle size and speed
With mobx it is not necessary to wrap an app with the storeprovider. I have noticed that if your stores get big this bloats the main app bundle size and penalizes on SEO performance. I shaved 40% of my page sizes by removing this.
2 . I have noticed that using useRootStore() causes uneeded re-renders
Doing this will cause the component to rerun useRootStore every time an update happens. This is uneeded calling of useRootStore and in big apps will cause performance issues.
VS
importing the store outside of the component which is way more performant
3 . Hydration on the server?
Now im not sure how to hydrate on serverside using my above approaches but I would rather hydrate in the browser by passing in the data from serverside through props to the store than bloat my app by wrapping it with a provider and slowing it down using the hook. It is any case probable that the store has to run in the browser to initialise itself, and that will probably trigger a dom rerender - im not sure? So that computation is happening anyways which means the app is building twice - on serverside and in the client. Possibly, im not sure.
final thoughts
As I am on a drive for performance and battery life on devices - some of the points above have come to my attention. The above comments are not to degrade your solution but bring forward what I have discovered.
You can use mobx without providers or hooks, just import the store, and reference it directly in the component.
The reason I'm using the provider component and
useRootStore
hook is because of the testability of the components. Without them, how are you going to substitute (mock) the store when testing the components with something likereact-testing-library
?Root store is used in
_app
component, everything that is included in the_app
component must be bundled for "first page load". If you don't need your store present through the whole app, don't include it in the root store, and load it only on the pages where it is needed, that will decrease the size.Question: suggested best practice for holding onto a store constructed with props #74
I believe that you are wrong here because
useRootStore
can never trigger a render, all it does is return a stable reference to the mobx store (always the same object). The same applies for the root provider component, it will never trigger a render, the only purpose of the provider component and the hook is to enable easier testing (maybe I should have mentioned that in the article). In your example, how are you going to test your component in isolation?In nextjs, the application always renders twice once on the server, and then on the client, it has nothing to do with Mobx.
getServerSideProps
andgetStaticProps
are essentially hydration for react components and the principle is the same. You need to decide if you want to render an "empty" store in the browser when the page loads.If care about SEO, then you will render on the server, so the crawler has something to index.
All good questions, keep'em coming :)
1 . That is a good idea but then at some point its not clear what is available in the root context and what is not. But thats development.
2 . I am not doing testing so i didnt think of that, good point. To clarify this unnecesarry rerender discussion, I see you have done storeContext different to another example I was looking at and I got the two mixed up. To clarify your code anyways, does useContext know that you are passing an existing context in?
How much execution happens when this is run:
^ because from what I have seen, this function will run everytime a change happens in a component, if the component is wrapped with mobx observable. And if useContext does some processing when it is invoked then i am worried about this extra processing.
3 . I was not saying mobx causes both renders, i was saying that they happen anyways. Hydration happens anyways.
So if I do this, it has the same effect as what you were doing:
useRootStore
hook is just a way to get to the root store without explicitly declaring the store inside the component, it's just getting back the value that is stored in context, and if there is no context above, it throws.3.Yes exactly :) You just need to guard against calling the
hydration
more than once, currently if will hydrate every time theCompany
component is rendered.Is there any way to prevent re-hydrating after we've initially hydrated our store?
For instance let's say we have a page that hydrates itself as follows:
The first time we visit this page, that's great we hydrate the client side store successfully.
The second time we visit this page, this is not so great. Our client store is already hydrated but we now waste time making an unnecessary API call before serving our HTML to the client.
How can we avoid re-hydrating data that has already been hydrated if the user navigates back to a previous page?
You can have a simple boolean flag on your store if the store is hydrated, do not hydrate it again.
Sorry to clarify I mean how can we prevent the initial fetch request from sending a second time?
you can't.
Thanks for the article! I have 2 questions? 1) When is best place to fetch the data to hydrate the components? On initializeStore or how can I access to store(in server) in getServerSideProps? 2) Is possible to persist the userStore for example to avoid auth fetch in each request, or is needed to refresh the store(in server)?
getServerSideProps
rather you should just return data that will later be used do populate stores - passed to the root store provider by the page component.Amazing article, thank you.
Thanks
Hey your first link in the article goes to gooogle.com/ ???
Thanks for pointing this out, I have updated the link.