Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal] Lazy models #410

Closed
ctrlplusb opened this issue Jan 21, 2020 · 10 comments
Closed

[Proposal] Lazy models #410

ctrlplusb opened this issue Jan 21, 2020 · 10 comments

Comments

@ctrlplusb
Copy link
Owner

ctrlplusb commented Jan 21, 2020

The addModel and removeModel API doesn't feel as integrated as the rest of the APIs. This proposal will detail an inline approach of declaring your code-split/lazy-loaded models.

Details coming soon.

@ctrlplusb ctrlplusb added this to the 4.0.0 milestone Jan 21, 2020
@kamikazePT
Copy link

kamikazePT commented Jan 22, 2020

My proposal for this would be "wrapping" those 2 functions as in:

instead of
import { StoreProvider } from 'easy-peasy'

we would do

import { Store } from 'easy-peasy'

<Store.Provider store={store}>
...
</Store.Provider>

and on a code-splitted module deep in the react tree....

import { Store } from 'easy-peasy'

<Store.Model model={model}>
...
</Store.Model>

where model would be something as

{
 [modelName] : { 
  ...modelDefinition
 }
}

Store.Model would have a useEffect hook that runs only once, calling "store.addModel" for each key of "model" prop, and on the cleanup of the useEffect, calling "store.removeModel" for each key of the "model" prop :)

if you dont like "Store" with Provider and Model components

we can keep StoreProvider and add StoreModel

@ctrlplusb what do you think?

@ctrlplusb
Copy link
Owner Author

Very interesting API @kamikazePT 💜

@ctrlplusb ctrlplusb modified the milestones: 4.0.0, 3.4.0 Mar 29, 2020
@dan-dr
Copy link

dan-dr commented May 26, 2020

Microsoft did something similar to @kamikazePT 's suggestion for redux: https://github.com/microsoft/redux-dynamic-modules

There's also redux-injectors that seems like it will be easy enough to port
https://github.com/react-boilerplate/redux-injectors

@ctrlplusb
Copy link
Owner Author

ctrlplusb commented Oct 8, 2020

So here is the API I imagined.

Defining a lazy model

import { createStore, lazy } from 'easy-peasy';
//                     👆
import todosModel from './models/todo';

const store = createStore({
  todos: todosModel,
  // We define a lazy model similar to how you define lazy react components 😊
  //          👇
  products: lazy(() => import('./models/products')),
});

Loading a lazy model

import { LoadLazyModel } from 'easy-peasy';

function ProductsSection() {
  return (
    <LoadLazyModel target={model => model.products}>
      <ProductsList />
    </LoadLazyModel>
  )
}

Using a loaded lazy model

Once it is loaded you can use it as normal.

import { useStoreState } from 'easy-peasy';

function ProductDetails({ id }) {
  const product = useStoreState(state => state.products.byId[id], [id]);
  return (
    <>
      <h1>{product.title}</h1>
      {/* ... */}
    </>
  )
}

What I really like about this design is that it keeps the model within the actual store model definition. It becomes a lot easier to imagine and reason about the store state. It also opens up nice opportunity for me to maintain types.

@ctrlplusb ctrlplusb removed this from the 3.4.0 milestone Oct 8, 2020
@ctrlplusb
Copy link
Owner Author

@kamikazePT @dan-dr

@kamikazePT
Copy link

kamikazePT commented Oct 8, 2020

@ctrlplusb

The only thing I dont like that much about that approach is that you are still obligated to know at the root level about the entire app state model

I would like to be able to further down the react tree be able to mount/unmount slices of state

what about this?

import { lazy } from 'easy-peasy';
const ProductsModel = lazy(() => import('./models/products'))

// and then

function ProductsSection() {
  return (
    <ProductsModel>
      <ProductsList />
    </ProductsModel>
  )
}

// or

function ProductsSection() {
  return (
    <ProductsModel keepMounted>
      <ProductsList />
    </ProductsModel>
  )
}

the flag keepMounted would keep the state model even if it was unmounted, otherwise it would wipe and re-create on mount/unmount of ProductsSection

EDIT: But now the lazy call seems worthless, my initial draft was something like

import { Store } from 'easy-peasy'
import model from './model'

function ProductsSection() {
  return (
    <Store.Model model={model}>
      <ProductsList />
    </Store.Model>
  )
}

// or

function ProductsSection() {
  return (
    <Store.Model model={model} keepMounted>
      <ProductsList />
    </Store.Model>
  )
}

@allpro
Copy link
Contributor

allpro commented Oct 15, 2020

It would be great if EP could handle nested/name-spaced models. In my current project we have over a hundred models. To make this scale manageable, every model is categorized by a namespace, like devices.groups, devices.freeze, devices.data, devices.geoFence, devices.data, devices.dataPoints, etc.

I'd like to be able to dynamically add models because some product features are user-specific, but currently it's impossible because EP cannot handle adding a model with a nested path. I have mentioned this before but it wasn't considered a priority then. However if working on updating the dynamic model handling, please consider handling 'model paths' like devices.groups in addition to simple model names in the root of the store.

@ctrlplusb
Copy link
Owner Author

@allpro definitely something to keep in mind 👍

My proposal above would allow for this, however, it does require declaration against the model itself. I believe @kamikazePT is wanting something to be more disjoint to the actual store.

Btw, 💜💜💜💜💜. Thanks so much for your financial contribution. I wish OC would allow me to send messages of thanks. Your continued support means so much. 💜

@kamikazePT
Copy link

@ctrlplusb exactly,something disjoint from the store, I would like to have the store created with just mandatory models for it to work and on demand based on user specific roles or other factors, attach New models that I didnt know even existed when I created the store

Also I quite dig the idea of namespaced path models 👍

Keep up the good work! 😄

@allpro
Copy link
Contributor

allpro commented Oct 15, 2020

I haven't given this syntax much thought, but some off-the-cuff feedback on the approaches discussed...

I agree it would be ideal if models could be added without needing to specify them in advance. This is the most decoupled approach and allows new models to be developed and implemented without modifying the base-models specified when the store is created. With such a pattern, adding models on-the-fly might become the norm!

I don't like requiring Provider syntax, or any React syntax, to extend the store. The store is a stand-alone data object, not a React component, so I'd prefer to interact with it as such. This allows model-loading code to be contained within helpers that are separate from React components, which is most likely how I'd handle it.

I use a getStoreModel() helper that allows accessing the store from anywhere, including outside the render tree. We have many complex data helpers that require store data for logic. For example, when generating menus we need to check permissions and feature-flags to determine what items to include. The point is that it is useful to interact directly with the store object, which should include adding models. There could also be an HOC and/or Hook component as alternatives, but these can wrap the lower-level method. This would be the most versatile combination, allowing any kind of system to be built around it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants