All Projects → dai-shi → Reactive React Redux

dai-shi / Reactive React Redux

Licence: mit
React Redux binding with React Hooks and Proxy

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to Reactive React Redux

Store
A beautifully-simple framework-agnostic modern state management library.
Stars: ✭ 204 (-57.5%)
Mutual labels:  reactive, proxy
Toxy
Hackable HTTP proxy for resiliency testing and simulated network conditions
Stars: ✭ 2,698 (+462.08%)
Mutual labels:  reactive, proxy
Radioactive State
☢ Make Your React App Truly Reactive!
Stars: ✭ 273 (-43.12%)
Mutual labels:  reactive, proxy
Fanqiang
翻墙-科学上网
Stars: ✭ 23,428 (+4780.83%)
Mutual labels:  proxy
Disqusjs
💬 Render Disqus comments in Mainland China using Disqus API
Stars: ✭ 455 (-5.21%)
Mutual labels:  proxy
Realm Js
Realm is a mobile database: an alternative to SQLite & key-value stores
Stars: ✭ 4,648 (+868.33%)
Mutual labels:  reactive
Nginx Le
Nginx with automatic let's encrypt (docker image)
Stars: ✭ 475 (-1.04%)
Mutual labels:  proxy
Shiny
Easy interactive web applications with R
Stars: ✭ 4,507 (+838.96%)
Mutual labels:  reactive
Xx Mini
👻 XX-Net 精简版
Stars: ✭ 472 (-1.67%)
Mutual labels:  proxy
Smocker
Smocker is a simple and efficient HTTP mock server and proxy.
Stars: ✭ 465 (-3.12%)
Mutual labels:  proxy
Resolve
Full stack CQRS, DDD, Event Sourcing framework for Node.js
Stars: ✭ 460 (-4.17%)
Mutual labels:  reactive
Proxymanager
🎩✨🌈 OOP Proxy wrappers/utilities - generates and manages proxies of your objects
Stars: ✭ 4,556 (+849.17%)
Mutual labels:  proxy
Fetch Some Proxies
Simple Python script for fetching "some" (usable) proxies
Stars: ✭ 470 (-2.08%)
Mutual labels:  proxy
Ergo
The management of multiple apps running over different ports made easy
Stars: ✭ 452 (-5.83%)
Mutual labels:  proxy
Proxyman
Configuring proxy settings made easy.
Stars: ✭ 472 (-1.67%)
Mutual labels:  proxy
Rxswift
Reactive Programming in Swift
Stars: ✭ 21,163 (+4308.96%)
Mutual labels:  reactive
Ngx Errors
A declarative validation errors module for reactive forms.
Stars: ✭ 472 (-1.67%)
Mutual labels:  reactive
Doux
🦄 Immutable reactivity system, made with ES6 Proxy.
Stars: ✭ 460 (-4.17%)
Mutual labels:  proxy
Material Motion Android
Reactive motion for Android. Deprecated; please use the Motion library in Material Components for Android instead: https://material.io/develop/android/theming/motion/.
Stars: ✭ 459 (-4.37%)
Mutual labels:  reactive
Reactive Interaction Gateway
Create low-latency, interactive user experiences for stateless microservices.
Stars: ✭ 465 (-3.12%)
Mutual labels:  reactive

There are several projects related to this repo. Here's the index of those.

  • reactive-react-redux v5-alpha (this repo): This has an experimental react-redux binding with useMutableSource. It provides useTrackedState, which tracks the usage of state in render, and it's originally proposed in this repo.
  • react-tracked: This project is to provide useTrackedState with React Context. v1.6 provides createTrackedSelector that will create useTrackedState from useSelector.
  • react-redux #1503: A pull request to add useTrackedState to the official react-redux library.
  • proxy-memoize: This is another project which is not tied to React, but combined with useSelector, we get a similar functionality like useTrackedState.

reactive-react-redux

CI npm size discord

React Redux binding with React Hooks and Proxy

If you are looking for a non-Redux library, please visit react-tracked which has the same hooks API.

Introduction

This is a library to bind React and Redux with Hooks API. It has mostly the same API as the official react-redux Hooks API, so it can be used as a drop-in replacement if you are using only basic functionality.

There are two major features in this library that are not in the official react-redux.

1. useTrackedState hook

This library provides another hook useTrackedState which is a simpler API than already simple useSelector. It returns an entire state, but the library takes care of optimization of re-renders. Most likely, useTrackedState performs better than useSelector without perfectly tuned selectors.

Technically, useTrackedState has no stale props issue.

2. useMutableSource without Context

react-redux v7 has APIs around Context. This library is implemented with useMutableSource, and it patches the Redux store. APIs are provided without Context. It's up to developers to use Context based on them. Check out ./examples/11_todolist/src/context.ts.

There's another difference from react-redux v7. This library directly use useMutableSource, and requires useCallback for the selector in useSelector. equalityFn is not supported.

How tracking works

A hook useTrackedState returns an entire Redux state object with Proxy, and it keeps track of which properties of the object are used in render. When the state is updated, this hook checks whether used properties are changed. Only if it detects changes in the state, it triggers a component to re-render.

Install

npm install reactive-react-redux

Usage (useTrackedState)

import React from 'react';
import { createStore } from 'redux';
import {
  patchStore,
  useTrackedState,
} from 'reactive-react-redux';

const initialState = {
  count: 0,
  text: 'hello',
};

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case 'increment': return { ...state, count: state.count + 1 };
    case 'decrement': return { ...state, count: state.count - 1 };
    case 'setText': return { ...state, text: action.text };
    default: return state;
  }
};

const store = patchStore(createStore(reducer));

const Counter = () => {
  const state = useTrackedState(store);
  const { dispatch } = store;
  return (
    <div>
      {Math.random()}
      <div>
        <span>Count: {state.count}</span>
        <button type="button" onClick={() => dispatch({ type: 'increment' })}>+1</button>
        <button type="button" onClick={() => dispatch({ type: 'decrement' })}>-1</button>
      </div>
    </div>
  );
};

const TextBox = () => {
  const state = useTrackedState(store);
  const { dispatch } = store;
  return (
    <div>
      {Math.random()}
      <div>
        <span>Text: {state.text}</span>
        <input value={state.text} onChange={event => dispatch({ type: 'setText', text: event.target.value })} />
      </div>
    </div>
  );
};

const App = () => (
  <>
    <h1>Counter</h1>
    <Counter />
    <Counter />
    <h1>TextBox</h1>
    <TextBox />
    <TextBox />
  </>
);

API

patchStore

patch Redux store for React

Parameters

  • store Store<State, Action>

Examples

import { createStore } from 'redux';
import { patchStore } from 'reactive-react-redux';

const reducer = ...;
const store = patchStore(createStore(reducer));

useTrackedState

useTrackedState hook

It return the Redux state wrapped by Proxy, and the state prperty access is tracked. It will only re-render if accessed properties are changed.

Parameters

  • patchedStore PatchedStore<State, Action>
  • opts Opts (optional, default {})

Examples

import { useTrackedState } from 'reactive-react-redux';

const Component = () => {
  const state = useTrackedState(store);
  ...
};

useSelector

useSelector hook

selector has to be stable. Either define it outside render or use useCallback if selector uses props.

Parameters

  • patchedStore PatchedStore<State, Action>
  • selector function (state: State): Selected

Examples

import { useCallback } from 'react';
import { useSelector } from 'reactive-react-redux';

const Component = ({ count }) => {
  const isBigger = useSelector(store, useCallack(state => state.count > count, [count]));
  ...
};

memo

memo

Using React.memo with tracked state is not compatible, because React.memo stops state access, thus no tracking occurs. This is a special memo to be used instead of React.memo with tracking support.

Parameters

  • Component any
  • areEqual any?

Examples

import { memo } from 'reactive-react-redux';

