8000 GitHub - Anson99/reactive-react-redux: React Redux binding with React Hooks and Proxy
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Anson99/reactive-react-redux

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

reactive-react-redux

Build Status npm version bundle size

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. state-based object for context value

react-redux v7 uses store-based object for context value, while react-redux v6 used to use state-based object. Using state-based object naively has unable-to-bail-out issue, but this library uses state-based object with undocumented function calculateChangedBits to stop propagation of re-renders. See #29 for details.

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 {
  Provider,
  useDispatch,
  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 = createStore(reducer);

const Counter = () => {
  const state = useTrackedState();
  const dispatch = useDispatch();
  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();
  const dispatch = useDispatch();
  return (
    <div>
      {Math.random()}
      <div>
        <span>Text: {state.text}</span<
8000
span class="pl-c1">>
        <input value={state.text} onChange={event => dispatch({ type: 'setText', text: event.target.value })} />
      </div>
    </div>
  );
};

const App = () => (
  <Provider store={store}>
    <h1>Counter</h1>
    <Counter />
    <Counter />
    <h1>TextBox</h1>
    <TextBox />
    <TextBox />
  </Provider>
);

API

Provider

const store = createStore(...);
const App = () => (
  <Provider store={store}>
    ...
  </Provider>
);

useDispatch

const Component = () => {
  const dispatch = useDispatch();
  // ...
};

useSelector

const Component = () => {
  const selected = useSelector(selector);
  // ...
};

useTrackedState

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

Advanced API

Experimental useTrackedSelectors
import React, { useCallback } from 'react';
import { useTrackedSelectors } from 'reactive-react-redux';

const globalSelectors = {
  firstName: state => state.person.first,
  lastName: state => state.person.last,
};

const Person = () => {
  const { firstName } = useTrackedSelectors(globalSelectors);
  return <div>{firstName}</div>;
  // this component will only render when `state.person.first` is changed.
};

const Person2 = ({ threshold }) => {
  const { firstName, isYoung } = useTrackedSelectors({
    ...globalSelectors,
    isYoung: useCallback(state => (state.person.age < threshold), [threshold]),
  });
  return <div>{firstName}{isYoung && '(young)'}</div>;
};

Examples

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

PORT=8080 npm run examples: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 10 11

Benchmarks

benchmark result

See blog entry and #3 for details.

Limitations in tracking

By relying on Proxy, there are some false negatives (failure to trigger re-renders) and some false positives (extra re-renders) in edge cases.

Proxied states are referentially equal only in per-hook basis

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

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

const state = useTrackedState();
const foo = useMemo(() => state.foo, [state]);
const bar = state.bar;
// if state.foo is not evaluated in render,
// it won't trigger re-render if only state.foo is changed.

Proxied state shouldn't be used outside of render

const state = useTrackedState();
const dispatch = useDispatch();
dispatch({ type: 'FOO', value: state.foo }); // This may lead unexpected behavior if state.foo is an object
dispatch({ type: 'FOO', value: state.fooStr }); // This is OK if state.fooStr is a string

Blogs

About

React Redux binding with React Hooks and Proxy

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 100.0%
0