JS Framework for managing the frontend state of a web application.
Redux
Visual of how an app without React manages it’s data.
Data stored in a sep. location - global data
.
The Anatomy of Redux
Store
Action
Reducers
Where did Redux come from?
A Single Source of Truth
: state is stored in a POJOState is Read Only
: State is immutable, modified by dispatching actions.Changes are Made with Pure Functions
: Reducers that receive the actions and return updated state are pure functions of the old state and action.When is it appropriate to use Redux?
When doing a project with simpler global state requirements, it may be better to choose React’s Context API over Redux.
Redux offers more flexibility and support for middleware along with richer developer tools.
Vocabulary
State
Store
getState
, dispatch(action)
, and subscribe(listener)
Actions
type
property.Pure Functions
Reducer
action
and current state
Middleware
Time Traveling Dev Tools
Thunks
What is Flux?
Unidirectional Data Flow - offers more predictability.
Actions
: Begins the data flow of data, simple object that contains a type; type indicates the type of change to be performed.
Dispatcher
: Mechanism for distributing actions to the store.
Store
: The entire state of the application, responsible for updating the state of your app.
View
: Unit of code that’s responsible for rendering the user interface. Used to re-render the application when actions and changes occur.
Redux
Single Source of Truth
State is Read-Only
Only Pure Functions Change State
Three methods
:
getState()
: Returns the store’s current state.dispatch(action)
: Passes an action into the store’s reducer to tell it what info to update.subscribe(callback)
: Registers a callback to be triggered whenever the store updates.Updating the Store
store.dispatch(action);
// Add Orange Action
const addOrange = {
type: "ADD_FRUIT",
fruit: "orange",
};
// Reducer for Orange Action
const fruitReducer = (state = [], action) => {
switch (action.type) {
case "ADD_FRUIT":
return [...state, action.fruit];
default:
return state;
}
};
// Run the Dispatch
console.log(store.getState()); // []
store.dispatch(addOrange);
console.log(store.getState()); // [ 'orange' ]
Subscribing to the store
Subscribers
: callbacks that can be added to the store via subscribe().const display = () => {
console.log(store.getState());
};
const unsubscribeDisplay = store.subscribe(display);
store.dispatch(addOrange); // [ 'orange', 'orange' ]
// display will no longer be invoked after store.dispatch()
unsubscribeDisplay();
store.dispatch(addOrange); // no output
Reviewing a simple example
// app.js
const { createStore } = require("redux");
// Define the store's reducer.
const fruitReducer = (state = [], action) => {
switch (action.type) {
case "ADD_FRUIT":
return [...state, action.fruit];
default:
return state;
}
};
// Create the store.
const store = createStore(fruitReducer);
// Define an 'ADD_FRUIT' action for adding an orange to the store.
const addOrange = {
type: "ADD_FRUIT",
fruit: "orange",
};
// Log to the console the store's state before and after
// dispatching the 'ADD_FRUIT' action.
console.log(store.getState()); // []
store.dispatch(addOrange);
console.log(store.getState()); // [ 'orange' ]
// Define and register a callback to listen for store updates
// and console log the store's state.
const display = () => {
console.log(store.getState());
};
const unsubscribeDisplay = store.subscribe(display);
// Dispatch the 'ADD_FRUIT' action. This time the `display` callback
// will be called by the store when its state is updated.
store.dispatch(addOrange); // [ 'orange', 'orange' ]
// Unsubscribe the `display` callback to stop listening for store updates.
unsubscribeDisplay();
// Dispatch the 'ADD_FRUIT' action one more time
// to confirm that the `display` method won't be called
// when the store state is updated.
store.dispatch(addOrange); // no output
Reducer function receives the current state
and action
, updates the state appropriately based on the action.type
and returns the following state.
You can bundles different action types and ensuing logic by using a switch/case statement.
const fruitReducer = (state = [], action) => {
switch (action.type) {
case "ADD_FRUIT":
return [...state, action.fruit];
case "ADD_FRUITS":
return [...state, ...action.fruits];
case "SELL_FRUIT":
const index = state.indexOf(action.fruit);
if (index !== -1) {
// remove first instance of action.fruit
return [...state.slice(0, index), ...state.slice(index + 1)];
}
return state; // if action.fruit is not in state, return previous state
case "SELL_OUT":
return [];
default:
return state;
}
};
Reviewing how Array#slice works
const fruits = ["apple", "apple", "orange", "banana", "watermelon"];
// The index of the 'orange' element is 2.
const index = fruits.indexOf("orange");
// `...fruits.slice(0, index)` returns the array ['apple', 'apple']
// `...fruits.slice(index + 1)` returns the array ['banana', 'watermelon']
// The spread syntax combines the two array slices into the array
// ['apple', 'apple', 'banana', 'watermelon']
const newFruits = [...fruits.slice(0, index), ...fruits.slice(index + 1)];
Avoiding state mutations
GOOD
const goodReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT_COUNTER":
const nextState = Object.assign({}, state);
nextState.count++;
return nextState;
default:
return state;
}
};
BAD
const badReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT_COUNTER":
state.count++;
return state;
default:
return state;
}
};
Using action creators
const addOrange = {
type: "ADD_FRUIT",
fruit: "orange",
};
store.dispatch(addOrange);
console.log(store.getState()); // [ 'orange' ]
fruit is the payload key
and orange is the state data
Action Creators
: Functions created from extrapolating the creation of an action object.
store.dispatch(addFruit("apple"));
store.dispatch(addFruit("strawberry"));
store.dispatch(addFruit("lychee"));
console.log(store.getState()); // [ 'orange', 'apple', 'strawberry', 'lychee' ]
Preventing typos in action type string literals
const ADD_FRUIT = "ADD_FRUIT";
const ADD_FRUITS = "ADD_FRUITS";
const SELL_FRUIT = "SELL_FRUIT";
const SELL_OUT = "SELL_OUT";
const addFruit = (fruit) => ({
type: ADD_FRUIT,
fruit,
});
const addFruits = (fruits) => ({
type: ADD_FRUITS,
fruits,
});
const sellFruit = (fruit) => ({
type: SELL_FRUIT,
fruit,
});
const sellOut = () => ({
type: SELL_OUT,
});
Understanding the limitations of implicit return values
const addFruit = (fruit) => {
return {
type: "ADD_FRUIT",
fruit,
};
};
const addFruit = (fruit) => {
debugger;
return {
type: "ADD_FRUIT",
fruit,
};
};