All Projects → kana-sama → coredux

kana-sama / coredux

Licence: MIT License
Dualism to Redux. Two-way combining of redux modules

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to coredux

cpsfy
🚀 Tiny goodies for Continuation-Passing-Style functions, fully tested
Stars: ✭ 58 (+314.29%)
Mutual labels:  composition, reducer
Microstates
Composable state primitives for JavaScript
Stars: ✭ 1,312 (+9271.43%)
Mutual labels:  composition, lens
BitLens
🔎 Have your bits and eat them too! A C++17 bit lens container for vector types.
Stars: ✭ 20 (+42.86%)
Mutual labels:  lens
v-bucket
📦 Fast, Simple, and Lightweight State Manager for Vue 3.0 built with composition API, inspired by Vuex.
Stars: ✭ 42 (+200%)
Mutual labels:  composition
useReduction
useReducer without boilerplate
Stars: ✭ 36 (+157.14%)
Mutual labels:  reducer
comby-reducer
A simple program reducer for any language.
Stars: ✭ 65 (+364.29%)
Mutual labels:  reducer
swift-composable-app-example
Example iOS app built with module composition in mind.
Stars: ✭ 79 (+464.29%)
Mutual labels:  composition
hermes-js
Universal action dispatcher for JavaScript apps
Stars: ✭ 15 (+7.14%)
Mutual labels:  reducer
reducer-class
Boilerplate free class-based reducer creator. Built with TypeScript. Works with Redux and NGRX. Has integration with immer.
Stars: ✭ 25 (+78.57%)
Mutual labels:  reducer
hard-reducer
Type friendly reducer helper
Stars: ✭ 56 (+300%)
Mutual labels:  reducer
RxReduxK
Micro-framework for Redux implemented in Kotlin
Stars: ✭ 65 (+364.29%)
Mutual labels:  reducer
riduce
Get rid of your reducer boilerplate! Zero hassle state management that's typed, flexible and scalable.
Stars: ✭ 14 (+0%)
Mutual labels:  reducer
SCF4-SDK
Motorized zoom lens controller Kurokesu SCF4 module featuring STM32 controller and Onsemi LC898201 driver control software
Stars: ✭ 14 (+0%)
Mutual labels:  lens
react-compose-events
A Higher-Order Component factory to attach outside event listeners
Stars: ✭ 25 (+78.57%)
Mutual labels:  composition
optic
An Erlang/OTP library for reading and updating deeply nested immutable data.
Stars: ✭ 34 (+142.86%)
Mutual labels:  lens
vue-snippets
Visual Studio Code Syntax Highlighting For Vue3 And Vue2
Stars: ✭ 25 (+78.57%)
Mutual labels:  composition
redux-recompose
A Redux utility belt for reducers and actions. Inspired by acdlite/recompose.
Stars: ✭ 70 (+400%)
Mutual labels:  reducer
ad-lens
Automatic Differentiation using Pseudo Lenses. Neat.
Stars: ✭ 16 (+14.29%)
Mutual labels:  lens
pyroclastic
Functional dataflow through composable computations
Stars: ✭ 17 (+21.43%)
Mutual labels:  composition
restate
A redux fractal state library 🕷
Stars: ✭ 55 (+292.86%)
Mutual labels:  composition

coredux

Dualism to Redux. Two-way combining of redux modules.

Example: https://github.com/kana-sama/coredux-example

Motivation

Reducer is just a composable first class setter. Also if some reducer changes a value on some action, it will change this value in a more complicated structure after combining multiple reducers into object reducer too. So, we can combine reducers as we please.

But with selectors everything is different , in the selectors we must know the full path to value in the state, so after reducers relocating we should fix some selectors. IMO it is a problem, we can do it better.

Welcome new concept - Node. It is not just setter, like a reducer, and not just getter, like a selector, it is a COMPOSABLE combination of setters and getters. We can combine any nodes and all selectors will automatically know, how to get the path to value, like actions in reducers.

Example

A simple example of the frequent case - table of posts - tuple of ids and normalized entities of posts.

Let's begin with defining initial state:

const defaultState = {
  areFetching: false,
  ids: [],
  entities: new Map(),
};

After we will define actions - commands for updating (setting) data:

export const fetchPostsRequest = createAction();
export const fetchPostsSuccess = createAction();

And queries for selecting (getting) data:

