reduxjs / react-redux

Official React bindings for Redux

Home Page:https://react-redux.js.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Provide React Hooks

timdorr opened this issue · comments

Today's the big day for React Hooks!

Assuming the API gets released, we should provide some hooks. Something like useRedux would be cool!

import * as actions from './actions'

function Counter() {
  const [count, {increment, decrement}] = useRedux(state => state.count, actions);
  
  return (
    <>
      Count: {count}
      <button onClick={() => increment()}>+</button>
      <button onClick={() => decrement()}>-</button>
    </>
  );
}

Note: Yes, I'm aware of useReducer. That is for a Redux-style reducer, but for a local component's state only. What we're building would be for the store's global state, using useContext under the hood.

Great issue. I was curious how this hook would affect Redux. Good to see the team is already thinking about how it can be used WITH Redux. Subscribing.

BTW, this wasn't coordinated at all, but React is telling us to do this 😄

In the future, new versions of these libraries might also export custom Hooks such as useRedux()

https://reactjs.org/docs/hooks-faq.html#what-do-hooks-mean-for-popular-apis-like-redux-connect-and-react-router

I'm already fiddling around with rewriting my #995 WIP PR for React-Redux v6 to use hooks internally instead of class components, and it looks way simpler so far. I hope to push up something for discussion within the next couple days.

As for actually exposing hooks... yeah, some kind of useRedux() hook might be possible as well, but I haven't gotten my brain that far yet :)

edit

Huh. Actually, now that I look at Tim's example... yeah, that looks totally doable. I'd have to play around with things some more to figure out specific implementation, but I don't see any reason why we can't do that.

looks legit. Isn't it a replacement for connect?

@sag1v : potentially, but only within function components (as is the case for all hooks).

@markerikson Yeah of course.
I'm a bit confused though, with this line:
const [state, {increment, decrement}] = useRedux(state => state.count, actions);
We are destructuring state.count into a state variable.

Shouldn't it be:
const [count, {increment, decrement}] = useRedux(state => state.count, actions);

Or:
const [state, {increment, decrement}] = useRedux(state => state., actions);

Yeah, probably. Give Tim a break - this is new to all of us :)

Aw sorry didn't mean to offend, I just thought i was missing something. 😔

No worries :) Just pointing out that it was simply a typo.

Fixed!

Would this be able to fix long-standing issues (/weaknesses) from the current wrapping-implementation, like those shown in #210 ?

Hey all, I experimented with a custom hook for a Redux store.

It's based on my library easy-peasy which abstracts Redux but it returns a standard redux store, so this solution would work for Redux too. It's a naive implementation but just wanted to illustrate to everyone the possibilities.

import { useState, useEffect, useContext } from 'react'
import EasyPeasyContext from './easy-peasy-context'

export function useStore(mapState) {
  const store = useContext(EasyPeasyContext)
  const [state, setState] = useState(mapState(store.getState()))
  useEffect(() => 
    store.subscribe(() => {
      const newState = mapState(store.getState())
      if (state !== newState) {
        setState(newState)
      }
    })
  )
  return state
}

export function useAction(mapActions) {
  const store = useContext(EasyPeasyContext)
  return mapActions(store.dispatch)
}
import React from 'react'
import { useStore, useAction } from './easy-peasy-hooks'

export default function Todos() {
  const todos = useStore(state => state.todos.items)
  const toggle = useAction(dispatch => dispatch.todos.toggle)
  return (
    <div>
      <h1>Todos</h1>
      {todos.map(todo => (
        <div key={todo.id} onClick={() => toggle(todo.id)}>
          {todo.text} {todo.done ? '✅' : ''}
        </div>
      ))}
    </div>
  )
}

See it in action here: https://codesandbox.io/s/woyn8xqk15

I could see useStore and useAction as piecemeal alternatives to the full-flavor useRedux hook.

A naive implementation:

const useSelector = selector => {
  const { getState } = useContext(ReduxContent)
  const [result, setResult] = useState(selector(getState()))

  useEffect(
    () =>
      store.subscribe(() => {
        const nextResult = selector(getState())
        if (shallowEqual(nextResult, result)) return
        setResult(nextResult)
      }),
    []
  )

  return result
}

const useActionCreator = actionCreator => {
  const { dispatch } = useContext(ReduxContent)
  return (...args) => dispatch(actionCreator(...args))
}

Usage:

const count = useSelector(state => state.count)
const increment = useActionCreator(increment)

I was thinking about this yesterday, after working on rewriting my #995 PR to use hooks internally.

There's an issue with how a hook like this would be written using our v6 approach. In v5, we put the store into legacy context, and the connect components subscribe directly. In v6, we put the store state into createContext, and the connect components read the store state object from context.