const ChildComponent = memo(({ obj1, obj2 }) => {
  // ...
});

Recipes

Context

You can create Context based APIs like react-redux v7.

import { createContext, createElement, useContext } from 'react';
import {
  PatchedStore,
  useSelector as useSelectorOrig,
  useTrackedState as useTrackedStateOrig,
} from 'reactive-react-redux';

export type State = ...;

export type Action = ...;

const Context = createContext(new Proxy({}, {
  get() { throw new Error('use Provider'); },
}) as PatchedStore<State, Action>);

export const Provider: React.FC<{ store: PatchedStore<State, Action> }> = ({
  store,
  children,
}) => createElement(Context.Provider, { value: store }, children);

export const useDispatch = () => useContext(Context).dispatch;

export const useSelector = <Selected>(
  selector: (state: State) => Selected,
) => useSelectorOrig(useContext(Context), selector);

export const useTrackedState = () => useTrackedStateOrig(useContext(Context));

useTrackedSelector

You can create a selector hook with tracking support.

import { useTrackedState } from 'reactive-react-redux';

export const useTrackedSelector = (patchedStore, selector) => selector(useTrackedState(patchedStore));

Please refer this issue for more information.

useTracked

You can combine useTrackedState and useDispatch to make a hook that returns a tuple like useReducer.

import { useTrackedState, useDispatch } from 'reactive-react-redux';

export const useTracked = (patchedStore) => {
  const state = useTrackedState(patchedStore);
  const dispatch = useDispatch(patchedStore);
  return useMemo(() => [state, dispatch], [state, dispatch]);
};

Caveats

Proxy and state usage tracking may not work 100% as expected. There are some limitations and workarounds.

Proxied states are referentially equal only in per-hook basis

const state1 = useTrackedState(patchedStore);
const state2 = useTrackedState(patchedStore);
// state1 and state2 is not referentially equal
// even if the underlying redux state is referentially equal.

You should use useTrackedState only once in a component.

An object referential change doesn't trigger re-render if an property of the object is accessed in previous render

const state = useTrackedState(patchedStore);
const { foo } = state;
return <Child key={foo.id} foo={foo} />;

const Child = React.memo(({ foo }) => {
  // ...
};
// if foo doesn't change, Child won't render, so foo.id is only marked as used.
// it won't trigger Child to re-render even if foo is changed.

You need to use a special memo provided by this library.

import { memo } from 'reactive-react-redux';

const Child = memo(({ foo }) => {
  // ...
};

Proxied state might behave unexpectedly outside render

Proxies are basically transparent, and it should behave like normal objects. However, there can be edge cases where it behaves unexpectedly. For example, if you console.log a proxied value, it will display a proxy wrapping an object. Notice, it will be kept tracking outside render, so any prorerty access will mark as used to trigger re-render on updates.

useTrackedState will unwrap a Proxy before wrapping with a new Proxy, hence, it will work fine in usual use cases. There's only one known pitfall: If you wrap proxied state with your own Proxy outside the control of useTrackedState, it might lead memory leaks, because useTrackedState wouldn't know how to unwrap your own Proxy.

To work around such edge cases, the first option is to use primitive values.

const state = useTrackedState(patchedStore);
const dispatch = useUpdate(patchedStore);
dispatch({ type: 'FOO', value: state.fooObj }); // Instead of using objects,
dispatch({ type: 'FOO', value: state.fooStr }); // Use primitives.

The second option is to use getUntrackedObject.

import { getUntrackedObject } from 'react-tracked';
dispatch({ type: 'FOO', value: getUntrackedObject(state.fooObj) });

You could implement a special dispatch function to do this automatically.

Examples

The examples folder contains working examples. You can run one of them with

PORT=8080 npm run examples:01_minimal

and open http://localhost:8080 in your web browser.

You can also try them in codesandbox.io: 01 02 03 04 05 06 07 08 09 11 12 13

Benchmarks

benchmark result

See #32 for details.

Blogs

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