Giter Club home page Giter Club logo

meiosis's People

Contributors

allforabit avatar avionbg avatar barneycarroll avatar cmnstmntmn avatar dependabot[bot] avatar ericop avatar fdaoud avatar foxdonut avatar fuzetsu avatar greduan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

meiosis's Issues

SAM pattern?

Hi,

I went through you tutorial and I like how it shows that you don't need much to build an app with modern ideas (view = f(model)). Especially, whatever rendering lib you use, it does not really matter :)

However, I knew meiosis before as a "framework" implementing the SAM pattern (https://foxdonut.gitbooks.io/meiosis-guide/content/sam.html). This part is missing from the tutorial. I'm wondering what's the relation between your previous gitbook and this tutorial. Do you intend to add state + model + next action predicate in that? It would be nice to show how to implement the SAM pattern as an extension to the meiosis pattern.

Just my thoughts, take it or leave it :)
PS: I'm also very eager to see / try to write an example with SAM and Reasonml, as the pattern matching would work really nicely with that, I believe.

About React Hooks and meiosis state

Now that we have React Hooks, the usage with meiosis pattern is obsolete?

How can I make use of meiosis + react hooks to get a better state management in general?

How to turn off a "map" callback?

I might use map on a stream down in a component that later gets unmounted. How do I stop the callback from continuing to happen?

I may be thinking about it wrong. If so, please correct me. I'm just getting started with this sort of state management.

params handling

@foxdonut
I tested a bit more :-)

one thing I noticed is that with the router's toPath functions all values that are not explicitly defined in routeConfig as path values or query params will be dropped:

query = Object.assign(query, pick(route.params, queryParams));

In practice this means that in the routing example this:

// adding-a-router/src/beverage/view.jsx
<a href={router.toPath(routing.childRoute([Route.Brewer({ id })]))}>Brewer</a>

...will result in id === undefined at the brewer component for route #/coffee/c2/brewer, as currently specified.

With pick in convertToPath we should probably pass the beverage id down as a prop.

Question on initialRoute

I'm new to Meiosis and have some problem getting the initialRoute work.
I'm using:
"meiosis-routing": "^3.0.0",
"meiosis-setup": "^5.1.2",

My configuration:

import { createRouteSegments, routeTransition } from 'meiosis-routing/state'
import createRouteMatcher from 'feather-route-matcher'
import { createFeatherRouter } from 'meiosis-routing/router-helper'

export const Route = createRouteSegments([
  'Home'
  , 'Contatti'
  , 'NotFound'
])

export const routeConfig = {
  Home: '/'
  , Contatti: '/contatti'
}

export const navTo = route => ({ nextRoute: () => Array.isArray(route) ? route : [route] })

export const routeService = state => ({
  routeTransition: () => routeTransition(state.route, state.nextRoute)
  , route: state.nextRoute
})


// ...

export const router = createFeatherRouter({
  createRouteMatcher
  , routeConfig
  , defaultRoute: [Route.NotFound()]
  // , getPath: function () { return document.location.pathname }
})

If I open in browser a url:
https://mysite.com
I get the right Route.Home(), but if I open this url:
https://mysite.com/contatti
initialRoute is not set to Route.Contatti() but stay on Route.Home().

I see in createFeatherRouter code the line 317:

  const initialRoute = parsePath ? parsePath(getPath()) : undefined;

have this line to get url path from browser url?

After router creation if I console.log(router.initialRoute) I get:

router.initialRoute
[
  {
    "id": "Home",
    "params": {}
  }
]

but I think that it have to be:

router.initialRoute
[
  {
    "id": "Contatti",
    "params": {}
  }
]

My app config is:

const app = {
  initial: Object.assign(
    {}
    , navTo(router.initialRoute)
    , home.initial
    , contatti.initial
  )
  , Actions: function (update) {
    return Object.assign(
      { navigateTo: route => update(navTo(route)) }
      , home.Actions(update)
      , contatti.Actions(update)
    )
  }
  , services: [routeService]
  , effects: update => []
}

home.initial and contatti.initial have not navigation function and
, home.Actions(update)
, contatti.Actions(update)
are empty

Have I not understanding the right Meiosis configuration?
Is a bug?

best regards,
Leonardo

Redraw based on only changed data

Hello!

I read your whole tutorial for mithril, awesome reading!

For example in chapter 12:

If I add console.log(label + ' redraw'); into view() function (~67th line) inside createTemperature() I see

Air redraw
Water redraw

every time I click any button!

Is it possible to rewrite meiosis pattern to rebuild vdom only for components dependent of changed data?

For example if I increase water temperature only Water redraw line must appear at console.

API observation (minor nit)

Hey @foxdonut ... as i am getting further along with meiosis, one odd thing to me about the API is the nextAction function declaration (model, proposal, actions).
In most cases, only the model and actions would be needed to make a decision on the next Action I think so requiring that the dev declare all three args to get the actions (which he will surely need for a nextAction) seems to miss out on the benefits of JS where I could just do something like nextAction: (model, actions) => { if(model.foo) { actions.go('foo') }}
I know the proposal can inform the context of the request/loop but as far as I have learned that isnt as common as just reading model state to decide what's next. Is that wrong? I dont want to be frivolous.

Is the view not aware of streams?

Love the introduction here
https://github.com/foxdonut/meiosis/wiki/The-Fundamental-Setup
but found this confusing:

There is just one source stream: update. The view code does not create additional streams. In fact, the view code is not aware of streams at all; views just call the update as a function that was passed as a callback.

When view is called, it is passed the update stream instead of function:

const update = flyd.stream();
   ...
models.map(model => ReactDOM.render(view(model, update), element));

The only reason it works here is because of the flyd api treating streams as functions. But you need to know this in order to write the view correctly, and it would not work when you replace flyd by another stream library with different api. E.g. if update(val) were replaced by update.emit(val), then the view must be aware of it.

What seem conceptually happen here is some sort of lifting where the update was previously defined for functions, but when called, is actually lifted to stream values.

Use of object.assign() in chapter 8

I can see why it doesn't matter in this particular example, but using object.assign() directly does actually have the side-effect of changing the existing value of the stream, while returning the same mutated object rather than a new one.

The stream isn't strictly a "stream" anymore - it's now a "stream-looking" way of performing continuous operations on mutable state, which is a bit confusing and likely could lead to bugs in more complex scenarios? ๐Ÿค”

Likely the appropriate function would be something like:

function(model, value) {
  return Object.assign({}, model, value);
}

Which is side-effect free.

I understand it doesn't lead to problems in this particular case, but you are trying to teach people functional programming patterns, where typically functions are kept pure and free of side-effects?

I'm by no means an expert, it just occurred to me that some people might not understand that using object.assign() in this way, with FRP patterns, in general, isn't really safe?

Keep tracer history consistent when editing the textarea

Currently, when editing the model with the textarea, the view does display correctly but subsequent interactions with the view are inconsistent.

This change makes editing the model with the textarea add to the tracer history, and makes view interactions consistent with the model snapshot from the tracer.

Tutorials for react deleted

Not so much an issue but more of a question, why have all the react related tutorials been deleted? I actually found them helpful.

Does it necessary to pass models from root down to every component to make meiosis pattern works ?

@foxdonut Please help

Refer to the example I have created: https://github.com/meepeek/meiosis-react-example , the component can be dependent on their own by the work of react setState and not meiosis itself. In temperature example, the update and models passed thought from root through every components which makes the code so complex.

It would be hard to scale if we have to pass everything down to make it works. I still cannot find the solution.

Can we have 2 updates in a single app without setState ?

View statics need to be hoisted onto returned func

Statics/function attributes need to be hoisted onto the returned function from createComponent so that HOC composition doesnt get buried.
For example, if you have a React component that is like:

function MyView(props) {
   return <div>me</div>
}
MyView.route = { styles: {... }}
export default MyView

The route static is lost (and inaccessible) because it is wrapped by the createComponent function. (here).
In my opinion the composition of HOC by meiosis is an implementation detail that should be hidden, so statics should be hoisted...something like https://github.com/mridgway/hoist-non-react-statics.

Thots?

Tutorial feedback

Congrats on releasing the new tutorial! The following is my feedback as I read through it. I've written a lot of educational material for programmers (beginners, specifically), so I hope to be of some help.

01 - Hello World

  • The virtual dom concept is introduced very lightly, which is ok for those already familiar with the concept. I suggest linking to a resource that explains it for the uninformed.
  • Great idea on starting with a simple example. I suggest merging the js into the html file to make it even simpler to grasp (I also added a vdom variable to be clear on what m() is returning).
  • Awesome diagram, for some reason this really helps put it together.

02 - View Function

  • A small change in diction can help the motivation for this next lesson: "...we rendered a simple, static message. Now, let's write a function that dynamically produces a message based on application state." Something like that.
  • "This is a plain JavaScript object" -> "The model is a plain JavaScript object" (just to be extra clear).
  • If you decide to keep the var vdom change in the previous lesson: "instead of directly passing what to render" -> "instead of hardcoding our VDOM"
  • Same idea to var vdom = view(initial)
  • To prevent potential confusion, after the "It is just a simple function of the model" sentence I recommend a new sentence-paragraph saying something like "In the next lesson we will learn how to let the user increment the counter model and make the view update in response."

03 - Update Model

  • I recommend moving the "and the function gets passed a DOM event as a parameter" detail to the below Note: section since it's not relevant to the current concept.
  • Nice diagram again :)

04 - Update Function

  • I recommend taking out the increase function currying. It's an elegant outcome, but it might be a roadblock for those not well-versed in fp. I think leaving it out will let the reader better focus on meosis concepts.
  • Same idea with createView. I personally understand the purpose of the abstraction, but in the context of the tutorial, the abstraction provides no tangible benefit to the example code. It'll be more work, but I recommend omitting it here (resulting in something like this) and introducing the concept in a new "components" lesson, where createView is called to create multiple counter views.

That's all I have for now. Great job with this so far. I like meiosis and want it to succeed. I'll be back later with more feedback after you make a round of updates.

meiosis-setup with snowpack?

I'm trying to start with meiosis-setup with snowpack. But I'm getting Uncaught TypeError: meiosis is not a function and sure enough it is undefined.

import meiosis from "meiosis-setup/mergerino";
import simpleStream from "meiosis-setup/simple-stream";
import merge from "mergerino";

const app = {};

console.log(meiosis);

const { update, states } = meiosis({ stream: simpleStream, merge, app });

What am I missing?

Can meiosis pattern have separation model and view ?

Can I have all models and actions in one place before assign to components ? The example I saw had only model and view contained in a component.

I want to make a develop environment where I can call actions manually. This way I can test all actions before coding the UI. Could you give me an example ?

SAM Pattern nap issue

First of I want to say thank you for writing all of this content, I've found it to be a tremendous resource.

When implementing a system that uses a next-action function I discovered that the system generates many duplicate events given the example implementation.

nap: actions => state => {
  if (state.pageId === "DataPage" && !state.data) {
    actions.loadData();
  }
}

const present = flyd.stream();
const actions = app.actions(present);
const states = flyd.scan(app.acceptor, app.initialState(), present)
  .map(app.state);
states.map(app.nap(actions));

Adding additional actions will result in n! calls to nap, state, and anything downstream as a function of the number of updates in the actions. This was easily fixed by tweaking the example using a stream combinator. The previous example then becomes:

nap: actions => state => {
  if (state.pageId === "DataPage" && !state.data) {
    actions.loadData();
  }
}

const present = flyd.stream();

const actions = app.actions(present);

const computed = flyd.scan(app.acceptor, app.initialState(), present)
  .map(app.state);

const states = flyd.combine(
  (computed) => {
    if (!app.nap(actions)(computed())) {
      // If nap indicates that there was no action called then create a new
      // event. This prevents nap from triggerring the same action
      // several times.
      return computed();
    }
  },
  [computed],
);

I derived this solution from the sam.js docs. With this small tweak a new state isn't produced and rendered if a next action was triggered.

Thanks again!

Alternative method for nesting approach

Lets assume we have an encapsulated part of software that we're willing to store all if its storage in one central place, lets call it a module. Consider that this module is made of instances of some components. Each component in meiosis is a function that returns a model and a view. In meiosis you're suggesting storing these models in a hierarchical dict. It's good approach and Redux has something similar too: https://redux.js.org/api/combinereducers.

But in my experience with Redux in a super huge web-application with more than 200 view components, more than 40 tables in server's db some having more than 200k rows served by a rest api I found that it'd be much easier if instead of this hierarchical dict my store was just a single level dicts with unique ids as keys and instance storage data as values. These keys can be uuids or in "model:id" format or even in "model:view:id" format, values can be js objects (or immutable objects), single level here doesn't mean values should be flat numbers or strings, it just means each component is in first level of hierarchy in the store dict. For example in my web-application a view component in this path in the store dict: x->y->z->a->b->c would want to have access to this path and use some data from there X->Y->Z->A->B->C. After the app grew I found it much easier if my store was stored the way I described. If c in example above needs data from C then it should have its whole path in hierarchical dict in hierarchical model but in single level dict model it only needs to know the uuid or if it's stored in "model:id" format it only needs to know the id of the instance.