When we call useContext(SomeContext), React marks that component as needing to re-render whenever the context updates, exactly the same as if we'd done <SomeContext.Consumer>{(value) => { }}</SomeContext.Consumer>. That's fine with connect, because we want the wrapper component to run its update process, check to see if the extracted values from mapState have changed, and only re-render the wrapped child if those are different.

However, if I were to do something like const updatedData = useRedux(mapState, mapDispatch), then our function component would re-render if any part of the Redux state had changed, and there's currently no way to look at updatedData and bail out of rendering this function component if it's the same as last time. @sophiebits and @gaearon confirmed the issue here: https://twitter.com/acemarke/status/1055694323847651335 .

Dan has filed React #14110: Provide more ways to bail out inside hooks to cover this. So, the issue is on their radar, and they'd like to have a way for function components to bail out of re-rendering before 16.7 goes final.

@Matsemann : using hooks won't fix the "dispatch in lifecycle methods" issue by itself, exactly. The switch to using createContext in v6 is what would really matter.

@markerikson I've updated my comment, did you see useSelector?

store.subscribe will fire on every received action, but it'll bail out if that state slice didn't change.

@hnordt : I'm specifically talking about a useRedux() hook that would be based on the v6 PRs, where we put the state into context rather than the whole store.

@markerikson I think the "spirit" of hooks is based on small units of work. useRedux would be "too much" in my opinion.

From Hooks docs:

Separating independent state variables also has another benefit. It makes it easy to later extract some related logic into a custom Hook.

https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables

Here an example of what I would like to see for react-redux future implementation. Feel free to play with it or ask questions. Love the feedback!

https://codesandbox.io/s/mm0qq8p43x

Thinking more about this and was asking myself why can't we split apart the state, and dispatch? It would reduce what each hook is trying to do conceptually and be smaller re-usable parts. I organized my previous example and cleaned it up a bit more on a separate fork. Feel free to play around with it https://codesandbox.io/s/1o79n7o46q.

Simplest Example:
image

@JesseChrestler I imagine we'll provide both the individual use* functions for state and actions, but also an all-in-one for those that want something that looks like connect() today.

@timdorr what about the different ways of retrieving state? I provided 3 ways to do it. I think the string variant is good for simplifying the example for new users. Having a single function is good for those already used to the way connect works and can easily port existing code. The object structure is more for when you've have the connect where you already have predefined selectors.

Single Function Example

