Redux is dead,
long live Redux!

@soyguijarro

Web developer from Madrid

JavaScript and React enthusiast

Silly pet projects expert

Controversial tech speaker

Ramón
Guijarro

Why people want
Redux dead

Core concepts

Store

Actions

Reducers

Connect

Store

import { applyMiddleware, createStore, compose } from "redux";
import thunkMiddleware from "redux-thunk";

import rootReducer from "./reducers";

export default preloadedState => {
  const middlewares =
    process.env.NODE_ENV !== "production"
      ? [require("redux-immutable-state-invariant").default(), thunkMiddleware]
      : [thunkMiddleware];
  const composeEnhancers =
    typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      : compose;
  const composedEnhancers = composeEnhancers(applyMiddleware(...middlewares));

  return createStore(rootReducer, preloadedState, composedEnhancers);
};

Actions

export const addFavorite = id => ({
  type: ADD_FAVORITE,
  id
});

const favoriteIdsReducer = (state = [], action) => {
  switch (action.type) {
    case ADD_FAVORITE:
      …
};

export default favoriteIdsReducer;

Reducers

return state.includes(action.id) ?
  state :
  [...state, action.id];
return [
  ...state.slice(0, action.index),
  ...state.slice(action.index + 1)
];
return [
  ...state.slice(0, action.index),
  action.id,
  ...state.slice(action.index + 1)
];
return {
  ...state,
  [action.id]: {
    ...state[action.id],
    data: action.data
  }
};

Connection

import React, { useEffect } from "react";
import { connect } from "react-redux";

import { getMovieById, fetchMovie } from "../store/reducers";

const Movie = ({ id, movie, fetchMovie }) => {
  …
  useEffect(() => {
    fetchMovie(id));
  }, [id]);
  …
}

export default connect(
  (state, ownProps) => ({
    movie: getMovieById(state, ownProps.id),
  }),
  { fetchMovie }
)(Movie);

Let's roll our own
Redux killer

React's built-in Redux

useReducer hook

Stable context

import React, { createContext, useContext, useReducer } from "react";

const StoreContext = createContext();

const StoreProvider = ({ children }) => {
  const [state, dispatch] = useReducer(rootReducer, initialState);
  return (
    <StoreContext.Provider value={[state, dispatch]}>
      {children}
    </StoreContext.Provider>
  );
};

export const useStore = () => useContext(StoreContext);

export default StoreProvider;
import React from "react";

import StoreProvider from "./store";

const App = () => <StoreProvider>…</StoreProvider>;

export default App;
import React from "react";

import { useStore, ACTIONS } from "./store";

const Favorites = () => {
  const [state, dispatch] = useStore();
  const {favoriteIds} = state;
  …
  dispatch({type: ACTIONS.ADD_FAVORITE, payload: id});
  …
};

export default Favorites;

We just killed Redux!

Right?

Performance implications

All components rerender when the context value changes

Multiple contexts

Sort of like smaller,
self-contained stores

More performant

Makes more sense

State slices

import React, { createContext, useContext, useReducer } from "react";

const FavoritesContext = createContext();

const FavoritesProvider = ({ children }) => {
  const [favoriteIds, dispatch] = useReducer(favoriteIdsReducer, []);
  return (
    <FavoritesContext.Provider value={[favoriteIds, dispatch]}>
      {children}
    </FavoritesContext.Provider>
  );
};

export const useFavorites = () => useContext(FavoritesContext);

export default FavoritesProvider;
import React from "react";

import { useFavorites, ACTIONS } from "./context/favorites";

const Favorites = () => {
  const [favoriteIds, dispatch] = useStore();
  …
  dispatch({type: ACTIONS.ADD_FAVORITE, payload: id});
  …
};

export default Favorites;

Binded actions

…

export const useFavorites = () => {
  const [favoriteIds, dispatch] = useContext(FavoritesContext);
  
  const addFavorite = id => {
    dispatch({type: ACTIONS.ADD_FAVORITE, id});
  };
  …
  
  return [favoriteIds, {addFavorite, …}];
};

…
import React from "react";

import { useFavorites } from "./context/favorites";

