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
DISPATCHasSync<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
DISPATCHasAsync(orAsync<>for Flow) orPromise<void>:useReducer<STATE, ACTION, Async>('name'), i.e ReduceSTATEusingACTIONAsynchronously.useReducerDispatcher<ACTION, Async>('name'), i.e dispatchACTIONAsynchronously.
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
DISPATCHasAsync<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.