Redux manages state of the web app in a single big container called Store.
store takes reducer & action as parameter
Reducer
- reducer is a function that takes state & returns state
const reducer = (state = 5) => {
return state;
}
const store = Redux.createStore(reducer);
//fetch state from store
const currentState = store.getState();
Action
action is an object that represents occurrence of an event
action can contain data as property associated/passed with it (optional)
action must have a type property that defines type of action
📜 Think of Redux actions as messengers that deliver information about events happening in your app to the Redux store. The store then conducts the business of updating state based on the action that occurred.
Action Creator & Dispatch
Before passing action to store, we need to wrap action in actionCreator()
actionCreator is simply a function that returns an action
const action = {
type: 'LOGIN'
}
const actionCreator = (action) => action;
// -------- OR -------- //
const loginActionCreator() {
return {
type: 'LOGIN'
}
}
- To pass action to store, we need to pass it with dispatch() function
store.dispatch(actionCreator());
More on Reducer
Redux need to respond to an action.
It is done by a reducer. Reducer in Redux is responsible for state modification in response to an action.
reducer takes state & action as arguments and always returns a new state.
reducer is not supposed to do anything else. Just take state & action and return a new state.
📜 The reducer is simply a pure function that takes state and action, then returns new state.
In redux, state is read-only.
Reducer must always make a new state and not tamper with taken state.
📜 Redux does not enforce state immutability. It is coder’s responsibility.
const defaultState = 5; //any data for the app
const reducer = (state = defaultState, action) => {
if(action.type === 'LOGIN') {
return {login: true};
}
return state;
};
- Use switch statement to handle how to respond to multiple actions
const authReducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOGIN': return {authenticated: true}; // no break because return will take control away
case 'LOGOUT': return {authenticated: false};
default: return state;
}
};
- Assign action types as read-only constants, then reference these constants wherever they are used.
const LOGIN = 'LOGIN'
const LOGOUT = 'LOGOUT'
Subscribe
We can listen to actions on stores.
We can subscribe() to the store, so whenever any action is dispatched to the store, subscribe gets called.
this is to mimic the scenario:
- dispatch an action to change state
- state change in store by reducer
- subscribe to store’s changes
so basically, subscriber will take an operation as input & perform that operation
let count = 0;
const subscriberOperation = () => {count+=1};
store.subscribe(subscriberOperation);
Combine Reducers
Our app’s state may grow big and complex.
But we should not split the states in Redux. Redux says maintain all state in one Redux store object.
Therefore, Redux offers reducer composition.
make multiple reducer to handle different states in app.
combine the reducers with combineReducers() method
pass to the store with createStore() method.
We combine reducers in a root Reducer.
const rootReducer = Redux.combineReducers({
count: counterReducer,
auth: authReducer
});
const store = Redux.createStore(rootReducer);
Action data
till now our action had type property
we can also pass data along with action
const defaultState = 1; //any data for the app. e.g. think of it as login count into an account.
let loginCount = {};
const loginActionCreator(u_name, u_password) {
return {
type: 'LOGIN',
username: u_name,
password: u_password
};
};
const authReducer = (state = defaultState, action) => {
switch (action.type) {
case 'LOGIN': return {authenticated: true}; // no break because return will take control away
case 'LOGOUT': return {authenticated: false};
default: return state;
}
};
const loginCountReducer = (state = defaultState, action) => {
if(action.type === 'LOGIN') {
if(loginCount.find(action.username))
loginCount[action.username]++;
else
loginCount = {...loginCount, ...{action.username: 1}}; //spread operator in action.
// Spread objects and place content in the object
}
}
const rootReducer = Redux.combineReducers({
count: loginCountReducer,
auth: authReducer
});
const store = Redux.createStore(rootReducer);
store.dispatch(loginActionCreator('user_1', 'encrypted_password'));
Handling Asynchronous Actions/Requests
To handling async requests and performing async actions, redux gives Redux Thunk Middleware.
just add
Redux.applyMiddleware()
as additional parameter in thecreateStrore()
methodto handle async actions we need to make a separate function
this function will itself return a function (call it func2) having the async code
func2 will take dispatch as parameter
and run the async order of things
Redux Thunk takes care of all the rest. It is responsible to handle how the logic is run.
const REQUESTING_DATA = 'REQUESTING_DATA'
const RECEIVED_DATA = 'RECEIVED_DATA'
const requestingData = () => { return {type: REQUESTING_DATA} }
const receivedData = (data) => { return {type: RECEIVED_DATA, users: data.users} }
const handleAsync = () => {
return function(dispatch) {
// Dispatch request action here
store.dispatch(requestingData());
setTimeout(function() {
let data = {
users: ['Jeff', 'William', 'Alice']
}
// Dispatch received data action here
store.dispatch(receivedData(data)); // receiving data in timeout
}, 2500);
}
};
const defaultState = {
fetching: false,
users: []
};
const asyncDataReducer = (state = defaultState, action) => {
switch(action.type) {
case REQUESTING_DATA:
return {
fetching: true,
users: []
}
case RECEIVED_DATA:
return {
fetching: false,
users: action.users
}
default:
return state;
}
};
const store = Redux.createStore(
asyncDataReducer,
Redux.applyMiddleware(ReduxThunk.default);
);