const state = useReduxState(state => ({
    count: countSelector(state),
    user: userSelector(state)
})

Object Example

const state = useReduxState({
    count: countSelector,
    user: userSelector
})

I think for larger projects having this object notation cleans up a lot of noise around mapping data. I suppose this can be pushed off on the user to implement and they would map their object with this single function. It could look like this.

Sample implementation

const reduxObject = (selectorObject) => (state) => Object.keys(selectorObject).reduce((selected, key) => {
   selected[key] = selectorObject[key](state)
   return selected;
}, {})

Sample use case

const state = useReduxState(reduxObject({
    count: countSelector,
    user: userSelector
}))

what do you think? I prefer to have this logic in the useReduxState, but wondering your thoughts on this.

Why not something like this:

import { useState, useEffect } from 'react'
import store from 'redux/store'
import objectCompare from 'libs/objectCompare'

const emptyFunction = () => ({})

export default function useRedux(mapStateToProps = emptyFunction, mapDispatchToProps = emptyFunction) {
  const stateToProps = mapStateToProps(store.getState())
  const dispatchToProps = mapDispatchToProps(store.dispatch)

  const [state, setState] = useState(stateToProps)

  useEffect(() => store.subscribe(() => {
    console.log(`Running subscribe`)
    
    const newStateToProps = mapStateToProps(store.getState())

    console.log('newStateToProps', newStateToProps)
    console.log('stateToProps', stateToProps)

    if (!objectCompare(newStateToProps, stateToProps)) {
      console.log('setState')

      setState(newStateToProps)
    }
  }))

  return {
    ...state,
    ...dispatchToProps
  }
}
import React from 'react'
import { useRedux } from 'hooks'
import { increaseCounter, nothingCounter } from 'redux/ducks/counter'

const mapStateToProps = ({ counter }) => ({ counter: counter.value })
const mapDispatchToProps =  dispatch => ({
  increase: () => dispatch(increaseCounter()),
  nothing: () => dispatch(nothingCounter())
})

export default function Block1() {
  const {
    counter,
    increase,
    nothing
  } = useRedux(mapStateToProps, mapDispatchToProps)

  return (
    <section>
      <p>{counter}</p>
      <button onClick={increase} children={'Click me'}/>
      <button onClick={nothing} children={'Nothing'}/>
    </section>
  )
}

I tried to implement type safe version with typescript.

// full implementation https://gist.github.com/mizchi/5ab148dd5c3ad6dea3b6c765540f6b73
type RootState = {...};
const store = createStore(...);

// Create React.Context and useXXX helpers with State
const { Provider, useStore, useSelector } = createStoreContext<RootState>();

// State user
function CounterValue() {
  // Update only !isEqual(prevMapped, nextMapped)
  const counter = useSelector(state => ({ value: state.counter.value }));
  return <span>counter.value: {counter.value}</span>;
}

// Dispatch user
function CounterController() {
  const { dispatch } = useStore(); // or just return dispatch to be safe?

  const onClickPlus = useCallback(() => {
    dispatch({ type: INCREMENT });
  }, []);

  const onClickIncrementHidden = useCallback(() => {
    dispatch({ type: INCREMENT_HIDDEN_VALUE }); // will be skipped by CounterView
  }, []);

  return (
    <>
      <button onClick={onClickPlus}>+</button>
      <button onClick={onClickIncrementHidden}>+hidden</button>
    </>
  );
}

function CounterApp() {
  return (
    <div>
      <CounterValue />
      <hr />
      <CounterController />
    </div>
  );
}

ReactDOM.render(
  <Provider store={store}>
    <CounterApp />
  </Provider>,
  document.querySelector(".root")
);

I do not have confidence useSelector is correct name. (useMappedState(fn)?)
IMO, name of redux (or Redux) is just library name, not behavior.

@JesseChrestler alternative version for you string variant:

const items = useStoreValue`todos.items`;

@gokalina yes! though you'd have to split on the periods and iterate the object or have the key that is

{
  'todos.items': []
}

But yeah even cleaner! 👍 I'd really like to simplify the API as much as possible. For fun experiment I wanted how much i could reduce the code using useReducer/useContext to create Redux. It actually turned out to save code because it's already using the same life cycle events react uses. So the need to subscribe vanished. I also no longer need to store state into my component for it to re-render. that's already handled by the context. Much cleaner IMHO.

using Redux:

import {useContext, useState, useEffect} from 'react'
import ReduxContext from './ReduxContext'
import {getReduxValue, createCompare} from './helper'

const useReduxState = mapState => {
  const store = useContext(ReduxContext)
  let initialState = getReduxValue(store, mapState)
  const [state, setState] = useState(initialState)
  const compareState = createCompare(nextState => setState(nextState))
  useEffect(
    () => {
      return store.subscribe(() => {
        compareState(getReduxValue(store, mapState))
      })
    },
    [mapState],
  )
  return state
}
export default useReduxState

using useReducer/useContext:

import {useContext} from 'react'
import ReduxContext from './ReduxContext'
import {getReduxValue} from './helper'
const useReduxState = mapState => {
  const [state] = useContext(ReduxContext)
  return getReduxValue(state, mapState)
}
export default useReduxState

If you're interested you can see my codesandbox here https://codesandbox.io/s/7ywwo0m690

Don't want to be the Debbie Downer at this party, but should we not maybe aim to
get a release out that supports the current, existing, and stable broken things
(like getDerivedStateFromProps) before re-writing/exposing an early-stage
proposal?

@deecewan : we're working on it :) See #1000. I was hoping to get through my cleanup work on that this weekend, but was otherwise occupied. Hopefully in the next couple days.

Also, note that most of the discussion in this thread has been from others besides Tim and myself.

Nice! Been following along for ages, so very keen to see it coming along.

Noted. I guess people should be able to work on what they want to work on, regardless. But I'd imagine that most people can't even use hooks yet, given they only exist on an alpha branch.

Also, noted that Tim did mention that this is a future issue for when hooks land.

Yep, this is just a tracking issue to let others know we know and to make sure we implement them when/if they're available.

@ctrlplusb

I'm just thinking about the design of these hooks a bit - I think finer-grained hooks for actions and state make more sense than a coarse-grained useRedux hook. Tweaking your example slightly, what if it were like this:

import React from 'react'
import { useStoreState, useStoreAction } from 'react-redux'
import { toggle, clear } from './actions'

export default function Todos() {
  // state mapping per value of interest
  const todos = useStoreState(state => state.todos.items)

  // useAction automatically binds action creators to dispatch
  const onToggle = useStoreAction(toggle)
  const onClear = useStoreAction(clear)

  return (
    <div>
      <h1>Todos</h1>
      <button onClick={onClear}>Clear Todos</button>
      {todos.map(todo => (
        <div key={todo.id} onClick={() => toggle(todo.id)}>
          {todo.text} {todo.done ? '✅' : ''}
        </div>
      ))}
    </div>
  )
}

@darthtrevino : WhyNotBoth.jpg :)

More seriously, I can imagine that we might offer all of those - useReduxState() (mapState by itself), useReduxActions() (mapDispatch by itself), and a combined useRedux() that does both (and probably uses the other two internally or something).

