redux-capi

redux-capi

    ›Examples

    Introduction

    • What it is
    • Usage
    • Rationale
    • Context

    API Specs

    • API Spec
    • Redactions
    • Selectors
    • Thunks
    • Mounting
    • Composing
    • Validation

    Reference

    • createAPI
    • API
    • api.mount()
    • api.mock()
    • Injection
    • Utilities

    Examples

    • Todo List

    Todo List

    The classic example for redux is the todo List. We will go through it and compare it with the standard redux implementation.

    Application Logic for State

    In the standard redux implementation you have state-related code in

    • The reducers, suplemented by actions and their related constants
    • In high order components which implement selectors

    Standard Redux

    Actions

    You have the actions:

    let nextTodoId = 0
    export const addTodo = text => ({
      type: 'ADD_TODO',
      id: nextTodoId++,
      text
    })
    
    export const setVisibilityFilter = filter => ({
      type: 'SET_VISIBILITY_FILTER',
      filter
    })
    
    export const toggleTodo = id => ({
      type: 'TOGGLE_TODO',
      id
    }) 
    

    Normally you have constants rather than strings for action types but in the interest of brevity the standard example omits that. It would also be preferable for the nextTodoId to be part of the state so that if the state is persisted you don't lose the high water mark.

    Reducers

    The root reducers combines reducers for the todo list and for setting the filter of what todos are to be displayed

    export default combineReducers({
      todos,
      visibilityFilter
    })
    

    You have the reducers for the todo list that supports adding and toggling

    const todos = (state = [], action) => {
      switch (action.type) {
        case 'ADD_TODO':
          return [
            ...state,
            {
              id: action.id,
              text: action.text,
              completed: false
            }
          ]
        case 'TOGGLE_TODO':
          return state.map(todo =>
            (todo.id === action.id)
              ? {...todo, completed: !todo.completed}
              : todo
          )
        default:
          return state
      }
    }
    

    and for setting visibility:

    const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
      switch (action.type) {
        case 'SET_VISIBILITY_FILTER':
          return action.filter
        default:
          return state
      }
    }
    

    Containers

    In standard redux there are two ways to bind redux to your visual components

    • In class-based components you have higher order components which use redux connect to bind the state to component properties. Usually some logic is present such as for selectors
    • In function-based components that use hooks the best practice is to have use functions that implement the hooks and return data and functions for manipulating the store. This is closer to the pattern one would use with redux-capi

    The standard implementation has a container for retreiving the todoList which would be

    const getVisibleTodos = (todos, filter) => {
      switch (filter) {
        case VisibilityFilters.SHOW_ALL:
          return todos
        case VisibilityFilters.SHOW_COMPLETED:
          return todos.filter(t => t.completed)
        case VisibilityFilters.SHOW_ACTIVE:
          return todos.filter(t => !t.completed)
        default:
          throw new Error('Unknown filter: ' + filter)
      }
    }
    
    const mapStateToProps = state => ({
      todos: getVisibleTodos(state.todos, state.visibilityFilter)
    })
    
    const mapDispatchToProps = dispatch => ({
      toggleTodo: id => dispatch(toggleTodo(id))
    })
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(TodoList)
    

    This contains a selector with the logic for selecting the appropriate todos and a binding to the toggleTodo function. The connect function takes the TodoList class as a parameter such that it becomes a higher order component for TodoList, called VisibleTodoList which is then used in place of TodoList.

    There is also a container for managing the filter buttons that determine which todos will be displayed:

    const mapStateToProps = (state, ownProps) => ({
      active: ownProps.filter === state.visibilityFilter
    })
    
    const mapDispatchToProps = (dispatch, ownProps) => ({
      onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
    })
    
    export default connect(
      mapStateToProps,
      mapDispatchToProps
    )(Link)
    

    It contains a selector to determine if the link should be active (because it represents the current filter criteria) and a binding to the action for setting the current filter. It becomes a higher order component of Link.

    Redux-capi

    ###API In redux-capi all of the above is reduced to an API specification

    export const todoAPI = createAPI({
        redactions: todoRedactions,
        selectors: todoSelectors,
    })
    
    

    ###Redactions These redactions replace the actions and individual reducers

    export const todoRedactions = {
    
        addTodo: (text) => ({
            nextId: {
                set: (state) => state.nextId +1
            },
            todos: {
                append: (state) => ({text, completed: false, id: state.nextId}),
            },
        }),
    
        toggleTodo: () => ({
            todos: {
                where: (state, item, ix, {id}) => item.id === id,
                assign: (state, todo) => ({completed: !todo.completed}),
            }
        }),
    
        setVisibilityFilter: (filter) => ({
            visibilityFilter: {
                set: () => filter,
            }
        }),
    }
    

    Note that in toggleTodo the id of the todo item is espected in the component context and is passed in when the API is used. ###Selectors The selectors are implemented as part of the API specification rather than in High Order Components.

    export const todoSelectors = {
    
        todos: (state) => state.todos,
    
        visibilityFilter: (state) => state.visibilityFilter,
    
        todo: [
            (select, {id, todos}) => select(id, todos),
            (id, todos) => todos.find(t => t.id === id)
        ],
    
        filteredTodos: [
            (select, {visibilityFilter, todos}) => select(visibilityFilter, todos),
            (visibilityFilter, todos) => {
                switch (visibilityFilter) {
                    case VisibilityFilters.SHOW_ALL:
                        return todos
                    case  VisibilityFilters.SHOW_COMPLETED:
                        return todos.filter(t => t.completed)
                    case VisibilityFilters.SHOW_ACTIVE:
                        return todos.filter(t => !t.completed)
                    default:
                        throw new Error('Unknown filter: ' + visibilityFilter)
                }
            }
        ],
     }
    

    Note that the todo selector selects an individual todo an expects the id to be in the component context. This example uses the longer form of memoized selectors.

    Component Structure

    There are no containers and the visual components simply use the parts of the API they need. The visual components are similar in structure but with one key difference. In the classic redux example the TodoList component iterates through the todos and passes in all of the properties of each todo list item as well as functions it can use for toggling them.

    const TodoList = ({ todos, toggleTodo }) => (
      <ul>
        {todos.map(todo =>
          <Todo
            key={todo.id}
            {...todo}
            onClick={() => toggleTodo(todo.id)}
          />
        )}
      </ul>
    )
    

    In the redux-capi version only the id is passed to the Todo child

    const TodoList = () => {
      const { filteredTodos } = todoAPI();
      return (
        <ul>
          {filteredTodos.map(todo =>
            <Todo
              key={todo.id} id={todo.id}
            />
          )}
        </ul>
      )
    }
    

    The todo item is then responsible for handling all individual todo concerns interacting directly with the API.

    const Todo = ({ id }) => {
        const {toggleTodo, todo } = todoAPI({id: id});
        return (
          <li
            onClick={toggleTodo}
            style={{
              textDecoration: todo.completed ? 'line-through' : 'none'
            }}
          >
            {todo.text}
          </li>
        )
    }
    

    The complete source is in the todos sub-project in https://github.com/selsamman/redux-capi-examples

    ← Utilities
    • Standard Redux
      • Actions
      • Reducers
      • Containers
    • Redux-capi
      • Component Structure
    redux-capi
    Docs
    Getting Started (or other categories)Guides (or other categories)API Reference (or other categories)
    Community
    User ShowcaseStack OverflowProject ChatTwitter
    More
    BlogGitHubStar
    Facebook Open Source
    Copyright © 2020 Sam Elsamman