Easy-peasy: [Proposal] Lazy models

Created on 21 Jan 2020  路  10Comments  路  Source: ctrlplusb/easy-peasy

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.

feedback-wanted

All 10 comments

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?

Very interesting API @kamikazePT 馃挏

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

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.

@kamikazePT @dan-dr

@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>
  )
}

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.

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

@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! 馃槃

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

giacomorebonato picture giacomorebonato  路  5Comments

rmariuzzo picture rmariuzzo  路  6Comments

ifyoumakeit picture ifyoumakeit  路  5Comments

gino8080 picture gino8080  路  5Comments

positimo picture positimo  路  3Comments