Most of this logic is already broken out internally in the memoized selectors that make up connect()'s logic right now anyway, we'd just need to reshuffle the pieces.

However, we're going to need React to support bailing out of context-triggered re-renders first. Dan just filed facebook/react#14110 to cover that.

That's fair, I just think it's worth working through the design alternatives a bit before committing to any

I'm just stoked AF to use hooks everywhere

react useReducer
const [state, dispatch] = useReducer(reducer, initialState);
As @timdorr stated in the first post

Yes, I'm aware of useReducer. That is for a Redux-style reducer, but for a local component's state only.

@markerikson you mentionned links:

  1. https://github.com/philipp-spiess/use-store
    const [substate, dispatch] = useSubstate(state => { return { count: state.count }; });
    possible clash of dispatch concept with useReducer

  2. https://github.com/ianobermiller/redux-react-hook
    const dispatch = useDispatch();
    possible clash of dispatch concept with useReducer

  3. https://github.com/martynaskadisa/react-use-redux
    this one is fine

and @timdorr proposal
const [count, {increment, decrement}] = useRedux(state => state.count, actions);
this one is fine too

I know i am taking it far i do understand the one from react is for local component state and the other one for redux state but if you can provide a solution that use actions name directly to trigger them without the need of dispatch => it would be great i think (@timdorr suggestion is cool)

Here's an idea for something that can be used to bail out on invoking the component, but this will only work with functions and not exotic components:

function withRedux(mapStateToProps, mapDispatchToProps) {
  return function factory(componentFunction) {
    Component.displayName =
      componentFunction.displayName || componentFunction.name
    return React.memo(Component)

    function Component(props) {
      const store = useContext(ReduxContext)
      useEffect(
        () => {
          /* setup subscription */
        },
        [store]
      )
      const storeState = store.getState()
      const mappedState = useMemo(() => mapStateToProps(storeState, props), [
        storeState,
        props
      ])
      const mappedDispatch = useMemo(
        () => mapDispatchToProps(store.dispatch.bind(store), props),
        [store, props]
      )
      return useMemo(
        () =>
          componentFunction({
            ...props,
            ...mappedState,
            ...mappedDispatch
          }),
        [props, mappedState, mappedDispatch]
      )
    }
  }
}

It also won't bail out on reconciling children, only on rendering this specific component.

EDIT: turns out it will; what timing 😆

More EDIT: seems this is basically what #1065 is doing anyway


PS: I wonder if this would be consistent with the rules of hooks:

const mappedState = mapStateToProps.length === 1
  ? useMemo(() => mapStateToProps(storeState), [storeState])
  : useMemo(() => mapStateToProps(storeState, props), [storeState, props])

There is a conditional, but both sides of the conditional only invoke exactly one hook.

This also doesn't deal with the shallow equality checking of mapStateToProps and mapDispatchToProps themselves, which would probably have to go through another level of indirection.

All in all, this still has the same problems as the current connect does, just with less layers in devtools. Hopefully we get a solution from the team soon (maybe something like throw React.noop but that sounds way too powerful).

I might be arriving late to the party but this is how I’ve been doing global state with hooks.

It’s far from a full solution but I wonder if it could be applied for Redux

https://codesandbox.io/embed/n31n1lw6ml

(Grabbing a local components state update function and adding/removing it as a subscription via useEffect)

BTW, to be clear about versioning stuff, here's what I'd like to do:

  • 6.x.0 - Add Hooks as a new API and maintain a React >= 16.4 compat. Add invariant checks around Hooks for React <16.7. No other changes to the library.
  • 7.0.0 - Start using Hooks internally for our existing connect() API. Require React >= 16.7.

So, a minor and then a major. We go all-in, but gradually. Obviously, this depends a lot on how Hooks turn out, both from an API/pattern perspective and a performance/ergonomics perspective.

I'm surprised at how similar my implementation of this is to you guys'. I just stumbled upon this thread when i was going to check if react-redux was doing something with hooks 😅

Basically i've got something like:
const [count, actions, dispatch] = useStore('count', { increment, decrement })

You can choose between passing a string like 'a.b.c.d' vs a function like state => state.a.b.c.d. Second argument to useStore is an object of action creators that will be bound via bindActionCreators the first render. useStore returns an array containing: state, bound actions & the dispatch function (just in case :-)).

I've got two examples here, one very simple example (counter/Counter.js) and one using a reselect selector (users/UserList.js)

The implementation ignores changes to the arguments passed to useStore after initial render, similar to what useState does. This is so that we can memoize the bound action creators and the "expensive" string.split('.') (for now i use get from lodash) computation every render.

See the sandbox: https://codesandbox.io/s/lx6yp1578z

Hello All,

I'm just coming from Angular to React (with typescript) and hoping to help keep the types a bit cleaner(and more extendable) than I've see in some of the more prominent libraries. I've distilled what seems to be the essence of these useRedux examples to the following: https://gist.github.com/baetheus/ee94b4cb172eefeafd1ab8c13abcf77e

