Typings
react-reducer-provider
defines typings for Flow and Typescript:
- Any can be used without an “special” [1] configuration.
- Typings definitions are located together with source files:
- Flow:
react-reducer-provider.js.flow
[2]. - Typescript:
react-reducer-provider.d.ts
.
- Flow:
- Typings definitions are located together with source files:
Both provide the following types:
- For Reducer Component Definition:
SyncReducerProvider<STATE, ACTION>
: specifies the Function React Component structure.- and the respective props types:
SyncReducerProps<STATE, ACTION>
- and the respective props types:
SyncReducer<STATE, ACTION>
: defines the structure of the synchronous reducer function.AsyncReducerProvider<STATE, ACTION>
: specifies the Function React Component structure.- and the respective props types:
AsyncReducerProps<STATE, ACTION>
- and the respective props types:
AsyncReducer<STATE, ACTION>
: defines the structure of the asynchronous reducer function.
- For Mapper Component Definition:
SyncMapperProvider<STATE, ACTION>
: specifies the Function React Component structure.- and the respective props types:
SyncMapperProps<STATE, ACTION>
- and the respective props types:
SyncMapper<STATE, ACTION>
: defines the structure of the synchronous mapper function.AsyncMapperProvider<STATE, ACTION>
: specifies the Function React Component structure.- and the respective props types:
AsyncMapperProps<STATE, ACTION>
- and the respective props types:
AsyncMapper<STATE, ACTION>
: defines the structure of the asynchronous mapper function.
- For Consumption:
Dispatcher<ACTION, DISPATCH>
: defines the function that receives the action that triggers the change of the state.ProviderValue<STATE, ACTION, DISPATCH>
: defines the structure of the value return byuseReducer
/useMapper
.useReducer<STATE, ACTION, DISPATCH>(id?: Id): ProviderValue<STATE, ACTION, DISPATCH>
.useReducerState<STATE>(id?: Id): STATE
.useReducerDispatcher<ACTION, DISPATCH>(id?: Id): Dispatcher<ACTION, DISPATCH>
.useMapper<STATE, ACTION, DISPATCH>(id?: Id): ProviderValue<STATE, ACTION, DISPATCH>
.useMapperState<STATE>(id?: Id): STATE
.useMapperDispatcher<ACTION, DISPATCH>(id?: Id): Dispatcher<ACTION, DISPATCH>
.
STATE
: State type, defined by the developer.
ACTION
: Action type, defined by the developer.
What is DISPATCH
?
Defines how the action is dispatch, thereby, the type of the reducer.
DISPATCH
is required only for Reducer Consumption, and can take 1 of 2 values:
Async<T>
.Sync<T>
, which is the default (Sync<void>
), and there is no need to be specified.
[1] Only the usual Flow or Typescript configuration (e.g. no need for @types package).
[2] I prefer Typescript over Flow, i.e. Flow is only tested here (in this project), so feel free to create an issue if something wrong is found. And Flow typings can be a little obsolete compare to Typescript.
Synchronous Reducer/Mapper
1 . Define the State:
interface TestState {
lastAction: number;
}
const initialState: TestState = {
lastAction: 0
}
2 . Define the Reducer:
function reduce(prevState: TestState, action: string): TestState {
switch (action) {
case 'ACTION1':
return {
lastAction: 1
}
default:
return prevState
}
}
or with additional parameters:
function reduce(prevState: TestState, action: string, param1: number, param2: boolean, paramN: {}): TestState {
switch (action) {
case 'ACTION1':
return {
lastAction: 1
}
default:
return prevState
}
}
3 . Define the Reducer Provider:
<SyncReducerProvider
id='testNamedReducer'
reducer={reduce}
initialState={initialState}
>
{children}
</SyncReducerProvider>
2 . Define the Mapper:
function map(action: string): TestState {
switch (action) {
case 'ACTION1':
return {
lastAction: 1
}
default:
return initialState
}
}
or with additional parameters:
function map(action: string, param1: number, param2: boolean, paramN: {}): TestState {
switch (action) {
case 'ACTION1':
return {
lastAction: 1
}
default:
return initialState
}
}
3 . Define the Mapper Provider:
<SyncMapperProvider
id='testNamedMapper'
mapper={map}
initialState={initialState}
>
{children}
</SyncMapperProvider>
4 . Define the Consumption:
Ignoring returned value:
Types in function approach (Recommended):
- More Readable.
- Less coding.
- Less imports.
useReducer
`useMapper`:
const [ state, dispatch ] = useReducer<TestState, string>('testNamedReducer')
return (
<button onClick={(): void => dispatch('ACTION1')}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState = useReducerState<TestState>('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher = useReducerDispatcher<string>('testNamedReducer')
return (
<button onClick={(): void => theDispatcher('ACTION1')}>
Children
</button>
)
Types in values approach:
useReducer
`useMapper`:
const [ state, dispatch ]: ProviderValue<TestState, string> = useReducer('testNamedReducer')
return (
<button onClick={(): void => dispatch('ACTION1')}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState: TestState = useReducerState('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher: Dispatcher<string> = useReducerDispatcher('testNamedReducer')
return (
<button onClick={(): void => theDispatcher('ACTION1')}>
Children
</button>
)
Using returned value:
- Must define
DISPATCH
asSync<STATE>
orSTATE
:
useReducer
`useMapper`:
const [ state, dispatch ] = useReducer<TestState, string, Sync<STATE>>('testNamedReducer')
return (
<button onClick={(): void => console.log(dispatch('ACTION1'))}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState = useReducerState<TestState>('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher = useReducerDispatcher<string, Sync<STATE>>('testNamedReducer')
return (
<button onClick={(): void => console.log(theDispatcher('ACTION1'))}>
Children
</button>
)
Types in values approach:
useReducer
`useMapper`:
const [ state, dispatch ]: ProviderValue<TestState, string, Sync<STATE>> = useReducer('testNamedReducer')
return (
<button onClick={(): void => console.log(dispatch('ACTION1'))}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState: TestState = useReducerState('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher: Dispatcher<string, Sync<STATE>> = useReducerDispatcher('testNamedReducer')
return (
<button onClick={(): void => console.log(theDispatcher('ACTION1'))}>
Children
</button>
)
Asynchronous Reducer/Mapper
1 . Define the State:
interface TestState {
lastAction: number;
}
const initialState: TestState = {
lastAction: 0
}
2 . Define the Reducer:
async function asyncReducer<STATE, ACTION>(prevState: STATE, action: ACTION): Promise<STATE>
async function reduce(prevState: TestState, action: string): Promise<TestState> {
switch (action) {
case 'ACTION1':
return someAsyncProcess()
.then(() =>({
lastAction: 1
}))
.catch(() =>({
lastAction: 0
}))
default:
return prevState
}
}
3 . Define the Reducer Provider:
<AsyncReducerProvider
id='testNamedReducer'
reducer={reduce}
initialState={initialState}
>
{children}
</AsyncReducerProvider>
2 . Define the Mapper:
async function asyncMapper<STATE, ACTION>(STATE, action: ACTION): Promise<STATE>
async function map(action: string): Promise<TestState> {
switch (action) {
case 'ACTION1':
return someAsyncProcess()
.then(() =>({
lastAction: 1
}))
.catch(() =>({
lastAction: 0
}))
default:
return initialState
}
}
3 . Define the Mapper Provider:
<AsyncMapperProvider
id='testNamedReducer'
mapper={map}
initialState={initialState}
>
{children}
</AsyncMapperProvider>
4 . the Define Reducer Consumption:
Ignoring returned value:
- Must define
DISPATCH
asAsync
(orAsync<>
for Flow) orPromise<void>
:useReducer<STATE, ACTION, Async>('name')
, i.e ReduceSTATE
usingACTION
Async
hronously.useReducerDispatcher<ACTION, Async>('name')
, i.e dispatchACTION
Async
hronously.
Types in function approach (Recommended):
- More Readable.
- Less coding.
- Less imports.
useReducer
`useMapper`:
const [ state, dispatch ] = useReducer<TestState, string, Async>('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={async (): Promise<void> => await dispatch('ACTION1')
.then(someFunc)
}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState = useReducerState<TestState>('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher = useReducerDispatcher<string, Async>('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={(): Promise<void> => theDispatcher('ACTION1')
.then(someFunc)
}>
Children
</button>
)
Types in values approach:
useReducer
`useMapper`:
const [ state, dispatch ]: ProviderValue<TestState, string, Async> = useReducer('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={async (): Promise<void> => await dispatch('ACTION1')
.then(someFunc)
}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState: TestState = useReducerState('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher: Dispatcher<string, Async> = useReducerDispatcher('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={(): Promise<void> => theDispatcher('ACTION1')
.then(someFunc)
}>
Children
</button>
)
Using returned value:
- Must define
DISPATCH
asAsync<STATE>
orPromise<STATE>
:
Types in function approach (Recommended):
- More Readable.
- Less coding.
- Less imports.
useReducer
`useMapper`:
const [ state, dispatch ] = useReducer<TestState, string, Async<TestState>>('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={async (): Promise<void> => await dispatch('ACTION1').then(newState => console.info(newState))
.then(someFunc)
}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState = useReducerState<TestState>('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher = useReducerDispatcher<string, Async<TestState>>('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={(): Promise<void> => theDispatcher('ACTION1').then(newState => console.info(newState))
.then(someFunc)
}>
Children
</button>
)
Types in values approach:
useReducer
`useMapper`:
const [ state, dispatch ]: ProviderValue<TestState, string, Async<TestState>> = useReducer('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={async (): Promise<void> => await dispatch('ACTION1').then(newState => console.info(newState))
.then(someFunc)
}>
Child{state.lastAction}
</button>
)
useReducerState
`useMapperState`:
const theState: TestState = useReducerState('testNamedReducer')
return (
<button>
Child{theState.lastAction}
</button>
)
useReducerDispatcher
`useMapperDispatcher`:
const theDispatcher: Dispatcher<string, Async<TestState>> = useReducerDispatcher('testNamedReducer')
const someFunc = () => {}
return (
<button onClick={(): Promise<void> => theDispatcher('ACTION1').then(newState => console.info(newState))
.then(someFunc)
}>
Children
</button>
)
HOC
Only for HOC there is a difference approach for Typescript and Flow (due to some Flow limitations).
Typescript:
inject<INJECTED_PROP extends object, PROPS extends object = object>(
ComponentClass: React.ComponentClass<PROPS>, injectedPropName: keyof INJECTED_PROP, id?: Id
): React.ComponentClass<PROPS>.
INJECTED_PROP
(Required): An object containing only 1 field with the desired name for the prop to be injected.
PROPS
: Properties of the Component to be exported.
Flow:
inject<INJECTED_PROP: {}, PROPS: {}>(
ComponentClass: AbstractComponent<PROPS>, injectedPropName: $Keys<INJECTED_PROP>, id?: Id
): AbstractComponent<$Diff<PROPS, INJECTED_PROP>>
INJECTED_PROP
(Required): An object containing only 1 field with the desired name for the prop to be injected.
PROPS
(Required): Properties of the “inner” Component.
injectReducer
`injectMapper`:
Typescript:
interface SomeReducerMainHoc0Props { someProp: string }
interface SomeReducerMainHoc1Props { reducer: ProviderValue<TestState, string> }
interface SomeReducerMainHocProps extends SomeReducerMainHoc0Props, SomeReducerMainHoc1Props { }
interface SomeReducerMainHocState { someState: number }
class SomeReducerMainHoc extends React.Component<SomeReducerMainHocProps, SomeReducerMainHocState> {
render() {
const [ state, dispatch ]: ProviderValue<TheState, string> = this.props.reducer
return (
<button onClick={() => dispatch('ACTION1')}>
Child{state.lastAction}
</button>
)
}
}
const SomeReducerMainHoc0 = injectReducer<SomeReducerMainHoc1Props, SomeReducerMainHoc0Props>(SomeReducerMainHoc, 'reducer', 'someNamedReducer')
Flow:
interface SomeReducerMainHocProps { reducer: ProviderValue<TheState, string>, someProp: string }
interface SomeReducerMainHocState { someState: number }
class SomeReducerMainHoc extends React.Component<SomeReducerMainHocProps, SomeReducerMainHocState> {
render() {
const [ state, dispatch ]: ProviderValue<TheState, string> = this.props.reducer
return (
<button onClick={() => dispatch('ACTION1')}>
Child{state.lastAction}
</button>
)
}
}
const SomeReducerMainHoc0 = injectReducer<{ reducer: * }, SomeReducerMainHocProps>(SomeReducerMainHoc, 'reducer', 'someNamedReducer')
injectReducerState
`injectMapperState`:
Typescript:
interface SomeReducerStateHoc0Props { someProp: string }
interface SomeReducerStateHoc1Props { state: TestState }
interface SomeReducerStateHocProps extends SomeReducerStateHoc0Props, SomeReducerStateHoc1Props { }
interface SomeReducerStateHocState { someState: number }
class SomeReducerStateHoc extends React.Component<SomeReducerStateHocProps, SomeReducerStateHocState> {
render() {
const theState: TheState = this.props.state
return (
<button>
Child{theState.lastAction}
</button>
)
}
}
const SomeReducerStateHoc0 = injectReducerState<SomeReducerStateHoc1Props, SomeReducerStateHoc0Props>(SomeReducerStateHoc, 'state', 'someNamedReducer')
Flow:
interface SomeReducerStateHocProps { state: TheState, someProp: string }
interface SomeReducerStateHocState { someState: number }
class SomeReducerStateHoc extends React.Component<SomeReducerStateHocProps, SomeReducerStateHocState> {
render() {
const theState: TheState = this.props.state
return (
<button>
Child{theState.lastAction}
</button>
)
}
}
const SomeReducerStateHoc0 = injectReducerState<{ state: * }, SomeReducerStateHocProps>(SomeReducerStateHoc, 'state', 'someNamedReducer')
injectReducerDispatcher
`injectMapperDispatcher`:
Typescript:
interface SomeReducerDispatcherHoc0Props { someProp: string }
interface SomeReducerDispatcherHoc1Props { dispatcher: Dispatcher<string> }
interface SomeReducerDispatcherHocProps extends SomeReducerDispatcherHoc0Props, SomeReducerDispatcherHoc1Props { }
interface SomeReducerDispatcherHocDispatcher { someDispatcher: number }
class SomeReducerDispatcherHoc extends React.Component<SomeReducerDispatcherHocProps, SomeReducerDispatcherHocDispatcher> {
render() {
const theDispatcher: Dispatcher<string> = this.props.dispatcher
return (
<button onClick={(): void => theDispatcher('ACTION1')}>
Children
</button>
)
}
}
const SomeReducerDispatcherHoc0 = injectReducerDispatcher<SomeReducerDispatcherHoc1Props, SomeReducerDispatcherHoc0Props>(SomeReducerDispatcherHoc, 'dispatcher', 'someNamedReducer')
Flow:
interface SomeReducerDispatcherHocProps { dispatcher: Dispatcher<string>, someProp: string }
interface SomeReducerDispatcherHocDispatcher { someDispatcher: number }
class SomeReducerDispatcherHoc extends React.Component<SomeReducerDispatcherHocProps, SomeReducerDispatcherHocDispatcher> {
render() {
const theDispatcher: Dispatcher<string> = this.props.dispatcher
return (
<button onClick={(): void => theDispatcher('ACTION1')}>
Children
</button>
)
}
}
const SomeReducerDispatcherHoc0 = injectReducerDispatcher<{ dispatcher: * }, SomeReducerDispatcherHocProps>(SomeReducerDispatcherHoc, 'dispatcher', 'someNamedReducer')
- An example with Typescript typings can be checked on line at gmullerb-react-reducer-provider-ts codesandbox:
Helper types
Additionally a Helper type is available: Action<TYPE, DATA>
, useful for defining more complex types of actions:
fields:
type: TYPE
: identifies the action.data?: DATA
: data required by the action.
e.g.:
export const enum AppActionType {
AUTH,
LOGOUT,
ADD_ITEM
}
export type AppActionData = User | string | undefined
export interface AppAction extends Action<AppActionType, AppActionData> {}
function reduce(prevState: AppState, action: AppAction): AppState {
switch (action.type) {
case AppActionType.AUTH:
return auth(prevState, action.data as User)
case AppActionType.LOGOUT:
return logout(prevState)
case AppActionType.ADD_ITEM:
return addItem(prevState, action.data as string)
Additional Examples
- A more “complete” example with Typescript can be seen at
typing ts test folder
. - A more “complete” example with Flow can be seen at
typing flow test folder
.
An example with Typescript typings can be checked on line at gmullerb-react-reducer-provider-ts codesandbox:
An example with Flow typings can be checked on line at gmullerb-react-reducer-provider-flow codesandbox:
An asynchronous example with Typescript typings can be checked on line at gmullerb-react-reducer-provider-async-ts codesandbox:
Examples of use can be looked at basecode-react-ts and basecode-cordova-react-ts.