Skip to content

Instantly share code, notes, and snippets.

@schettino
Created March 30, 2019 20:23
Show Gist options
  • Save schettino/c8bf5062ef99993ce32514807ffae849 to your computer and use it in GitHub Desktop.
Save schettino/c8bf5062ef99993ce32514807ffae849 to your computer and use it in GitHub Desktop.
Better Reducers with React and Typescript 3.4
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)
}
@rbuetzer
Copy link

rbuetzer commented May 3, 2019

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.

const actions = {
    updateName: (name: string) => {
        return <const>{
            type: 'UPDATE_NAME',
            name,
        };
    },
    addPoints: (points: number) => {
        return <const>{
            type: 'ADD_POINTS',
            points,
        };
    },
    setLikesGames: (value: boolean) => {
        return <const>{
            type: 'SET_LIKES_GAMES',
            value,
        };
    },
};

// Union of all action's return types
export type ActionType<TActions extends { [key: string]: (...args: any) => any }> = ReturnType<
    TActions[keyof TActions]
>;

type Action = ActionType<typeof actions>;

// react-redux specific stuff

const mapStateToProps = (state: State) => state;
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators(actions, dispatch);

/**
 * react-redux connector
 * const MyComponent = connectGameState(MyInnerComponent)
 */
export const connectGameState = connect(
    mapStateToProps,
    mapDispatchToProps
);

/**
 * Property type for MyInnerComponent:
 * const MyInnerComponent = (props: GameStateProps) => { ... }
 */
export type GameStateProps = ReturnType<typeof mapStateToProps> &
    ReturnType<typeof mapDispatchToProps>;

@zeromancer
Copy link

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,
  }
}

@Ja-rek
Copy link

Ja-rek commented Apr 14, 2020

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