const Favorites = () => {
  const [favoriteIds, {addFavorite}] = useStore();
  …
  addFavorite(id);
  …
};

export default Favorites;

Async actions

…

export const useMovies = () => {
  const [moviesById, dispatch] = useContext(MoviesContext);

  const fetchMovie = async id => {
    dispatch({ type: ACTIONS.REQUEST_MOVIE, id });
    const data = await api.fetchMovie(id);
    dispatch({ type: ACTIONS.RECEIVE_MOVIE, id, data });
  };

  return [moviesById, { fetchMovie }];
};

…
…

export const useMovie = id => {
  const [moviesById, { fetchMovie }] = useMovies();

  return [
    movie: moviesById[id],
    { fetchMovie: () => fetchMovie(id) },
  ];
};

…
import React, { useEffect } from "react";

import { useMovie } from "../context/movies";

const Movie = ({ id }) => {
  const [movie, { fetchMovie }] = useMovie(id);
  …
  useEffect(() => {
    fetchMovie();
  }, []);
  …
};

export default Movie;

Are we sure about
killing Redux?

Huge ecosystem

Redux DevTools

Middleware

Other utils

Recent improvements

Redux Starter Kit

import { applyMiddleware, createStore, compose } from "redux";
import thunkMiddleware from "redux-thunk";

import rootReducer from "./reducers";

export default preloadedState => {
  const middlewares =
    process.env.NODE_ENV !== "production"
      ? [require("redux-immutable-state-invariant").default(), thunkMiddleware]
      : [thunkMiddleware];
  const composeEnhancers =
    typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      : compose;
  const composedEnhancers = composeEnhancers(applyMiddleware(...middlewares));

  return createStore(rootReducer, preloadedState, composedEnhancers);
};
import { configureStore } from "redux-starter-kit";

import rootReducer from "./reducers";

export default preloadedState =>
  configureStore({
    reducer: rootReducer,
    preloadedState
  });
const ADD_FAVORITE = "ADD_FAVORITE"

export const addFavorite = id => ({ type: ADD_FAVORITE, id });
…

const favoriteIdsReducer = (state = [], action) => {
  switch (action.type) {
    case ADD_FAVORITE:
      return state.includes(action.id) ?
        state :
        [...state, action.id];
    …
    default:
      return state;
  }
};

export default favoriteIdsReducer;
import { createAction, createReducer } from 'redux-starter-kit';

export const addFavorite = createAction("ADD_FAVORITE")
…

const favoriteIdsReducer = createReducer([], {
  [addFavorite]: (state, action) => {
    if (!state.includes(action.payload)) {
      state.push(action.payload);
    }
  },
  …
});

export default favoriteIdsReducer;

Hooks in React Redux

import React, { useEffect } from "react";
import { connect } from "react-redux";

import { getMovieById, fetchMovie } from "../store/reducers";

const Movie = ({ id, movie, fetchMovie }) => {
  …
  useEffect(() => {
    fetchMovie(id));
  }, [id]);
  …
}

export default connect(
  (state, ownProps) => ({
    movie: getMovieById(state, ownProps.id),
  }),
  { fetchMovie }
)(Movie);
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";

import { getMovieById, fetchMovie } from "../store/reducers";

const Movie = ({ id }) => {
  const movie = useSelector(state => getMovieById(state, id));
  const dispatch = useDispatch();
  …
  useEffect(() => {
    dispatch(fetchMovie(id));
  }, [id]);
  …
};

export default Movie;
import React, { useEffect } from "react";

import { useMovie } from "../context/movies";

const Movie = ({ id }) => {
  const [movie, { fetchMovie }] = useMovie(id);
  …
  useEffect(() => {
    fetchMovie();
  }, []);
  …
};

export default Movie;

Some final
conclusions

We don't need to choose

We don't need to choose

Consider what we lose

We don't need to choose

Consider what we lose

Take context into account

We don't need to choose

Consider what we lose

Take context into account

Redux is more than a library

@soyguijarro

Thank you!

Redux is dead, long live Redux!

By Ramón Guijarro

Redux is dead, long live Redux!

  • 660