react-reducer-provider.github.io

Asynchronous/Synchronous React Centralized State

Follow me on GitHub

useReducer · useReducerState · useReducerDispatcher · useMapper · useMapperState · useMapperDispatcher

(React ≥ 16.8.0)

Consumption

Reducer or Mapper will never be accessible directly from children elements, they will be able to access the State and/or Dispatcher.

There are different ways of doing this:

  • useReducer, which give access both State and Dispatcher.
  • useReducerDispatcher, which give access only the Dispatcher.
  • useReducerState, which give access only the State.

or

  • useMapper, which give access both State and Dispatcher.
  • useMapperDispatcher, which give access only the Dispatcher.
  • useMapperState, which give access only the State.

Consumption

When using useReducer/useMapper, useReducerDispatcher/useMapperDispatcher and/or useReducerState/useMapperState, Be Aware that they use React.useContext and quote: ‘A component calling useContext will always re-render when the context value changes’, in this case when state changes, therefore when using useReducerDispatcher/useMapperDispatcher although it not depends “directly” on state the component will be re-render when state changes. Final words, use SyncMapperProvider and/or AsyncMapperProvider,SyncReducerProvider and/or AsyncReducerProvider everywhere is required and use useReducer/useMapper, useReducerDispatcher/useMapperDispatcher and/or useReducerState/useMapperState wisely (small scopes, as close to where is required with small amount of children). If children re-render is too expensive then React.useMemo:

const FunComponent1 = () => {
  const dispatch = useReducerDispatcher('testNamedReducer10')
  return React.useMemo(() => (
    <RelatedChildComponent
      onClick={dispatch}
    />
  ), [dispatch])
}

(check test case ‘should get the same dispatcher references after state changes’ at SyncReducerProvider.test.jsx or AsyncReducerProviderWithAsync.test.jsx)

useReducer/useMapper

useReducer(id)
useMapper(id)

parameters:

  • id ?: string | number | symbol: constitutes the identifier (name, number or symbol) of the SyncReducerProvider, AsyncReducerProvider,SyncMapperProvider or AsyncMapperProvider being accessed.

returns:

a tuple containing:

  • [0]: the state.
  • [1]: the dispatcher.
  • [2]: the provider id.
  • state: the state.
  • dispatch: the dispatcher.
  • provider: the provider id.

Trying to reassign state, dispatch, provider, [0], [1] or [2] will result in aTypeError: Cannot assign to read only property '..' of object '[object Array]' Exception.
Trying to add new fields will result in a TypeError: can't define property "..": Array is not extensible Exception.
For purpose of avoiding re-renders and/or improving performance always use the elements of the tuple as reference, never the tuple perse, keep in mind that the tuple that is returned may change but elements will only change when state changes. Also, can use useEffect/useMemo/useCallback. This is not an “issue” when using the elements of the tuple as reference or when using use*Dispatcher or use*State.

import { useReducer } from 'react-reducer-provider'
import React from 'react'

export default function SomeComponent1() {
  const [ state, dispatch ] = useReducer('someNamedReducer')
  return (
    <button onClick={() => dispatch('ACTION1')}>
      Go up (from {state})!
    </button>
  )
}

or

import { useReducer } from 'react-reducer-provider'
import React from 'react'

export default function SomeComponent1() {
  const { state, dispatch } = useReducer('someNamedReducer')
  return (
    <button onClick={() => dispatch('ACTION1')}>
      Go up (from {state})!
    </button>
  )
}

useReducerDispatcher/useMapperDispatcher

useReducerDispatcher(id)
useMapperDispatcher(id)

parameters:

  • id ?: string | number | symbol: constitutes the identifier (name, number or symbol) of the SyncReducerProvider, AsyncReducerProvider,SyncMapperProvider or AsyncMapperProvider being accessed.

returns:

  • the dispatcher of the respective Reducer/Mapper Provider.
import { useReducerDispatcher } from 'react-reducer-provider'
import React from 'react'

export default function SomeComponent2() {
  const dispatch = useReducerDispatcher('someNamedReducer')
  return (
    <button onClick={() => dispatch('ACTION2')}>
      Go down!
    </button>
  )
}

useReducerState/useMapperState

useReducerState(id)
useMapperState(id)

parameters:

  • id ?: string | number | symbol: constitutes the identifier (name, number or symbol) of the SyncReducerProvider, AsyncReducerProvider,SyncMapperProvider or AsyncMapperProvider being accessed.

returns:

  • the state of the respective Reducer/Mapper Provider.
import { useReducerState } from 'react-reducer-provider'
import React from 'react'

export default function SomeComponentN() {
  const currentState = useReducerState('someNamedReducer')
  return (
    <div>
      Current:{currentState}
    </div>
  )
}

Error

When the associated Reducer Provider can not be found, i.e. the id trying to be used by any react-reducer-provider hook is not defined, the the following error may appear:

TypeError: Cannot read property '_context' of undefined

Check the id of the defined Reducer Providers, and use a valid one.

Synchronous Consumption

Dispatcher

  • when accessing the Reducer Provider, the dispatcher will be also a synchronous function:

    function dispatch<ACTION>(action: ACTION): void

    e.g.:

  export default function SomeComponent2() {
    const dispatch = useReducerDispatcher('someNamedReducer')
    return (
      <button onClick={() => dispatch('ACTION2')}>
        Go down!
      </button>
    )
  }

An SyncReducerProvider example can be checked on line at gmullerb-react-reducer-provider codesandbox:
Edit gmullerb-react-reducer-provider
An SyncMapperProvider example can be checked on line at gmullerb-react-mapper-provider codesandbox:
Edit gmullerb-react-mapper-provider

Asynchronous Consumption

Dispatcher

  • when accessing the Reducer Provider, the dispatcher will be also a asynchronous function:

    async function dispatch<ACTION>(action: ACTION): Promise<void>

    e.g.:

  export default function SomeComponent2() {
    const dispatch = useReducerDispatcher('someNamedReducer')
    return (
      <button onClick={(async () => dispatch('ACTION2').then(someProcess())}>
        Go down!
      </button>
    )
  }

When the dispatch is resolved is an indication that the state was change, but not of any required re-rendering being done.
An AsyncReducerProvider can be checked on line at gmullerb-react-reducer-provider codesandbox:
Edit gmullerb-react-reducer-provider
An AsyncMapperProvider example can be checked on line at gmullerb-react-mapper-provider-async codesandbox:
Edit gmullerb-react-mapper-provider-async
Although AsyncReducerProvider can be used for synchronous reducer/dispatcher (check AsyncReducerProviderWithSync.test.jsx), It is not is purpose and implementation is suitable for asynchronous processes, long story short, for synchronous processes, use SyncReducerProvider.
Examples of use can be looked at basecode-react-ts and basecode-cordova-react-ts.


More Documentation

Main documentation

Back to homepage