Created
March 30, 2019 20:23
-
-
Save schettino/c8bf5062ef99993ce32514807ffae849 to your computer and use it in GitHub Desktop.
Better Reducers with React and Typescript 3.4
This file contains 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 { useReducer } from 'react' | |
export function updateName(name: string) { | |
return <const>{ | |
type: 'UPDATE_NAME', | |
name | |
} | |
} | |
export function addPoints(points: number) { | |
return <const>{ | |
type: 'ADD_POINTS', | |
points | |
} | |
} | |
export function setLikesGames(value: boolean) { | |
return <const>{ | |
type: 'SET_LIKES_GAMES', | |
value | |
} | |
} | |
export const initialState = { | |
name: '', | |
points: 0, | |
likesGames: true | |
} | |
type State = typeof initialState | |
type Action = ReturnType< | |
typeof updateName | typeof addPoints | typeof setLikesGames | |
> | |
function reducer(state: State, action: Action): State { | |
switch (action.type) { | |
case 'UPDATE_NAME': | |
return { ...state, name: action.name } | |
case 'ADD_POINTS': | |
return { ...state, points: action.points } | |
case 'SET_LIKES_GAMES': | |
return { ...state, likesGames: action.value } | |
default: | |
return state | |
} | |
} | |
export default function useUserReducer(state = initialState) { | |
return useReducer(reducer, state) | |
} |
ReactTodoTypesafeState.ts
import uuid from "uuid/v4"
//
// Filter
//
export enum FilterType {
ALL = "ALL",
COMPLETE = "COMPLETE",
INCOMPLETE = "INCOMPLETE",
}
export const FilterTypes = Object.values(FilterType)
export enum FilterActionType {
SHOW_ALL = "SHOW_ALL",
SHOW_COMPLETE = "SHOW_COMPLETE",
SHOW_INCOMPLETE = "SHOW_INCOMPLETE",
}
export const FilterActionTypes = Object.keys(FilterActionType)
export function isOfFilterActionTypes({ type }: { type: string }): boolean {
return FilterActionTypes.includes(type)
}
export function showAll() {
return {
type: FilterActionType.SHOW_ALL,
} as const
}
export function showComplete() {
return {
type: FilterActionType.SHOW_COMPLETE,
} as const
}
export function showIncomplete() {
return {
type: FilterActionType.SHOW_INCOMPLETE,
} as const
}
// type FilterType = "ALL" | "COMPLETE" | "INCOMPLETE"
export type FilterState = FilterType
export type FilterAction = ReturnType<typeof showAll | typeof showComplete | typeof showIncomplete>
export const filterReducer = (state: FilterState, action: FilterAction): FilterState => {
switch (action.type) {
case FilterActionType.SHOW_ALL:
return FilterType.ALL
case FilterActionType.SHOW_COMPLETE:
return FilterType.COMPLETE
case FilterActionType.SHOW_INCOMPLETE:
return FilterType.INCOMPLETE
default:
return state
}
}
//
// Todo
//
export type Todo = {
id: string
task: string
complete: boolean
}
export const todoInit: Todo = {
id: "",
task: "",
complete: false,
}
export type TodosState = Todo[]
export const todosInit: Todo[] = [
{
id: uuid(),
task: "Learn React",
complete: true,
},
{
id: uuid(),
task: "Learn Firebase",
complete: false,
},
{
id: uuid(),
task: "Learn GraphQL",
complete: false,
},
]
export enum TodoActionType {
DO_TODO = "DO_TODO",
UNDO_TODO = "UNDO_TODO",
ADD_TODO = "ADD_TODO",
}
export const TodoActionTypes = Object.values(TodoActionType)
export function isOfTodoActionTypes({ type }: { type: string }): boolean {
return TodoActionTypes.includes(type)
}
export function doTodo(id: string) {
return {
type: TodoActionType.DO_TODO,
id,
} as const
}
export function undoTodo(id: string) {
return {
type: TodoActionType.UNDO_TODO,
id,
} as const
}
export function addTodo(task: string) {
return {
type: TodoActionType.ADD_TODO,
task,
} as const
}
export type TodoAction = ReturnType<typeof doTodo | typeof undoTodo | typeof addTodo>
export function todoReducer(state: TodosState, action: TodoAction): TodosState {
switch (action.type) {
case TodoActionType.DO_TODO:
return state.map(todo => {
if (todo.id === action.id) {
return { ...todo, complete: true }
} else {
return todo
}
})
case TodoActionType.UNDO_TODO:
return state.map(todo => {
if (todo.id === action.id) {
return { ...todo, complete: false }
} else {
return todo
}
})
case TodoActionType.ADD_TODO:
return state.concat({
task: action.task,
id: uuid(),
complete: false,
})
default:
return state
}
}
//
// App
//
export type AppState = {
todos: TodosState
filter: FilterType
}
export const appStateInit: AppState = {
todos: todosInit,
filter: FilterType.ALL,
}
export const appStateEmpty: AppState = {
todos: [],
filter: FilterType.ALL,
}
export type AppAction = FilterAction | TodoAction
export function appReducer(state: AppState, action: AppAction): AppState {
return {
todos: isOfTodoActionTypes(action) ? todoReducer(state.todos, action as TodoAction) : state.todos,
filter: isOfFilterActionTypes(action) ? filterReducer(state.filter, action as FilterAction) : state.filter,
}
}
PureScript
data Action
= Increment Int
| Decrement Int
| Reset
reducer :: Action -> Int -> Int
reducer (Increment x) state = state + x
reducer (Decrement x) state = state - x
reducer Reset _ = 0
result :: Int
result = reducer (Decrement 5) 5
🤣 🤣 🤣
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Very nice, thanks for sharing!
For our project, I put all actions into an object and added a (project-wide) type
ActionType
, which unifies all the return types. What's more, the action object also serves nicely as input for react-redux'bindActionCreators
function.