Giter Club home page Giter Club logo

Comments (25)

luisherranz avatar luisherranz commented on August 14, 2024 3

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 using onInitialize actions.
  • Wait until all fetchings finish:
    For example using await overmind.initalized.
  • Render the app HTML:
    For example using const 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 using overmind.rehydrate(initialState)
  • Finally, render React again:
    For example using ReactDomClient.hydrate(<App />, root)

from overmind.

christianalfoni avatar christianalfoni commented on August 14, 2024 2

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.

christianalfoni avatar christianalfoni commented on August 14, 2024 1

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.

christianalfoni avatar christianalfoni commented on August 14, 2024 1

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.

christianalfoni avatar christianalfoni commented on August 14, 2024 1

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.

christianalfoni avatar christianalfoni commented on August 14, 2024

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.

JM-Mendez avatar JM-Mendez commented on August 14, 2024

@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.

luisherranz avatar luisherranz commented on August 14, 2024

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.

luisherranz avatar luisherranz commented on August 14, 2024

What's the reasoning behind a createOvermindSSR instead of a overmind.hydrate API?

from overmind.

christianalfoni avatar christianalfoni commented on August 14, 2024

@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.

christianalfoni avatar christianalfoni commented on August 14, 2024

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.

christianalfoni avatar christianalfoni commented on August 14, 2024

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.

luisherranz avatar luisherranz commented on August 14, 2024

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.

christianalfoni avatar christianalfoni commented on August 14, 2024

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.

luisherranz avatar luisherranz commented on August 14, 2024

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.

christianalfoni avatar christianalfoni commented on August 14, 2024

@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.

christianalfoni avatar christianalfoni commented on August 14, 2024

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.

luisherranz avatar luisherranz commented on August 14, 2024

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.

christianalfoni avatar christianalfoni commented on August 14, 2024

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.

luisherranz avatar luisherranz commented on August 14, 2024

Awesome! We'll check it out :)

from overmind.

vinaydate avatar vinaydate commented on August 14, 2024

@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.

christianalfoni avatar christianalfoni commented on August 14, 2024

@vinaydate You have an example where it is not working? Like a codesandbox? 😄

from overmind.

vinaydate avatar vinaydate commented on August 14, 2024

@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.

neves avatar neves commented on August 14, 2024

@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.

codersguild avatar codersguild commented on August 14, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.