There are some challenges in this storage model such as what if user is modifying an instance stored in "model:1" but when the edit shouldn't apply to whole web app nor it should be synced with server until he presses some "save" button. (not syncing with server is not the challenge but not syncing with whole web app is a challenge.) It can be solved by following a pattern to store editions in a temporary store and sync it with global storage only when user saves the editions, views should read data from the temporary store dedicated for their edits unless it's not present. I can't remember any other challenges write now but I'm sure there were more and I'm sure the solution to these challenges were straight forward.

This model is similar to how you would store your data in your server using a db server. Most of the reasons that database servers don't save data in a big dict and rather store it in tables and give you an api to access your data in "model:id" format (SELECT * FROM "model"_talbe WHERE id="id") and it's obvious to us that this model of saving data is best approach applies here too.

What do you think about it?

Feature request: bind actions to Actions object

Basically, adding this to the common.js before exporting everything;

  const bindActions = Object.entries(actions).reduce((acc, [key, func], index) => {
    acc[key] = func.bind(actions)
    return acc
  }, {})

  return { update, contexts, states, actions: bindActions }

This way we can call actions from inside actions with simple this.action. It would be a nice addition since you can include more logic between the actions inside them.
I have something similar, basically :

Actions : {
   api : { /* ... */ }
   paging: { /* ... */ }
   sorting: { /* ... */ }
}

where paging and sorting are internally calling the api action, and so on ...

Example Implementation

Hi!

So I follow what's going in with hyperapp development, I'm a big fan of that project. I saw meiosis come up in a discussion and checked it out. I liked it so much I ended up hacking together some tooling to the weather example.

The styling is kind of gross right now, but that's simple enough to fix. Maybe something more polished would be nice for folks to quickly dig in on a local machine

https://github.com/jcpst/template-meiosis

meiosis-setup with typescript: some suggestions

@foxdonut It is really impressive what you've achieved with the TypeScript support, so I have only a few remarks/suggestions/requests.

  1. Can we reduce the number of import lines?

Currently:

import Stream from 'mithril/stream';
import { toStream } from 'meiosis-setup/common';
import { App, Service, setup } from 'meiosis-setup/mergerino';
import { merge } from '../utils/mergerino';

I would prefer:

import Stream from 'mithril/stream';
import { toStream } from 'meiosis-setup/common';
import { App, Service, setup, merge } from 'meiosis-setup/mergerino';

As we are already using mergerino, it makes little sense (considering it is quite stable) to import it separately.

  1. What does getCell do? It has no documentation.
export const { states, getCell } = setup({ stream: toStream(Stream), merge, app });
  1. In your example, you still use:
const { states, update, actions } = meiosis({ stream: simpleStream, merge, app });

However, in your code, I do not see the actions being exported, and instead, you export states and getCell? And in the mergerino version, getCell also has a nest function. What is that used for?

  1. Considering we get setup from meiosis-setup/mergerino, it would make sense that the merge does not have to be supplied separately, i.e. why can't we use:
export const { states, getCell } = setup({ stream: toStream(Stream), app });
  1. Why is toStream first exported, just to convert Stream to StreamLib? Can't this be done internally, so instead, pass
export const { states, getCell } = setup({ stream: Stream, app });

Where Stream is of type mithril/stream or flyd.

  1. Alternatively, as I see that you have implemented a simpleStream yourself, why not leave it out, so it becomes:
import { App, Service, setup } from 'meiosis-setup/mergerino';

export const { states, getCell } = setup({ app });

So the simpleStream will be the default value for stream, the same with merge and mergerino.

  1. When setting up the app, as in:
const app: App<IAppModel, IActions> = {
  initial: Object.assign({}, appStateMgmt.initial) as IAppModel,
  Actions: context => Object.assign({}, appStateMgmt.actions(context)) as IActions,
  /** Services update the state */
  services: [] as Array<Service<IAppModel>>,
};

Why do you write services using lower-case letters, versus Actions and Effects? I prefer the former, but perhaps there is a specific reason for that.

  1. Personal style thingy: In memory-trainer, file app-state.ts, you have the following
