Last active
February 11, 2020 09:22
-
-
Save misterjohannesson/034128924b72a1bfbffc2d365bd1343a to your computer and use it in GitHub Desktop.
Generic List Reducer, React, Typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Reducer } from "react"; | |
/** | |
* Different mutating types to be performed in the reducers dispatch. | |
*/ | |
export enum ListReducerActionType { | |
/** Remove one or many item(s) from state */ | |
Remove, | |
/** Replace one or many item(s) from state if present */ | |
Update, | |
/** Append one or many item(s) to state*/ | |
Add, | |
/** Replace or append one or many item(s) of state*/ | |
AddOrUpdate, | |
/** Reset the whole state */ | |
Reset | |
} | |
//The conditional type, specifying the type of data depending on the action type | |
type ActionDataType<T, K> = K extends ListReducerActionType.Reset | |
? T[] | |
: K extends ListReducerActionType.Remove | |
? T[keyof T][] | T[keyof T] | |
: T[] | T; | |
type ListReducerActionInput<T, K extends ListReducerActionType> = { | |
type: K; | |
data: ActionDataType<T, K>; | |
}; | |
type AllListActions<T> = | |
| ListReducerActionInput<T, ListReducerActionType.Remove> | |
| ListReducerActionInput<T, ListReducerActionType.Update> | |
| ListReducerActionInput<T, ListReducerActionType.Add> | |
| ListReducerActionInput<T, ListReducerActionType.Reset> | |
| ListReducerActionInput<T, ListReducerActionType.AddOrUpdate>; | |
/** | |
* | |
* @typeparam `T` type of the reducer state | |
* @param {keyof T} key value of `U` | |
* @return {Reducer} React reducer for a stateful list of `T` | |
* | |
* Can be initiated like this | |
* `listReducer<Entity>("id")` | |
* Where `Entity` is the type of the list | |
* and `"id"` is a property key on the type | |
* that is to be used to find index in the list | |
*/ | |
export default <T>(key: keyof T): Reducer<T[], AllListActions<T>> => ( | |
state: T[], | |
action: AllListActions<T> | |
) => { | |
const replace = (t: T) => { | |
const index = state.findIndex(i => i[key] === t[key]); | |
state[index] = t; | |
}; | |
switch (action.type) { | |
case ListReducerActionType.AddOrUpdate: | |
if ((action.data as T[]).push) { | |
console.log("what?"); | |
} else { | |
const index = state.findIndex(i => i[key] === (action.data as T)[key]); | |
if (index !== -1) { | |
replace(action.data as T); | |
return [...state]; | |
} else { | |
return [...state, action.data as T]; | |
} | |
} | |
case ListReducerActionType.Add: | |
if ((action.data as T[]).push) { | |
return [...state, ...(action.data as T[])]; | |
} else { | |
return [...state, action.data as T]; | |
} | |
case ListReducerActionType.Update: { | |
if ((action.data as T[]).push) { | |
(action.data as T[]).forEach(replace); | |
} else { | |
replace(action.data as T); | |
} | |
return [...state]; | |
} | |
case ListReducerActionType.Remove: | |
if ((action.data as T[keyof T][]).push) { | |
return state.filter( | |
t => (action.data as T[keyof T][]).indexOf(t[key]) === -1 | |
); | |
} else { | |
return state.filter(t => t[key] !== action.data); | |
} | |
case ListReducerActionType.Reset: | |
return action.data as T[]; | |
default: | |
return state; | |
} | |
}; |
My new solution to my previous problem is still not fully optimal.
But at least there is only one possible type for when the type is Remove.
The new revision changes initialization to
const [items, dispatchItems] = useReducer( listReducer<Item, "id">("id"), [] );
And then the remove action looks the same but accepted types are different
const removeItems= useCallback((item: Item) => {
dispatchItems({
type: ListReducerActionType.Remove,
data: item.id //The accepted types are: string | string[]
}
}, [])
If I can now find a way to have type "id"
to also be a value then I am golden.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is my default React Reducer for when using function components, an array state and the
useReducer
hook in typescript.The way to use it is quite simply, imagine having this type
Then inside the functional components having an array of these
Item
s is easyMy only gripe with this (and something I will try to study and figure out how to resolve) is that when using the
Remove
action the typeof the data isn't just the type of the key from the parameter, rather it is the types of any of the keys.
For example
Here data can also be the type of the other key
completed
I am sure there is a trick to it - just havent gotten that deep in the advance Typescript... yet.