Notable Differences

  • useReduxFactory takes a context as an argument. It assumes that the entire store is passed to it for now, but this could be extended to accept a frozen store reference (to avoid unnecessary rerenders) as was referenced in #1063 (comment)
  • useRedux accepts a selector and a comparator function. This keeps users from being stuck with insufficient diffing mechanisms between renders.
  • useRedux returns the selected state and the dispatch method. I'm really not sure why action factories are being passed through the useRedux hook. It doesn't seem like they are changed at all and would already be available to the scope of the function anyway.
  • Typescript types that inherit well from the existing redux and react typings.

The intent with mapDispatchToProps is specifically to hide dispatch from the component, and have it call functions with known names instead. This is helpful when testing the inner component as you don't have to provide a mock dispatch which needs to be aware of the action creator return type implementation details; just mock action creators that only have to care about what the input arguments are.

@Kovensky This makes sense for a higher order component passing functions to a presentational component, but does it makes sense in the context of a stateful component?

In the example I provided it would be the same amount of code to mapDispatchProps outside of the useRedux hook as it would inside. The difference is that by keeping the hook focused on selecting from the store with the dispatch mappings separate there is less for the useRedux hook to do. The coupling is looser, the cohesion is higher.

This is what I am using right now in an app written in TypeScript.

import { useContext } from 'react';
import { Action, Dispatch } from 'redux';
import { ReactReduxContext } from 'react-redux';
import { AppState } from 'modules/types';

export function useRedux<T extends Action>(): [AppState, Dispatch<T>] {
  const { storeState: state, store: {dispatch}} = useContext(ReactReduxContext);
  return [state, dispatch];
}

export default useRedux;

Hi,

I thinks we can handle with to hooks useDispatch and useStateMapper.

We can pass action to useDispatch and then it will return memoize function.

useStateMapper will return mapped key with redux store and return value, in this function we have to create useEffect hook as explained by @ctrlplusb but with shallowEqual comparison.
useStateMapper should accept string or function to map.

Please See this code on code sand box to find out how these too hooks works exactly.

I have made a package named react-redux-peach will provide these two hooks and you can see and talk to me about this solution.

In react-redux-peach package I have used another package named redux-peach, it combine action and its reducers in one object and make better DX to use redux and actions.

Typescript combining a lot of ideas here + concat hook convenience and binding actions to dispatch:

const Context = createContext<Store<any>>(undefined as any);

const useSelector = <S, R, C>(selector: OutputSelector<S, R, C>) => {

  const store = useContext(Context);
  const [ state, setState ] = useState(selector(store.getState()));

  useEffect(() => store.subscribe(() => {
    const newState = selector(store.getState());
    if (state !== newState) setState(newState);
  }));
  return state;
};

const useDispatch = <A extends Index<any>>(actionsMap: A) => {
  const store = useContext(Context);
  return useMemo(() => Object
    .keys(actionsMap)
    .reduce<A>((acc, key) => {
      acc[ key ] = (...args: any[]) => store.dispatch(actionsMap[ key ](...args));
      return acc;
    }, {} as any), [ actionsMap ]);
};

interface ConcatHooks {
  <T1>(f1: () => T1): T1;
  <T1, T2>(f1: () => T2, f2: () => T2): T1 & T2;
  <T1, T2, T3>(f1: () => T1, f2: () => T2, f3: () => T3): T1 & T2 & T3;
}

const concatHooks: ConcatHooks = R.pipe(R.unapply(R.identity), R.chain(R.call), R.mergeAll);



const listings = createSelector(...);
const listing = createSelector(...);

const useListings = () => useSelector(listings);
const useListing = () => useSelector(listing);
const useActions = () => useDispatch({l listingSelected });

const Listings: React.SFC = memo(() => {

  const { listings, listing, listingSelected } = concatHooks(useListings, useListing, useActions);

  return (
    <FlatList<any>
      data={ listings }
      renderItem={ (props) => (
        <ListingItem
          { ...props }
          onPress={ (item) => listingSelected({ key: item.key }) }
        />
      ) }
      ListHeaderComponent={ () => (
        <Text>{ listing ? listing.key : 'None selected' }</Text>
      ) }
    />
  );
});

I created redux-hooker for those who are interested. Have a look at http://github.com/linde12/redux-hooker

Basically useStoreState is a subscribe with shallow eq check, useActions is a memoed bindActionCreators. I dont think it has to be much more complicated than that.

@linde12: nice! How could this be combined with memoized selectors? Thanks a lot for all your Initiatives, folks!

@PatricSachse do you mean like reselect?

In that case you would do it something like this:

const selectUserList = createSelector(...)
const userList = useStoreState(selectUserList)

