Comments (25)
Yes, a overmind.rehydrate
would make HMR easy to set up.
Also, it would be very useful for server side rendering which usually follows this pattern:
- Create the Overmind store in the server.
- Start populating it with the route, data from databases and so on:
For example usingonInitialize
actions. - Wait until all fetchings finish:
For example usingawait overmind.initalized
. - Render the app HTML:
For example usingconst html = ReactDomServer.renderToString(<App />)
- Inject this initial state in the HTML:
For example using<script>var initialState = ${htmlescape(overmind.state)}</script>
- In the client, create store again.
- Use the state stored in
initialState
to rehydrate the store to the exact same point:
For example usingovermind.rehydrate(initialState)
- Finally, render React again:
For example usingReactDomClient.hydrate(<App />, root)
from overmind.
Yes, exactly! But okay!
After the release this Friday the SSR story of Overmind begins. We have already identified how the application should be structured to reuse the config
. That was the first step.
The second is to create an SSR instance, which will look very much like the createOvermindMock
instance.
And then we need to rehydrate... which now gives Overmind an HMR story as well :)
It is noted and will be implemented with SSR after release on Friday.
from overmind.
Aha, so this is not something built into the library, we just need to provide an API that allows the user to put the state on HMR and then reapply it after the hot reload?
Like:
// store.js
const overmind = new Overmind(config)
if (module.hot) {
if (module.hot.data && module.hot.data.state) {
overmind.rehydrate(module.hot.data.state);
}
module.hot.dispose(data => {
data.state = overmind.state
});
}
export default overmind;
Like, pseudo code
from overmind.
Hi guys!
Actually, Overmind automatically reconfigures the instance so you do not have to do anything 😄
So:
if (module.hot) {
// Path to config and main app
module.hot.accept(['./overmind', './App'])
}
I would love for you to try next
as the hot reloading has a different way rehydrating. Make sure you do a npm install overmind@next overmind-react@next
.
@neves Would love for you to try next as well 😄
from overmind.
Okay, so @natew and I did a ton of iterations and latest version of Overmind has a ton of improvements on HMR, also updated docs: https://overmindjs.org/core/devtools#hot-module-replacement
Please reopen if any more issues 😄
from overmind.
I have very little experience with HMR in terms of manually patching up stuff. The only thing we do is to prevent existing instance to be recreated. You have any reference to how others are doing this?
from overmind.
@christianalfoni all that redux does is pass the current state to any newly replaced reducer module, which is usually just the root reducer.
So I think if it's possible to call an action directly on the instance, or even a public method that can accept a state object to attach to the new instance could work. But then we'd have to figure out a way to prevent abusing that.
from overmind.
In MobxStateTree it is as simple as this:
- Save snapshot in
module.hot.data
when HMR triggers and apply it afterwards:
// store.js
const store = Store.create({});
if (module.hot) {
if (module.hot.data && module.hot.data.store) {
applySnapshot(store, module.hot.data.store);
}
module.hot.dispose(data => {
data.store = getSnapshot(stores);
});
}
export default store;
- Then, render the app again using the new App or the new store, whatever has changed:
// index.js
import AppContainer from 'react-hot-loader/lib/AppContainer';
import store from './store';
import App from './App';
const render = async (Component, store) => {
ReactDOM.hydrate(
<AppContainer>
<Component store={store} />
</AppContainer>,
document.getElementById('root')
);
};
if (module.hot) {
module.hot.accept(['./App.js', './store.js'], () => {
const store = require('./store').default;
const Component = require('./App').default;
render(Component, store);
});
}
render(App, store);
from overmind.
What's the reasoning behind a createOvermindSSR
instead of a overmind.hydrate
API?
from overmind.
@luisherranz Ah, you still have overmind.rehydrate
. The createOvermindSSR
is to allow you to effectively reuse your configuration on the server, make mutations and create a description of the state to be rehydrated:
// On server
(req, res) => {
const overmind = createOvermindSSR(config)
overmind.state.foo = "bar2"
res.send(`
<html>
<head>
<script>window.__INITIAL__STATE = ${JSON.stringify(overmind.hydrate())}</script>
</head>
<body>
</body>
</html>
`
}
And in your js:
const onInitialize = (_, overmind) => {
overmind.rehydrate(window.__INITIAL_STATE)
}
Though this is SSR specific as we hydrate/rehydrate mutations, not actual state. So hydrate returns array of actual mutations performed, reducing the payload created :)
So for HMR I am currently thinking that we rather do something very specific for HMR
. Maybe we could even call it overmind.HMR()
, which takes in the previous instance instead of just the state. The reason is that it gives us access to handling whatever else we would need related to HMR. Cleaning up, managing devtools, events, patching whatever else needs patching etc.
So:
const overmind = new Overmind(config)
if (module.hot) {
if (module.hot.data && module.hot.data.overmind) {
overmind.HMR(module.hot.data.overmind);
}
module.hot.dispose(data => {
data.overmind = overmind
});
}
Any thoughts on that? :)
from overmind.
Actually, what I realised is that we already have internal HMR handling. I think we can just use that to reconfigure the existing instance. I will try that first, cause then you do not even have to add this code to your project :)
from overmind.
Okay, so this is a bit trickier than just replacing the state. The reason is that all components are looking at the previous version of the state through "tracking trees". Do not have reference to all these tracking trees to update them with the new state, meaning that when they hot reload they do not know of the new state anyways... need to research this quite a bit more
from overmind.
I really like the idea of send the mutations instead of state in SSR. Brilliant :)
But why not just exposing a overmind.getMutations()
(apart from the current addMutationListener
) and overmind.applyMutations(mutations)
so users can use those APIs whatever they want? For example, it may be useful to syncronize the state between two browsers together via websockets.
from overmind.
Hi again :)
Been working some more on this now, but to answer your question first :)
Finding a more generic API is one way to go, but I fear the concept of syncing state differs so much from SSR hydrate/rehydrate that both might be confusing trying to create a common API. So for example doing a sync of two clients would be addFlushListener
which passes mutations and you can very easily re-apply the mutations with an action just looking at the mutations and running them on the state
object. Where SSR is more about creating a "safe" instance of Overmind where you do a set of mutations, grab them and then put them back when the client instance is created. They differ quite a bit in "when and where", which I think affects the API quite a bit. If that makes sense :)
So what I have done now is introduce a reconfigure
method on overmind
. Since we already detect when a new instance is created (related to HMR) we can just call this method internally and what it does now is basically re-apply the configuration. This includes the state, the actions and also the effects. What it also does is forceFlush
all the components so that they re-render and get the latest state. Maybe the method should be private, I do not know any other scenarios it would be necessary.... but now you do not have to write any code and hot reloading just works :)
Will push it to next
shortly so that it can be tested, but pretty happy about how it turned out :)
from overmind.
Finding a more generic API is one way to go, but I fear the concept of syncing state differs so much from SSR hydrate/rehydrate that both might be confusing trying to create a common API
Ok, agreed. SSR is enough of a common scenario to deserve it's own API. I take it back :)
I'm wondering if it may be also useful to do some optimizations. For example, components will only render once in the server so no tracking is needed. In Mobx you have to use a special API for that called useStaticRendering
:
https://github.com/mobxjs/mobx-react#server-side-rendering-with-usestaticrendering
My last suggestion is about naming. Right now you have:
new Overmind(config, options) // <- create overmind instance in the client - OK
createConnect(overmind) // <- create connect from an overmind instance - OK
createHook(overmind) // <- create hook from an overmind instance - OK
createOvermindSSR(config) // <- create overmind instance in the server - ??
Using new Overmind
to get a client overmind instance but a createOvermindSSR
function to get a server overmind instance doesn't seem consistent. What about an option for SSR:
new Overmind(config, {
ssr: true
})
from overmind.
@luisherranz Hm, yeah... we could mock out the proxy-state-tree
on the SSR version, that is a good point. I will look into that to do minimal work :)
Yeah, the naming here can do some work. Cause what I actually though now was:
createOvermind
, createOvermindMock
and createOvermindSSR
... so we got different create
factories. The problem with doing:
new OVermind(config, {
ssr: true
})
Is that we also have this mock version that needs to conform to the same API. So with this API approach you would have to do:
new Overmind(config, {
mocked: true
})
Which then would conflict with ssr
as you can really only have one of them. But actually under the hood now it is passing in a third argument to the constructor called mode
:
new Overmind(config, options, mode)
Where mode is MODE_TEST
and MODE_SSR
. So I am thinking either we do:
createOvermind
, createOvermindMock
and createOvermindSSR
, which I personally prefer as they are very explicit... and now also consistent :)
Or we can do:
import { Overmind, MODE_TEST, MODE_SSR } from 'overmind'
new Overmind(config, options, MODE_TEST)
new Overmind(config, options, MODE_SSR)
What you think? :)
from overmind.
btw, also doing some smart merging. Cause when we do the HMR internally we return the same instance that you already have and merge in the changed stuff. The tricky part is that we have to identify what is actually changed in the new version. So it compares the original unchanged configuration, with the changes on the instance and with the new configuration... to figure out what is actually changed state and what is completely new state.
The internal HMR also takes care of having multiple Overmind instances in one app.
The actions and effects are of course just overriden as they are not "stateful".
from overmind.
Ups, I forgot about createOvermindMock
.
Then I'd stick with createOvermind
, createOvermindMock
and createOvermindSSR
or I'd use a string option (and set MODE_TEST
, MODE_SSR
internally). Maybe:
new Overmind(config, {
mode: 'client' | 'server' | 'test'
})
But it's your call. Both options look great :)
Hm, yeah... we could mock out the
proxy-state-tree
on the SSR version, that is a good point. I will look into that to do minimal work :)
Amazing. I should dig more into the code so I can help you with this type of stuff!
from overmind.
Cool :)
Okay, but I guess we can close this? Cause now Overmind "reconfigures" the instance when it is hot reloaded... out of the box. So all state, actions and effects are hot reloaded and the components are flushed out.
SSR is also now ready for release I believe.
Please reopen if I missed something here :)
from overmind.
Awesome! We'll check it out :)
from overmind.
@christianalfoni great work. In love of this lib.
I am using create-react-app. Not able to use HMR and hot reloading of Overmind store with previous state.
from overmind.
@vinaydate You have an example where it is not working? Like a codesandbox? 😄
from overmind.
@vinaydate You have an example where it is not working? Like a codesandbox? 😄
My app is quite complex, so I will try to trim it down, for codesandbox, or will create a simple app, and let you know.
P.S. I did not give a codesandbox link, because, I thought this may be a regular issue, which may have a solution, which, I missed/did not know, particularly for CRA HMR overmind combination.
from overmind.
@christianalfoni is it expected to hmr work when a derived state function, getter, action or effect is changed? I'm using CRA3 and I just added this to my index.js:
if (module.hot) {
module.hot.accept('./App', () => {
ReactDOM.render(<App />, document.getElementById('root'))
})
}
When I change any component, the state is keeped, but if I change the state itself, it keep the data but the actions don't update the state anymore.
from overmind.
I am using the createOvermind()
function with createhook()
. Any suggestions on how module.hot
might work there? I tried overmind.rehydrate()
but that does not solve the issue.
My current implementation :
const overmind = createOvermind(config)
if (module.hot) {
if (module.hot.data && module.hot.data.state) {
overmind.rehydrate(module.hot.data.state);
}
module.hot.dispose(data => {
data.state = overmind.state
});
}
render(
<Provider value={overmind}>
<App />
</Provider> ,
document.getElementById('root')
);
from overmind.
Related Issues (20)
- Is there any way to change states outside component? HOT 10
- [BUG] State mutation error HOT 3
- Svelte + Overmind : Function called outside component initialization
- [BUG] Statemachine errors on send(), possible documentation issue HOT 2
- Devtools: add the option to fail silently if port wasn't found HOT 2
- [BUG] Vue State Hooks loses reactivity once using in nested components. HOT 4
- [BUG] States changes are not reflecting in the UI but dev tool shows the value changed HOT 1
- [BUG] Typo in "ensureMutationTrackingIsEnabled" function HOT 3
- Error: While trying to resolve module `phoenix` HOT 1
- [BUG] TypeError with webpack 5 HOT 1
- [BUG] proxy-state-tree - You are mutating the path HOT 1
- Does the graphql package work with any subscriptions on a graphql server or is it specific to the phoenix framework?
- [BUG] svelte reactivity broken on subsequent state changes cross-components HOT 4
- [Question] Is the project dead? Is so, what are options to migrate to? HOT 5
- [BUG] Incompatible with React 18 with Strict Mode HOT 13
- Page doesn't re-render after navigation in Next 13 HOT 12
- [BUG] `overmind-react` - multiple overmind named instances HOT 1
- [BUG] Overmind JS Not working in Next JS 13 HOT 1
- [BUG] Usage of 'useSyncExternalStore' causes errors with react below version 18 HOT 3
- [BUG] Can't mock overmind for jest snapshots HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from overmind.