actions: context => { // ...

Whereas I prefer, so I don't have to write context.update(...) everywhere:

actions: ({ update, getState }) => { // ...
  1. When creating actions using arrow functions, you cannot call another action. Using function, you can, by using this. However, Typescript coding practices do not really like the usage of this. Isn't it possible to also provide the actions, like the state and update function, as a parameter (in the context as defined before)? So you can always get to another action. I.e. the above example would become:
actions: ({ update, getState, actions }) => { // ...

more food for thought

export const initRoute = routes => ({
routes,
index: 0,
local: routes[0] || {},
child: routes[1] || {}
});
export const nextRoute = route => {
const index = route.index + 1;
return {
routes: route.routes,
index,
local: route.routes[index] || {},
child: route.routes[index + 1] || {}
};
};

Dear @foxdonut

I have been taking a look at your routing WIP. Array based, programmable routes are ๐Ÿ”ฅ. Here are some thoughts that may make the concept easier to grok:

Initially I had problems wrapping my head around what route and routes mean or refer to in different places of the code.

We have route in state: This can be thought of an array of ...routeSegments that match against components in the view tree and to some extend against url paths as well.

Then there is the route component prop (object), that gets calculated based on a given route array in state: This is where the actual routing or navigation is happening on a per component basis. Confusingly, it contains a routes value which references the original route array.

Then on top of this there are various helper functions where it is hard to tell if they work with the component prop route or the state route / routes or both. (e. g. initRoute and nextRoute produce a prop, while parentRoute, childRoute, siblingRoute produce a state route arry.

I think the confusion may be resolved by re-naming the state prop to something different, like routing or navigation or something similar.

I would also suggest to better differentiate the two functions nextRoute and initRoute that each yield the routing prop from the other functions that yield state route arrays.

Consider the following:

export function Routing (route = [], index = 0) {
  return {
    route,
    index,
    local: route[index] || {},
    child: route[index + 1] || {},
    next () {
      return Routing(route, index + 1)
    }
  }
}

And then in the Root

import { Route, Routing } from "somewhere"
import { Beer, Beverages } from 'somewhere-else'

const componentMap = { Beer, Beverages }

function Root ({ state, actions }) {
  const routing = Routing(state.route.current)
  const Component = componentMap[routing.local.id]

  return <div>
     <Button onClick={() => actions.navigateTo([ Route.Beer(), Route.Beverages() ])}>
        Show list of beers
     <Button>
     <Component state={state} actions={actions} routing={routing} />
  </div>
}

And then in Beer

import { Beverages } from "../beverages";
import { Beverage } from "../beverage";

const componentMap = {
  Beverages,
  Beverage
}
function Beer ({ state, actions, routing }) {
  const Component = componentMap[routing.child.id]

  return (
    <div>
      <div>Beer Page</div>
      <Component
        state={state}
        actions={actions}
        routing={routing.next()}
        beverages="beers" />
    </div>
  ) 
}

I think the mechanism will be much easier to understand just by virtue of better differentiating its moving parts.

this might also help clarify the signatures of the other helper functions that consume the routing prop to yield a new route array. (So that you don't end up referencing a routes when in fact there is only route.

const routing = new Routing([ Route.Beer(), Route.Beverages() ])

function showBeerDetails () {
  actions.navigateTo(siblingRoute(routing, [ Route.Beverage({ id: 'b2' }) ])
}

/**
 *
 * @param {Object} routing - the component routing object / prop
 * @param {Array.<{ id: String, params: Object}>} routing.route - original state route ref
 * @param {Number} routing.index - current route segment reference
 *
 * @param {{ id: String, params: Object}} sibling - route segment to inject into route
 * @param {Array.<{ id: String, params: Object }>} [children] - optional sibling children
 *
 * @return {Array.<{ id: String, params: Object }>} - new state route
 */
function siblingRoute (routing = {}, [ sibling, ...children ]) {
  return routing.route.slice(0, routing.index).concat(sibling, children)
}

A am about to try your new routing mechanism with an app of mine where I have to deal with ledgers, that contain accounts, and accounts that contain transactions, and transactions that have lots of transaction details and reference different accounts each. So this will be awesome!

react performance impact?

Hi is there a performance impact with calling ReactDOM.render again upon changes in the model?
Does it work the same as a normal re-render?

Convert to Mergerino

Could you explain why do you convert from Patchinko to Mergerino?
Has Patchinko any important disadvantages to avoid using it in Meiosis?

subtle routing bug

Hello @foxdonut

I tried out simple-stream with the routing example and ran into an issue.

When entering the app at the initial route path: /beer or /coffee or /login or /settings, I get an Error at the Root component

image

For some reason, state is null at that point.
I logged state one level above in the App component

let count = 0
export class App extends Component {
  /* ... */
  render () {
    const state = this.state
    const { actions } = this.props

    count++
    console.log('App state', state, count)
    return el(Root, { state, actions })
  }
}

and it is in fact null:

image

Now the strange thing is, this happens only when 'cold-loading' the the routing example at the /beer/*, /coffee/*, /settings and /login route paths. The /tea and / paths work when cold loading.

Also this issue only occurs when using simple-stream and mithril/stream. For some reason flyd handles this fine.

I have a hard time wrapping my head around this. Can you reproduce this error?

It must me somehow related to the buffered update mechanism in setup, I think.

Alternative way of connecting stream to React function components

Hi foxdonut,

Thanks for your work on meiosis! I wanted to suggest a somewhat-opinionated alternative to how to hook up the state stream to the view in React which differs slightly from the setup and the docs. The following, from the setup, is very useful as a template for getting things set up initially:

export default ({ React, Root }) => ({ states, update, actions }) => {
  const [init, setInit] = React.useState(false);
  const [state, setState] = React.useState(states());

  if (!init) {
    setInit(true);
    states.map(setState);
  }

  return React.createElement(Root, { state, update, actions });
};

The main issues I encountered with the above in a larger codebase were the inconvenience of passing state/update/actions as props and, more importantly, the fact that above re-renders the entire tree on every state update. I've read your post on preventing re-renders. I agree with avoiding focusing on performance too early, but these problems do come up, so it's nice to have a way to deal with it if it does. I thought that your suggested solution with shouldComponentUpdate and an id per component was useful, and this alternative is sort of like that but for a hooks + function component workflow.

export const StatesContext = createContext;

/*
* sliceFn is a pure fn that takes the current state object as a param
* and returns a slice of that state to which a component wishes to subscribe.
*/
export const useStateSlice = (sliceFn) => {
    const states = useContext(StatesContext);
    const stateSlice = sliceFn(states());
    const [slice, setSlice] = useState(stateSlice);
  
    // You can do any sort of comparison you want to decide when to rerender - id check, some deep equals, etc.
    // If you wanted to the hook to be more flexible, could allow passing a custom
    // comparison function, kind of similar to shouldComponentUpdate 
    const changed = (a, b) => JSON.stringify(a) !== JSON.stringify(b);

    useEffect(() => {
        let prevSlice = slice;

        const slices = states.map(newState => {
            const newSlice = sliceFn(newState);
            
            if (changed(prevSlice, newSlice) {
                prevSlice = newSlice;
                setSlice(newSlice);
            }
        });

        return () => slices.end(true);

    }, []);

    return slice;
};

//Top Level
export default ({  Root }) => ({ states, update, actions }) => {
    return <StatesContext.Provider value={states}>
        <Root update={update} actions={actions} />
    </StatesContext.Provider>
};

// Some deeply nested component which only needs a tiny bit of the state
const StatusIndicator = (props) => {
    const status = useStateSlice(state => state.status);
    
    return <div>{status}</div>
};

The general idea is to prevent any unnecessary re-rendering by allowing each component which needs some piece of state to create a stream which takes a slice of the current state (kind of like Redux mapStateToProps) and compares it to the previous slice, updating the component's local state if there has been a change. It's very convenient to use in components that perhaps previously didn't need any state and then require it later. The use of React's context isn't strictly necessary here, as long as the hook has a reference to the state stream.

Just wanted to put this out there, not sure if there was a better place. Maybe this will help someone googling for help with these patterns later on.

How to integrate it into a redux app

Great stuff!, i was wondering it this pattern will be hard to integrate inside an existing redux app, how do i use that pattern only on part of the app on a specific route?

Brilliant!

const models = scan(
(model, patch) => acceptors.reduce(acceptor, accumulator(model, patch)),
acceptors.reduce(acceptor, initialState),
update
);

This solves a real headache!

react context - some clues?

Hello,
I like your work and I try to map some part of my project with meiosis pattern.
I'm using a lot redux and redux-saga with some pro and many cons.
I like the way redux and react-redux use context to propagate store accross component hierarchy.
I'm trying to figure out how to do the same thing with meiosis but I'm stuck.

Do you have any clues or work in progress?

thanks

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.