A variation on some of the above: https://github.com/Satyam/lacorazon/blob/master/src/store/hooks.js

It has both useDispatch and useSelector and both allow to get a whole bunch of dispatch-bound action creators and selected values at once, for example (from https://github.com/Satyam/lacorazon/blob/master/src/User.js#L17-L25):

export default function User({ match }) {
  const id = match.params.id;
  const { history } = useReactRouter();
  const [user, isLoading] = useSelector([selUser, selUsersIsLoading], id);
  const [doGetUser, doSetUser, doDeleteUser] = useDispatch([
    getUser,
    setUser,
    deleteUser
  ]);
// ...

Only one subscription to the store and one useEffect is used per call to useSelector which, I guess, could save some resources on large applications over calling it repeatedly for each selector. You can also do like below, but it takes two separate subscriptions via useEffect:

  const user = useSelector(selUser, id);
  const isLoading = useSelector(selUsersIsLoading);

The selectors can be either functions or in the form of strings: (from https://github.com/Satyam/lacorazon/blob/master/src/store/users/selectors.js)

import { NAME } from './constants';
export const selUsers = NAME + '.data';
export const selUsersIsLoading = NAME + '.isLoading';
export const selUsersGotAll = NAME + '.gotAll';
export const selUser = NAME + '.data.%0';

The selUser selector could be written like this:

export const selUser = (state, id) => state[NAME].data[id];

The NAME constant tells where this particular slice of the store is kept, and also serves as a prefix for all action types, but this is not related to the hook in question (see: https://github.com/Satyam/lacorazon/blob/master/src/store/users/constants.js)

The whole app in that repository is just an exercise to test some ideas, mostly trying out hooks, not for production.

How about https://github.com/facebookincubator/redux-react-hook. Will this be the official Hooks for Redux?

@jvitela : no. Any official hooks will be part of this package, React-Redux.

This is how I would write it (I tested it, it works):

Usage:

import { useActions, useSelector } from 'react-redux';

const MyComp = () => {
  const [setLocale] = useActions([actions.setLocale]); // Wraps actions with dispatch
  const locale = useSelector(state => state.locale.current); // Selects value and re-renders if it changes

  return (...);
}

Implementation:

import React, { useState, useContext, useEffect } from "react";

export const useStore = () => {
  return useContext(ReactReduxContext);
};

export const useActions = (...actions) => {
  const store = useStore();

  return actions.map(action => (...args) => store.dispatch(action(...args)));
};

export const useSelector = (selectorFn) => {
  const store = useStore();
  let [curResult, setCurResult] = useState(() =>
    selectorFn(store.getState())
  );

  useEffect(() => {
    return store.subscribe(() => {
      const nextResult = selectorFn(store.getState());

      if (nextResult !== curResult) {
        curResult = nextResult; // for this function closure
        setCurResult(nextResult); // for the component
      }
    });
  }, []);

  return curResult;
};

@hnordt your implementation will call the selector on each render. Check out my implementation above which uses an initializer function for useState to avoid this

I just wanna say 👍 to the memoized useSelector pattern emerging above, I think selectors as a pattern and naming convention should be promoted over and above arbitrary state mappers. useSelector and useAction feel like the obvious react-redux "hook primitives" to me, and then a way to extend those into pluralised versions useSelectors and useActions as a convenience.

I like @adamkleingit 's approach to this. Simple and performant.

I'll point out that these implementations that subscribe to the store directly are likely to have issues when used with React's concurrent mode. That's one of the big reasons why we switched from direct subscriptions to passing the current state value in context.

Please see my post Idiomatic Redux: The History and Implementation of React-Redux for background on this.

However, as mentioned earlier, computing a derived value from context does not allow bailing out if that value hasn't changed, so we can't currently build a hook that works this way. See the discussion in facebook/react#14110 .

@markerikson very interesting article! I had no idea react-redux has so much under the hood.
A few thoughts:

  1. I assume useEffect runs in top-down order (?). If so, it doesn't introduce the bugs that you describe when rendering lists and then deleting an item.
  2. Selectors that run in useEffect shouldn't delay React in any way as far as I know, right?
  3. Do we really need memoized selectors in a world where each selector is separate and will only cause re-render if the return value changed?
  4. I guess useSelector can accept extra options, like an isEqual function to compare selector results, and maybe debounce?

@adamkleingit : yup, that's one of the reasons why I wrote that post :)

Answering your questions:

  1. I haven't tried checking what order useEffect calls run in. However, the "tearing" concern is about more than the top-down aspect - it's about different components possibly using different store state during the same render pass.
  2. Not sure what you're asking here.
  3. Also not sure what you're asking here either. As I said in my last comment, see facebook/react#14110 for our concerns with using new context, and function components re-rendering.
  4. Yeah, definitely not following your train of thought here. Why in the world would you debounce a selector? How would a useSelector hook make use of isEqual?

I understand the problem now :) So for example parent component subscription gets called => parent state is updated => parent re-renders => child re-renders => child takes latest selector value which is outdated.
So the current direction is to keep the state in context, and wait for React to solve the problem of bailing out of re-render with useContext?

Basically, yeah.

What about, instead of passing the state through context, managing a list of selectors, and lazily subscribing to the store only once? Then if the state changes, go over the list of selectors and invoke them using the same state. And if the return value changed - call the useState callback (which is also saved alongside the selector)?

Like this:
https://codesandbox.io/s/y31zop4nxz

BTW, doesn't React already handle this internally?
I mean, the update function returned as 2nd arg from useState synchronously updates the internal state saved in React, but asynchronously forces the components to re-render.
Am I right?

No, the state is not synchronously updated. The queued state changes are applied as part of the re-rendering process.

But are they all applied before the rendering starts? Or they might occur alongside rendering?
BTW, what do you think about my proposed solution instead of using context?

State updates are, as far as I know, effectively applied during the rendering process. So, as React prepares to render a given component, it looks at the queued state updates , runs them, and finishes actually rendering.

My guess is that any approach that still relies on direct subscriptions is likely to still have tearing issues down the road.

Excuse me , Is there any solution to integrating react hook into react-redux after a long discussion above?

IMO useReducer covers a lot of use cases. Yes, there is no central store in this approach. And bye-bye to serializable state and hot reloading?

IMO useReducer covers a lot of use cases. Yes, there is no central store in this approach. And bye-bye to serializable state and hot reloading?

To me, useReducer is just a cleaner way to manage my components internal state. Or for really small applications i might use it at the top level. I definitely don't see it as a redux replacement.

Whatever we end up doing i wish that useStoreState, useSelector, or w/e its named would be able to bind actions with bindActionCreators from redux. It's convenient to be able to have a actions object which you can pass as your callbacks. E.g. <button onClick={actions.fetchMore}>Fetch more</button> rather than having to destructure N actions or keep N actions in an array and refer to them by index.

Quite excited about hooks support in react-redux!!

Our team is experimenting with a useStore() API (with Flow support), useStore reads from a context.

// store.js
// @flow
import { createStore, thunkMiddleware } from "@xxxx/redux";

export const { StoreProvider, useStore } = createStore<StoreState>(rootReducer);

// app.js
import { StoreProvider } from "./store";

export function App() {
  return (
    <StoreProvider>
      <NumberInput />
    </StoreProvider>
  );
}

export function NumberInput() {
  // useStore will automatically infer the store state flow type here!
  const [{ numberState }, dispatch] = useStore();
  const onChange = e =>
    dispatch({ type: "UPDATE_NUMBER", value: e.target.value });

  return (
    <div>
      Number:
      <input value={numberState.number} onChange={onChange} />
    </div>
  );
}

To support something similar to thunks, our lib ended up with a simple implementation of middlewares:

// store.js
import { createStore, thunkMiddleware } from "@xxxx/redux";

export const { StoreProvider, useStore } = createStore<StoreState>(rootReducer,
  [
    thunkMiddleware,
    /* ...middlewares */
  ]
);

// number-input.js
import {inputNumber} from './actions';

export function NumberInput() {
  const [{ numberState }, dispatch] = useStore();
  const onChange = e => dispatch(inputNumber(e.target.value))
  // ...
}

How about contexts.js:

import React from 'react';

const formatConnectName = (
  contextName
) => `connect${contextName.charAt(0).toUpperCase()}${contextName.slice(1)}`
const formatProviderName = (
  contextName
) => `${contextName.charAt(0).toUpperCase()}${contextName.slice(1)}Provider`

const create = (contexts) => {
  let Providers = {}
  let connectors = {}
  contexts.forEach(context => {
    let {Provider, Consumer} = React.createContext(context);
    Providers[formatProviderName(context)] = Provider;
    connectors[formatConnectName(context)] = (
      mapStateToProps, mapDispatchToProps
    ) => Target => props => (
      <Consumer>
        {store => (
          <Target
            {...mapStateToProps(store.getState())}
            {...mapDispatchToProps(store.dispatch)}
            {...props}
          />)}
      </Consumer>
    )
  })
  return {Providers, connectors}
};
const contexts = [
  'app'
];
const Contexts = create(contexts);

export const Providers = Contexts.Providers;
export const connectors = Contexts.connectors;

for usage

import React, { Component } from 'react';

import Sample from 'containers/Sample';
import { Providers } from 'contexts';
import store from 'store';
const { AppProvider } = Providers;

class App extends Component {
  render() {
    return (
          <AppProvider value={store}>
            <Sample/>
          </AppProvider>
    );
  }
}

export default App;

and

import React, { Component } from 'react';
import { connectors } from 'contexts';

const mapStateToProps = ({ test: { testid }}) => ({ testid })
const mapDispatchToProps = dispatch => ({
  test: dispatch({ type: 'TEST' })
})

const Sample = ({ testid }) => (
    <div className={sample}>
      <div>sample: {testid}</div>
    </div>
);

export default connectors.connectApp(mapStateToProps, mapDispatchToProps)(Sample);
[removed] doesn't add to the discussion (click here if you want to see the post)

This thread is too long already, I apologize in advance if I missed several things that add to the discussion.

My 2 cents. When hooks where first announced by Dan, I worked on partially replicating redux and react-redux from scratch to see what they were all about. I never had time to finalize it but it is working fine so far, so I decided to cleaned it up a bit and make the repo public for you guys to take a look:

https://github.com/pgarciacamou/redooks

There are 3 main files: src/App.js, src/react-redooks.js, and src/redooks.js.

The gist:

import React from "react";
import { useStore, combineReducers } from "./redooks.js";
import { useConnect } from "./react-redooks.js";

// reducers/index.js
function someProp(state, action) { /* ... */ }
const rootReducer = combineReducers({ someProp });

// stores/MainStore.js
const MainStoreContext = React.createContext();
function useMainStore(mapStateToProps, mapDispatchToProps) {
  return useConnect(MainStoreContext, mapStateToProps, mapDispatchToProps);
}
function MainStoreProvider({ children }) {
  const storeRef = useStore(rootReducer);
  return useMemo(() => (
    <MainStoreContext.Provider value={storeRef}>
      {children}
    </MainStoreContext.Provider>
  ), [storeRef, children]);
}

// App.js
function App() {
  return (
    <MainStoreProvider>
      <InnerComponent />
    </MainStoreProvider>
  );
}

// InnerComponent.js
function InnerComponent() {
  const [state, actions] = useMainStore(
    function mapStateToProps(state) { /* ... */ },
    function mapDispatchToProps(dispatch) { /* ... */ }
  );
  return /* ... */;
}

Feel free to test drive it, fork/comment.

@pgarciacamou That looks like a nice DX, but as far as I understand it the reason this thread has stalled a bit is because there are some low level issues around React concurrent mode and Redux hook implementations that subscribe to the store itself via context vs. subscribing to the state of the store from the top of the component tree via context - with the former being undesirable. I think nearly all of the proposed implementations posted here so far haven't addressed this, and we're still waiting on some decisions upstream in React itself.

@markerikson mentioned it in a comment here: #1063 (comment)

More background in his blog post here: https://blog.isquaredsoftware.com/2018/11/react-redux-history-implementation/

That will probably solve the performance issue but I wonder if putting partial states to local state using useState results in the tearing issues again (#86)?

Thanks @jedrichards, @thomschke, and @epeli! It all makes more sense now.

As a quick FYI, we're doing some internal discussions to try to figure out a way forward regarding the hooks and perf aspects. I'll put up a roadmap issue once I've got an actual idea what we're going to do, but no ETA on that.

Please use redux-react-hook from facebook incubator. They do provide the equivalent of mapState and mapDispatch which are enough bindings to setup the workflow in React hooks. I've been working with Hooks version since last month

Hi all, redhooks is another implementation of redux's principles. It reimplements redux's API using Hooks and the Context API. It also supports the use of middleware like redux-thunk or redux-saga or your custom middleware conforming to the middleware's API.

@iusehooks I also thought about this idea, redux is very tiny library and porting it as first React citizen might be simpler and more performant approach than keeping Redux as separate package and worrying about how to write connection layer

Could you please refrain from spamming this thread with redux alternatives and asking the same questions all over again. Take the time to read the thread first as a courtesy to those who already have and are subscribed to it.

This thread is about the hooks implementation for redux. Unless you have any insights about how to do the performance optimization needed to implement hooks for redux or are a maintainer that has updates, this is just alerting a ton of people continuously about nothing.

Not sure to whom you refers. But I didn't ask any questions. For sure didn't repeat😂

If you have a problem with an alternative that works for now, you can skip reading my comment and spamming yourself.

People are trying to discuss/solve what's already is done. So posted. If that alternative is not optimal, sure release a better one. And next time learn some manners how to talk in front of tons of people you are aware of.

Awright, this thread is starting to become useless.

I'm going to lock it for the time being, but not close it.

I'm unfortunately busy with day job stuff atm, so I haven't been able to turn my full attention to figuring out what we're going to do next. I hope to spend some time on that this weekend, but I can't guarantee anything.

As I said, I'll post a roadmap issue and publicize it as soon as I actually have something meaningful to say.

This issue has been superseded by the roadmap laid out in #1177 . Please see that issue for plans and any links to further discussions on this topic.