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!
- 793