export const getArePostsFetching = createQuery();
export const getPostsIds = createQuery();
export const getPostsEntities = createQuery();
export const getPosts = createQuery();

And the realization of these setters and getters as the node:

export const posts = createNode(defaultState)
  .setter(fetchPostsRequest, state => ({ ...state, areFetching: true }))
  .setter(fetchPostsSuccess, (state, posts) => ({
    areFetching: false,
    ids: posts.map(post => post.id),
    entities: new Map(posts.map(post => [post.id, post])),
  }))
  .getter(getArePostsFetching, select => state => state.areFetching)
  .getter(getPostsIds, select => state => state.ids)
  .getter(getPostsEntities, select => state => state.entities)
  .getter(getPosts, select =>
    createSelector(
      select(getPostsEntities),
      select(getPostsIds),
      (entities, ids) => ids.map(id => entities.get(id))
    )
  );

select - converts queries to selectors.

Now we can use this node for setting and getting data:

const posts = [
  { id: 1, text: "hello", commentsIds: [1] },
  { id: 2, text: "world", commentsIds: [2, 3] },
];

const state1 = defaultState;
const state2 = posts.reducer(state1, fetchPostsRequest());
posts.select(getArePostsFetching)(state2); // true
const state3 = posts.reducer(state2, fetchPostsSuccess(posts);
posts.select(getPostsIds)(state3); // [1, 2]
posts.select(getPosts)(state3); // posts

Let's create another node for comments in another style:

import { createAction, createQuery, createNode, combineNodes } from "coredux";

export const fetchCommentsRequest = createAction();
export const fetchCommentsSuccess = createAction();

export const getAreCommentsFetching = createQuery();
export const getCommentsIds = createQuery();
export const getCommentsEntities = createQuery();

const areFetching = createNode(false)
  .setter(fetchCommentsRequest, true)
  .setter(fetchCommentsSuccess, false)
  .getter(getAreCommentsFetching);

const ids = createNode([])
  .setter(fetchCommentsSuccess, (ids, comments) =>
    comments.map(comment => comment.id)
  )
  .getter(getCommentsIds);

const entities = createNode(new Map())
  .setter(
    fetchCommentsSuccess,
    (entities, comments) =>
      new Map(comments.map(comment => [comment.id, comment]))
  )
  .getter(getCommentsEntities);

export const comments = combineNodes({
  isFetching,
  ids,
  entities,
});

combineNodes creates a new node for an object with all actions and queries of the subnodes, just like a combineReducers for actions.

And now we can combine these two nodes into one root node and define a complex getter:

export const getPostsWithComments = createQuery();

export const root = combineNodes({
  comments,
  posts,
}).getter(getPostsWithComments, select =>
  createSelector(
    select(getPosts),
    select(getCommentsEntities),
    (posts, commentsById) =>
      posts.map(post => ({
        ...post,
        comments: post.commentsIds.map(id => commentsById.get(id)),
      }))
  )
);

Test:

const commentA = { id: 1, text: "comment A" };
const commentB = { id: 2, text: "comment B" };
const commentC = { id: 3, text: "comment C" };

const comments = [commentA, commentB, commentC];

const postA = { id: 1, text: "post", commentsIds: [commentA.id, commentB.id] };
const postB = { id: 2, text: "post 2", commentsIds: [commentC.id] };

const posts = [postA, postB];

const { dispatch, getState } = createStore(root.reducer);

expect(root.select(getAreCommentsFetching)(getState())).toBe(false);
dispatch(fetchCommentsRequest());
expect(root.select(getAreCommentsFetching)(getState())).toBe(true);
dispatch(fetchCommentsSuccess(comments));
expect(root.select(getAreCommentsFetching)(getState())).toBe(false);

expect(root.select(getCommentsIds)(getState())).toEqual([
  commentA.id,
  commentB.id,
  commentC.id,
]);

const commentsValues = root
  .select(getCommentsEntities)(getState())
  .values();

expect(commentsValues).toContain(commentA);

expect(root.select(getArePostsFetching)(getState())).toBe(false);
dispatch(fetchPostsRequest());
expect(root.select(getArePostsFetching)(getState())).toBe(true);
dispatch(fetchPostsSuccess(posts));
expect(root.select(getArePostsFetching)(getState())).toBe(false);

expect(root.select(getPosts)(getState())).toContain(postA);
expect(root.select(getPostsWithComments)(getState())).toContainEqual({
  ...postA,
  comments: [commentA, commentB],
});
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].