Skip to content
This repository was archived by the owner on Nov 10, 2021. It is now read-only.

[Question] Why multi store subscriptions? #33

Closed
hnordt opened this issue Feb 16, 2019 · 4 comments
Closed

[Question] Why multi store subscriptions? #33

hnordt opened this issue Feb 16, 2019 · 4 comments

Comments

@hnordt
Copy link

hnordt commented Feb 16, 2019

Just for curiosity, why you've opted for a multi-store subscription model instead of subscribing to the store in a top-level Provider?

Something like:

const ReduxStateContext = React.createContext()

function ReduxStateProvider({ store, children }) {
  const [state, setState] = React.useState(() => store.getState())

  useEffect(() => {
    let willUnsubscribe = false

    const checkForUpdates = () => {
      if (willUnsubscribe) return

      setState(prevState => {
        const nextState = store.getState()
        return shallowEqual(prevState, nextState) ? prevState : nextState
      })
    }

    checkForUpdates()

    const unsubscribe = store.subscribe(checkForUpdates)

    return () => {
      willUnsubscribe = true
      unsubscribe()
    }
  }, [store])

  return (
    <ReduxStateContext.Provider value={state}>
      {children}
    </ReduxStateContext.Provider>
  )
}

export function useMappedState(mapState) {
  const state = useContext(ReduxStateContext)

  const [derivedState, setDerivedState] = useState(() => mapState(state))

  useEffect(() => {
    setDerivedState(prevDerivedState => {
      const nextDerivedState = mapState(state)
      return shallowEqual(prevDerivedState, nextDerivedState)
        ? prevDerivedState
        : nextDerivedState
    })
  }, [state])

  // It might not even need useEffect() 🤔 (getDerivedStateFromProps)
  setDerivedState(prevDerivedState => {
    const nextDerivedState = mapState(state)
    return shallowEqual(prevDerivedState, nextDerivedState)
      ? prevDerivedState
      : nextDerivedState
  })

  return derivedState
}

Only the ReduxStateProvider subscribes to store updates, then it passes the update down to all consumers. Consumers have a chance to bail out by comparing prevDerivedState with nextDerivedState.

@brunolemos
Copy link

const state = useContext(ReduxStateContext)

Every single action that is dispatched via redux and change any place of the state would trigger a rerender on all components that are using useMappedState simply because it's using the line above.

Context updates triggers a rerender just like useState updates and you can't bail out of context updates yet, see item 2: facebook/react#14110

@hnordt
Copy link
Author

hnordt commented Feb 16, 2019

But then the consumer will bail out via setDerivedState. This same problem happens to the multi store approach, because every hook subscribes to the store.

@hnordt
Copy link
Author

hnordt commented Feb 16, 2019

Hum, I think I got it. The useContext will make the component using this hook to rerender, the setDerivedState bails out itself only.

@hnordt hnordt closed this as completed Feb 16, 2019
@ianobermiller
Copy link
Contributor

See also the react-redux roadmap on why they are going back to using their own subscription rather than context: reduxjs/react-redux#1177

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants