All Projects → theKashey → restate

theKashey / restate

Licence: other
A redux fractal state library 🕷

Programming Languages

javascript
184084 projects - #8 most used programming language
HTML
75241 projects

Projects that are alternatives of or similar to restate

vue-reactive-store
A VueX alternative : declarative + reactive + centralized way to structure your data store. Inspired by VueX and Vue.js . Compatible with vue-devtools.
Stars: ✭ 27 (-50.91%)
Mutual labels:  state-management, state
useSharedState
useSharedState is a simple hook that can be used to share state between multiple React components.
Stars: ✭ 0 (-100%)
Mutual labels:  state-management, state
okito
Your best flutter coding friend. All in one; state management, navigation management(with dynamic routing), local storage, localization, dependency injection, cool extensions with best usages and with the support of best utilities!
Stars: ✭ 37 (-32.73%)
Mutual labels:  state-management, state
rex-state
Convert hooks into shared states between React components
Stars: ✭ 32 (-41.82%)
Mutual labels:  state-management, state
react-cool-form
😎 📋 React hooks for forms state and validation, less code more performant.
Stars: ✭ 246 (+347.27%)
Mutual labels:  state-management, state
temperjs
State management for React, made simple.
Stars: ✭ 15 (-72.73%)
Mutual labels:  state-management, state
tstate-machine
TypeScript implementation of State Manager(like StateMachine)
Stars: ✭ 20 (-63.64%)
Mutual labels:  state-management, state
storken
🦩 Storken is a React State Manager. Simple as `useState`.
Stars: ✭ 22 (-60%)
Mutual labels:  state-management, state
teaful
🍵 Tiny, easy and powerful React state management
Stars: ✭ 638 (+1060%)
Mutual labels:  state-management, state
mutable
State containers with dirty checking and more
Stars: ✭ 32 (-41.82%)
Mutual labels:  state-management, state
snap-state
State management in a snap 👌
Stars: ✭ 23 (-58.18%)
Mutual labels:  state-management, state
concave
🧐 Lens-like state management (for React).
Stars: ✭ 13 (-76.36%)
Mutual labels:  state-management, state
XUI
XUI makes modular, testable architectures for SwiftUI apps a breeze!
Stars: ✭ 100 (+81.82%)
Mutual labels:  state-management, state
ReduxSimple
Simple Stupid Redux Store using Reactive Extensions
Stars: ✭ 119 (+116.36%)
Mutual labels:  state-management, state
atomic-state
Atomic State is a state management library for React
Stars: ✭ 15 (-72.73%)
Mutual labels:  state-management, state
xstate
State machines and statecharts for the modern web.
Stars: ✭ 21,286 (+38601.82%)
Mutual labels:  state-management, state
vuse-rx
Vue 3 + rxjs = ❤
Stars: ✭ 52 (-5.45%)
Mutual labels:  state-management, state
react-state
React-State - Superpowers for managing local and reusable state in React
Stars: ✭ 14 (-74.55%)
Mutual labels:  state-management, state
agile
🌌 Global State and Logic Library for JavaScript/Typescript applications
Stars: ✭ 90 (+63.64%)
Mutual labels:  state-management, state
vue
Vue integration for Nano Stores, a tiny state manager with many atomic tree-shakable stores
Stars: ✭ 25 (-54.55%)
Mutual labels:  state-management, state

RESTATE


restate

A fractal state management library.

Build status Greenkeeper

Restate connects to the Redux State AND to the local component state, producing a new state, you can use with, or without Redux.

Restate, re-store, redux-focus, redux-lenses, re-dux, redux-tree... Oh, it was not easy to name The base layer for a redux composition.

The goal of Restate is to provide hierarchical, decoupled, isolated synthetic stores, and make your application faster and simpler. Restate just creates a new branch, from a original store, allowing you to control it, and use composition on redux-level. And it does not need Redux for it. You can use Restate without Redux, connecting your components to the syntetic derived state.

import reduxRestate from 'redux-restate'; // to low-level redux manupulations
import reactReduxRestate from 'react-redux-restate'; // to work with multiple stores
import reactReduxFocus from 'react-redux-focus'; // to focus a lens on a single store
import reactReduxLoop from 'react-redux-loop'; // to call React from Redux
import reactSemaphore from 'react-redux-semaphore'; // to create *suspense* 

The problem

As long React spreads component architecture and highly composable patterns, the major part of it - Redux - do not follow this way.

In the world of redux Store is a singlentone. You can create a connection to that store, and fetch the data you need. But how?

Redux is the same for any connection, created from any point of Render Tree.

You have to use React props, to pass the ids you need deeply into react Tree to use them to get the data out of the store.

Redux is not composable. Redux is not component friendly.

Medium articles about: Restate , Fractal state .

The borders

Redux's connect method produces PureComponent. No update from the top will pass PureComponent. All updates will start from PureComponent.

Connect is the end for all updates, and the beginning.

Then you will change the store, all connected component will be triggered to update. They will mapStateToProps and maybe do nothing more, in case the result object is shallowEqual to the older one.

Unless you will specify areStatesEqual for each connect, which is not quite possible, to say the truth.

The idea

This is rework of ideas from Yandex Map API Option Manager. Option Manager was build to handle 2 cases:

  • GeoJSON. Where a lot of similar objects can be nested inside each other (features and collections).
  • The single configuration file.

It is easier to explain via example:

  1. You are setTimeone. You need your durition, and you are reading from store values named -> duration
  2. Actually you are an animation. You prepend your request by your name -> animationDuration
  3. Animation is internal component of Zoom Control. And it add to all data-requests passed from nested components it's name -> zoomControlAnimationDuration. As result - final component could use simple names - color, duration, value,- but store could contains much more complex names.

OptionManager (OptionMapper to be clear) work as lenses scoping the store.

The solution

Redux-restate get:

  • one or more stores as input,
  • combine function to create a new store
  • dispatch function, to handle dispatch

And produces the new store.

Thus makes redux composable, and enabled the component way.

Restate is the end for any update, and the beginning. But not for all. Only the ones you need.

The implementation

  • redux-restate for redux level.
  • react-redux-retate for react multy-state case.
  • react-redux-focus for single store case.
  • recct-redux-semaphore to control update propagation.

Usage

redux-restate

import restate from 'redux-restate';
const newStore = restate({ store: baseStore }, composeState, routeDispatch, options);
  • composeState(states):NewState get one one more states as input, produce the output
  • routeDispatch(dispatchers, event) get one one more dispatch as input, then call the disired one with even, also provided.

react-redux-restate

import reactReduxRestate from 'react-redux-restate';
const RestatedComponent = reactReduxRestate(
  { otherStore: otherStore /*store or store key*/ }, // default store will be injected automagically
  (stores, props) => composeState,
  (dispatchers, event, props) => routeDispatch,
  options,
)(WrappedComponent);

The same as redux-restate, but in form of React HOC. The default store, accessible with storeKey, is available as .default for next functions.

  • composeState(states, event, props) get one one more states as input, plus props, produce the output

  • routeDispatch(dispatchers, event, props) dispatch as input, plus props, then call the disired one with even, also provided.

Note: if composeState will return undefined the state will not change.

reproviding a state

Sometimes it is worth to keep the old store. Just save it using a different name.

import { reprovide } from 'react-redux-restate';
const Reprovider = reprovide('new-store-name', 'old-store-name');
const DefaultReprovider = reprovide('new-store-name'); // old will be `store`

Fork/Unfork

There is a "standard way" to reprovide a state

import { ForkReduxStore, UnforkReduxStore } from 'react-redux-restate';

const App = () => (
  <Provider state={state}>
    <ForkReduxState>
      // state "forked" into "global" state
      <FocusOrRestateTheStore>
        // state is "altered" here // you can always refer to "global" as a state key
        <UnforkReduxState>// state is reverted to the original</UnforkReduxState>
      </FocusOrRestateTheStore>
    </ForkReduxState>
  </Provider>
);

react-redux-focus

import reactReduxFocus from 'react-redux-focus';
const FocusedComponent = reactReduxFocus(
  (state, props) => state.todos[props.id],
  (dispatch, event, props) => dispatch({ ...event, id: props.id }),
)(WrappedComponent);

Or you can use Component approach

import { ReduxFocus } from 'react-redux-focus';

<ReduxFocus
  focus={(state, props) => state.todos[props.id]}
  onDispatch={(dispatch, event, props) => dispatch({ ...event, id: props.id })}
>
  <WrappedComponent />
</ReduxFocus>;

The same as react-redux-restate, but for a single store.

  • composeState(state, props): newState - focus will work only with one state
  • routeDispatch(dispatch, props)

