Cover image for Redux meets hooks for non-redux users: a small concrete example with reactive-react-redux

Redux meets hooks for non-redux users: a small concrete example with reactive-react-redux

dai_shi profile image Daishi Kato Originally published at blog.axlight.com ・4 min read
The Todo List example from Redux


If you had already used Redux and loved it, you might not understand why people try using React context and hooks to replace Redux (a.k.a no Redux hype). For those who would think Redux DevTools Extension and middleware are nice to have, Redux and context + hooks are actually two options. Context + hooks is just fine to share state among components, however if apps get bigger, they are likely to require Redux or other similar solutions; otherwise they end up having many contexts that can't be handled very easily. (I'd admit this is hypothetical and we would be able to find better solutions in the future.)

I've been developing a library called "reactive-react-redux" and although it's based on Redux, it's different.


Its API is very straightforward, and thanks to Proxy it's optimized for performance. With the hope that this library would pull back people who seek Redux alternatives with context + hooks, I created example code. It's the famous Todo List example from Redux.


The rest of this post shows example code rewritten with reactive-react-redux.

Types and reducers

The example is written in TypeScript. If you are not familiar with TypeScript, try ignoring State, Action and *Type.

The following is the type definitions for State and Action.


export type VisibilityFilterType =
  | 'SHOW_ALL'

export type TodoType = {
  id: number;
  text: string;
  completed: boolean;

export type State = {
  todos: TodoType[];
  visibilityFilter: VisibilityFilterType;

export type Action =
  | { type: 'ADD_TODO'; id: number; text: string }
  | { type: 'SET_VISIBILITY_FILTER'; filter: VisibilityFilterType }
  | { type: 'TOGGLE_TODO'; id: number };

The reducers are almost identical to the original example as follows.


import { combineReducers } from 'redux';

import todos from './todos';
import visibilityFilter from './visibilityFilter';

export default combineReducers({


import { TodoType, Action } from '../types';

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

export default todos;


import { Action, VisibilityFilterType } from '../types';

const visibilityFilter = (
  state: VisibilityFilterType = 'SHOW_ALL',
  action: Action,
): VisibilityFilterType => {
  switch (action.type) {
      return action.filter;
      return state;

export default visibilityFilter;

Action creators

There could be several ways to dispatch actions. I would create hooks for each actions. Note that we still explore better practices.


import { useCallback } from 'react';
import { useReduxDispatch } from 'reactive-react-redux';

import { Action, VisibilityFilterType } from '../types';

let nextTodoId = 0;

export const useAddTodo = () => {
  const dispatch = useReduxDispatch<Action>();
  return useCallback((text: string) => {
      type: 'ADD_TODO',
      id: nextTodoId++,
  }, [dispatch]);

export const useSetVisibilityFilter = () => {
  const dispatch = useReduxDispatch<Action>();
  return useCallback((filter: VisibilityFilterType) => {
  }, [dispatch]);

export const useToggleTodo = () => {
  const dispatch = useReduxDispatch<Action>();
  return useCallback((id: number) => {
      type: 'TOGGLE_TODO',
  }, [dispatch]);

They are not really action creators, but hooks that return action dispatchers.


We don't distinguish presentational components from container components. It could be still open for discussion about how to structure components, but for this example, components are just flat.


App is also identical to the original example.

import * as React from 'react';

import Footer from './Footer';
import AddTodo from './AddTodo';
import VisibleTodoList from './VisibleTodoList';

const App: React.FC = () => (
    <AddTodo />
    <VisibleTodoList />
    <Footer />

export default App;


There are small modifications but not major.

import * as React from 'react';

type Props = {
  onClick: (e: React.MouseEvent) => void;
  completed: boolean;
  text: string;

const Todo: React.FC<Props> = ({ onClick, completed, text }) => (
      textDecoration: completed ? 'line-through' : 'none',
      cursor: 'pointer',

export default Todo;


We don't have mapStateToProps or selectors. getVisibleTodos is simply called in render.

import * as React from 'react';
import { useReduxState } from 'reactive-react-redux';

import { TodoType, State, VisibilityFilterType } from '../types';
import { useToggleTodo } from '../actions';
import Todo from './Todo';

const getVisibleTodos = (todos: TodoType[], filter: VisibilityFilterType) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos;
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed);
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed);
      throw new Error(`Unknown filter: ${filter}`);

const VisibleTodoList: React.FC = () => {
  const state = useReduxState<State>();
  const visibleTodos = getVisibleTodos(state.todos, state.visibilityFilter);
  const toggleTodo = useToggleTodo();
  return (
      {visibleTodos.map(todo => (
        <Todo key={todo.id} {...todo} onClick={() => toggleTodo(todo.id)} />

export default VisibleTodoList;


Again, as useReduxState return the entire state, it simply uses its property to evaluate active.

import * as React from 'react';
import { useReduxState } from 'reactive-react-redux';

import { useSetVisibilityFilter } from '../actions';
import { State, VisibilityFilterType } from '../types';

type Props = {
  filter: VisibilityFilterType;

const FilterLink: React.FC<Props> = ({ filter, children }) => {
  const state = useReduxState<State>();
  const active = filter === state.visibilityFilter;
  const setVisibilityFilter = useSetVisibilityFilter();
  return (
      onClick={() => setVisibilityFilter(filter)}
        marginLeft: '4px',

export default FilterLink;


Because we rely on type checking, it's OK to pass strings to filter prop to FilterLink.

import * as React from 'react';

import FilterLink from './FilterLink';

const Footer: React.FC = () => (
    <span>Show: </span>
    <FilterLink filter="SHOW_ALL">All</FilterLink>
    <FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
    <FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>

export default Footer;


This is a bit modified from the original example to use controlled form with useState.

import * as React from 'react';
import { useState } from 'react';

import { useAddTodo } from '../actions';

const AddTodo = () => {
  const [text, setText] = useState('');
  const addTodo = useAddTodo();
  return (
        onSubmit={(e) => {
          if (!text.trim()) {
        <input value={text} onChange={e => setText(e.target.value)} />
        <button type="submit">Add Todo</button>

export default AddTodo;

Online demo

Please visit the codesandbox and run the example in your browser.

The source code can also be found here.

For more information

I didn't explain internal details about reactive-react-redux in this post. Please visit the GitHub repo to see more information which includes the list of previous blog posts.

Originally published at https://blog.axlight.com on June 3, 2019.

Posted on by:

dai_shi profile

Daishi Kato


A freelance programmer. I’m interested in working remotely with people abroad: https://contact.axlight.com https://github.com/dai-shi


markdown guide