react-redux-semaphore

HOC approach.

import reduxSemaphore from 'react-redux-semaphore';

const WillUseOldStateUnlessConditionAreMet = reduxSemaphore((state, props) => isValid(store.importantData))(
  TargetComponent,
);

Component approach

import { ReduxSemaphore } from 'react-redux-semaphore';
<ReduxSemaphore condition={(state, props) => isValid(store.importantData)}>
  <TargetComponent />
</ReduxSemaphore>;

Optimization

Restate will perform shallowEqual compare for the old and the new states. Please use reselect or another memoization library to keep branches unchanged. Otherwise - specify areStatesEqual option.

Multiple store case

It is absolutely common, that some parts of application can be absolutely independent. But, in the same time, they are united. The simplest examples

  • routing
  • notifications
  • user data
  • analytics

Using the composeState you can control how your data passes down.
Using the routeDispatch you can control how dispatches bubbles up.

  • You can extend dispatched event by some data.
  • You can choose which dispatch to call - the original one, from a nearest (default) store, or the parent one (the Application level)

Examples

Deepdive with Restart instantly

Connect all restate to the original store, just for original store lensing and optimization.

 import {createProvider} from 'react-redux'
 import reactReduxRestate from 'react-redux-restate';

 const Provider = createProvider('non-default-name');
 const Remap = reactReduxRestate(...., {
   storeKey:'non-default-name'
   // restoreKey: 'store' // will defaults to store
 })

Isolate middle of application

Provide store, reprovide store, restore original store...

import reactReduxRestate, { reprovider } from 'react-redux-restate';
const Reprovider = reprovider('realStore');
const RestoreStore = reprovider('store', 'realStore');

const RestateStore = reactReduxRestate(
  {}, // no extra stores
  states => focusOnSomeBranchIn(states.default),
  (dispatch, event, props) => dispatch.default({ ...event, id: props.id }),
);

const RestateWithOriginalStore = reactReduxRestate(
  { realStore: 'realStore' }, // connect the real store back
  states => mixStates(states.default, states.realStore),
  (dispatch, event, props) => dispatch.realStore({ ...event, id: props.id }),
);

const RestateForOriginalStore = reactReduxRestate(
  {}, // no extra stores
  states => focusOnSomeBranchIn(states.default),
  (dispatch, event, props) => dispatch.default({ ...event, id: props.id }),
  {
    storeKey: 'realStore', // use `realStore` as default
  },
);

const Application = () => (
  // put redux store inside
  <Provider store={myReduxStore}>
    // re-export current store as `realStore
    <Reprovider>
      // switch to syntetic redux
      <RestateStore>
        <SomePartOfAnApplication>
          // restore original store
          <RestoreStore>
            <WorkWithOriginalStore />
          </RestoreStore>
          // or use restate with 2 stores connected
          <RestateWithOriginalStore />
          // or connect restate to the real store
          <RestateForOriginalStore />
        </SomePartOfAnApplication>
      </RestateStore>
    </Reprovider>
  </Provider>
);

Also Check out example-todo in packages.

Before all the things - map Todos

const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter),
});

const mapDispatchToProps = {
  onTodoClick: toggleTodo,
};

export const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);

Next - render Todo...

The original Redux-todo-list

const TodoListRedux = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo => (
      // Here redux "ends". You have to map onClick in magic way
      <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
    ))}
  </ul>
);

The remap variant

// direct mapping. Here is nothing more that Todo need
const mapStateToProps = state => state;
const mapDispatchToProps = {
  onClick: toggleTodo,
};
const ConnectedTodo = connect(mapStateToProps, mapDispatchToProps)(Todo);

// Focusing on Todo-only
const TodoMapped = reduxFocus(
  (state, props) => state.todos[props.id],
  // Todo should just dispatch an event. All logic is here.
  (dispatch, event, props) => dispatch({ ...event, id: props.id }),
)(ConnectedTodo);

const TodoList = ({ todos, onTodoClick }) => <ul>{todos.map(todo => <TodoMapped key={todo.id} id={todo.id} />)}</ul>;

The variant with Remap is twice longer, but it will run faster out of the box. No Todo will be re-rendered if any other gonna to change Todo will become isolated from rest of application.

[Animation]

As result you can re-connect any existing "connected" component.

Licence

MIT

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