How Does redux custom reducer work with Thunk - reactjs

I have some experience working with React and Redux
Frankly, Till now, i have been usually just copying-pasting the code without comprehending what is happening.
I was thinking about how reducer works (while trying Vuex) and that lead me to following question
import {
MEETUP_GROUP_DATA_SUCCESS,
MEETUP_GROUP_DATA_LOADING,
MEETUP_GROUP_DATA_ERROR,
GOOGLE_PROFILE_LOGOUT
} from "./../config/type.js"
const intialState = {
meetupProfileData: null,
meetupProfileLoading: null,
meetupProfileError: null,
}
export default function (state = intialState, action) {
switch (action.type) {
case MEETUP_GROUP_DATA_LOADING:
return {
...state,
meetupProfileLoading: true
}
case MEETUP_GROUP_DATA_SUCCESS:
return {
...state,
meetupProfileLoading: false,
meetupProfileError: false,
meetupProfileData: action.payload,
}
case MEETUP_GROUP_DATA_ERROR:
return {
...state,
meetupProfileLoading: false,
meetupProfileError: action.payload,
}
case GOOGLE_PROFILE_LOGOUT:
return {
...state,
meetupProfileLoading: null,
meetupProfileError: null,
meetupProfileData: null,
}
default:
return state
}
}
Here notice our const intitalState
Now, Suppose an action is dispatched. This will make redux call this function
export default function (state = intialState, action) {
Here our state is equal to initialState. So every-time we dispatch an action our state should be equal to intialState? since we are saying state = intialState
So what is the use of ...state here? If you are going to answer by saying that it makes copy of previous state then please mention how would our state have copy of previous state because every time an action is being dispatched our state is being equal to initial state and our initial state have all the parameters as null

The parameter state = initialState means that state defaults to initialState, if it was not provided. Redux will call your reducers once in the beginning to "initialize" your state, meaning it will call your reducer with null or undefined. Since you're setting the state to initialState in this case and returning that, Redux will receive the initial state you've set up.
All subsequent calls will use the new state - so when your reducer receives an action that will change something, you need to return the whole state again with the relevant updates.
Returning { ...state, somethingToUpdate: "foo" } means you're essentially copying the state variable into a new object, overwriting the somethingToUpdate key with "foo".
Basically, Redux only calls your reducers and if your reducer receives an action it does not care about, it needs to return the current state. If you happen to return nothing (maybe forgot to add the default: return state in the switch), the state gets undefined again and will reset to the initialState due to the default parameter provided in the function signature (state = initialState).

This statement
export default function (state = intialState, action) { }
says initialize it with intialState for the first time.
...state
Is like copy state and after that we are changing values while returning state/object.

Related

Confusion on how to properly use reducer in Redux

I was learning Redux and wanted to properly understand how reducer works. If you look at the reducer called titleReducer:
const initialState={
titleColor: null,
}
const titleReducer = (state = initialState, action) => {
switch (action.type) {
case TURN_TITLE_GREEN:
return {
...state,
titleColor: 'green'
}
case UPDATE_TITLE:
return {
...state,
title: action.payload
}
default: return state;
}
}
So, I wanted to ask some questions on the above code. Firstly, imagine that there are three reducers: reducer1. reducer2 and titleReducer(I know silly example but bear with me). The question is why even if state of titleReducer has only one property "titleColor", we need to create new make a copy of state by using ...state. What if we just use:
{
title: action.payload
}
The second question is ok every time titleReducer get "TURN_TITLE_GREEN" action, the reducer takes the previous state and creates a new copy and sends back to a component. The question is "where is the previous state is taken from? I mean, is it true that when titleReducer receives the action of TURN_TITLE_GREEN, it creates new copy and at the same time puts 'titleColor: 'green' into state which will serve as previous state next time. Is that true?"

In a Redux reducer, if state is not defined, can you immediately return the initial state?

I have seen 3 forms of reducers:
// Form 1
function reducer(state, action) {
if (state === undefined)
return state = [];
// handle action.type
// ...
}
// Form 2
function reducer(state, action) {
if (state === undefined)
state = [];
// handle action.type
// ...
}
// Form 3
function reducer(state = [], action) {
// handle action.type
// ...
}
Are they all the same? Form 1 and Form 2 differ by Form 1 immediately returning the initial state without looking at and taking care of action.type at all.
And I think Form 2 and Form 3 are exactly the same, using default parameter value.
Can any claim be substantiated by any official docs or specs? It think it means, the very first time a reducer is called, action.type won't be anything meaningful.
In a Redux reducer, if state is not defined, can you immediately return the initial state?
yes, we can.
But for this, you don't need to check for undefined or any other non-empty check.
switch default statement will handled it very smoothly.
function reducer(state, action) {
switch(action.type){
//rest case
default:
return state;
}
}
// Form 2
function reducer(state, action) {
switch(action.type){
//rest case
default:
return state;
}
}
Redux initializes it dispatches a "dummy" action to fill the state. Please check this docs
Reducers are not allowed to return undefined under any condition, It can return null if necessary
Technically all three are same, as first time the reducers will called with dummy, state is no longer undefined at Form 1. On subsequent call, it will be called with meaningful action from your application code and as state = [] at that time, it will check the action.type
You may simply use: createReducer from redux-starter-kit
Which is also been used in this demo from microsoft
A utility function that allows defining a reducer as a mapping from action type to case reducer functions that handle these action types. The reducer's initial state is passed as the first argument.
The body of every case reducer is implicitly wrapped with a call to produce() from the immer library. This means that rather than returning a new state object, you can also mutate the passed-in state object directly; these mutations will then be automatically and efficiently translated into copies, giving you both convenience and immutability.
#param initialState — The initial state to be returned by the reducer.
#param actionsMap — A mapping from action types to action-type-specific case redeucers.
Usage
export const LocalStorageReducer = createReducer<Store['localStorage']>(
new LocalStorage(), // <= where you define the init value of this state
{
storeLocalStorageInput(state, action) {
return state = {...state, [action.payload.item]: action.payload.value};
},
clearLocalStorageInput(state, action) {
return state = new LocalStorage();
},
}
);
export const reducer = combineReducers({
localStorage: LocalStorageReducer,
...
type of createReducer
(alias) createReducer<LocalStorage, CaseReducers<LocalStorage, any>>(initialState: LocalStorage, actionsMap: CaseReducers<LocalStorage, any>): Reducer<LocalStorage, AnyAction>
import createReducer
sample of state
export class LocalStorage {
/**
* Editing plan id in planner pages
*/
planId: string | undefined;
/***
* Touched id list
*/
touchedIdList: Array<string>;
constructor() {
this.planId = undefined;
this.touchedIdList = Array<string>();
}
}
There are already developed methods to do those things by libs, no need to manually do it again in most situation.

Does state reinitialize to initial state everytime an action is dispatched in react using redux?

For e.g. in the code below (NOTE: nothing particular in this code, just serves as an example):
const INITIAL_STATE = {
hidden: true,
cartItems: []
};
const cartReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CartActionTypes.TOGGLE_CART_HIDDEN:
return {
...state,
hidden: !state.hidden
};
case CartActionTypes.ADD_ITEM:
return {
...state,
cartItems: addItemToCart(state.cartItems, action.payload)
};
case CartActionTypes.REMOVE_ITEM:
return {
...state,
cartItems: removeItemFromCart(state.cartItems, action.payload)
};
case CartActionTypes.CLEAR_ITEM_FROM_CART:
return {
...state,
cartItems: state.cartItems.filter(
cartItem => cartItem.id !== action.payload.id
)
};
default:
return state;
}
};
Q1: Does state revert back to the INITIAL_STATE i.e.,
{
hidden: true,
cartItems: []
};
everytime an action is dispatched to this reducer?
Q2: So the state in the combinereducer would also reinitialize to initial state , say default state is returned in the reducer? or to put it another way:
Q3: the state in the combinereducer corresponding to the particular reducer keeps getting reinitialized/set to the state defined by the most recently dispatched action , for e.g. in the code above if CartActionTypes.ADD_ITEM is the most recently dispatched action to the reducer
case CartActionTypes.ADD_ITEM:
return {
...state,
cartItems: addItemToCart(state.cartItems, action.payload)
};
then the state in combinereducer would be (based on the above case):
{
hidden: true,
cartItems: addItemToCart(state.cartItems, action.payload)
};
and hidden would be reintialized to true regardless of what its value might have been in state due to earlier actions dispatched? So basically the latest action dispatched decides which current state values will be altered regardless of previous actions dispatched and the state values not altered by the latest dispatch are reintialized to the initial state?
Q:If im right in my understanding then won't the various components in react that call the state values (e.g. using mapStatetoProps) only have access to the state values defined by the latest dispatch?
When you dispatch an action it will be sent to the reducer to compute the next state based on the CURRENT STATE and the ACTION dispatched.
Now your function cartReducer accepts 2 parameters, state and action. You are essentially telling the reducer function that if no state was provided then use INITIAL_STATE. So when your reducer function executes it will see if the value of state is undefined. If so then it will use INITIAL_STATE else it will use the provided state value, which is your current state.
You can check out more about default paramenters in JavaScript here. You can go over the redux core principles here

Redux action taking too long add one item to empty array - No Promise returned

I am using Redux to manage the state of my react app. I am creating an object, then passing this object to addTile function which is my action.
So my action.ts looks like this:
export function addTile(tile){
return {
type: "ADD_TILE",
payload: tile
}
}
My reducer.ts looks like this:
const reducer = (state = {
isPanelOpen: false,
isDiscardAllChangesOpen: false,
tiles: [],
tempTiles: [],
}, action) => {
switch (action.type) {
case "PANEL_VISIBILITY":
state = {
...state,
isPanelOpen: action.payload
};
break;
case "ADD_TILE":
state = {
...state,
tiles: [...state.tiles, action.payload]
}
break;
}
return state;
};
export default reducer;
However, if I try to use this in my component like this:
this.props.addTile(tile)
alert(this.props.tiles.length)
The length will be 0. However, the item is really added to the array, but at the time of the alert execution, the length was 0. From my reading on Redux docs, actions by default are async (or at least that's how I understand they are).
I even try to do this:
this.props.addTile(tile)
.then(response => { //some code})
Then I get cannot read property then of undefined.
Any ideas?
When you try to check the prop right after dispatching an action, React has not yet had a chance to re-render. It's not that it's "taking too long", it's that your own code is still executing. So, React has not re-rendered your component, and the prop value is still the same.
Your promise example would only work if addTile() was a thunk that returned a promise.

Accessing a reducer state from within another reducer

I have a reducer whereby I am retuning the appropriate state when an action is dispatched. Now I am calling an API at regular intervals so the result will trigger an action again and again. So what I want is that if the reducer state already has data then another reducer doesn't show the state as loading while the call is sent. It must maintain its loading state when receiving data the first time only. I hope I am able to explain it properly
Here are my code snippets
Loading state reducer
const loading = (state = false, action) => {
switch (action.type) {
case 'GET_AUDIT_DATA': // here I want to return true only when there is no data available
return true
case 'GET_AUDIT_DATA_RECEIVED':
return false
case 'GET_AUDIT_DATA_ERROR':
return false
default:
return state
}
}
Combining reducers
const allReducers = combineReducers({
auditData: AuditData,
auditLoading: AuditLoading,
modifiedOrders: ModifiedOrders
});
export default allReducers;
Reducer returning data on action triggered by superagent
const auditData = (state = [], action) => {
switch(action.type) {
case 'GET_AUDIT_DATA_RECEIVED':
console.log(action.data);
return action.data;
case 'GET_AUDIT_DATA_ERROR':
return action.err;
default :
return state;
}
}
export default auditData;
So initially the auditData doesn't contain any data, only after the first success call it returns the data. When this is called at the same time loading state reducer is called and it should return true in GET_AUDIT_DATA action only when the audit data reducer doesn't contain any data.
Also is returning just the current obtained data from auditData the right way to go or I should do it differently. Basically I want to overwrite the current data with the new one.
You can call getState() over a store to get the list of reducers and the current state inside the reducers.
Import the store into auditLoading (use store to get values. Don't mutate the store)
store.getState().auditLoading will give you the state of auditLoading reducer.
This approach is similar to the callback provided by redux-thunk. In which (dispatch, getState) => {} will be returned to the action.
The best way to proceed is to send to Loading state reducer an information to know if the other reducer already have data. To have at the end:
const loading = (state = false, action) => {
switch (action.type) {
case 'GET_AUDIT_DATA':
if(!action.dataAlreadyInitialized){
return true
}
case 'GET_AUDIT_DATA_RECEIVED':
return false
case 'GET_AUDIT_DATA_ERROR':
return false
default:
return state
}
}
You should have access from your action function to the application state and do:
dispatch({
type:'GET_AUDIT_DATA',
dataAlreadyInitialized: appState.auditData.length > 0
});
The accepted answer is fine (pass in the data length through the action) but can get laborious if it's a piece of information that is widely used. There is another solution that is sometimes preferable for something like 'current user' that might be used by every action.
According to the Redux FAQ https://redux.js.org/faq/reducers it is perfectly acceptable to add a third argument to the reducer function. I.e.:
Loading state reducer
const loading = (state = false, action, noData) => {
switch (action.type) {
case 'GET_AUDIT_DATA':
return noData
case 'GET_AUDIT_DATA_RECEIVED':
return false
case 'GET_AUDIT_DATA_ERROR':
return false
default:
return state
}
}
Combining reducers
Unfortunately it means we have to write code to combine the reducers, rather than use the combineReducers shortcut. But it's not too hard, you just call each reducer and create a new object if anything changed:
const allReducers = (state = null, action) => {
const auditData = AuditData(state?.auditData, action);
const auditLoading = AuditLoading(state?.auditLoading, action, !state?.auditData?.length);
const modifiedOrders = ModifiedOrders(state?.modifiedOrders, action);
return (auditData !== state?.auditData ||
auditLoading !== state?.auditLoading ||
modifiedOrders !== state?.modifiedOrders) ?
{ auditData, auditLoading, modifiedOrders } : state;
});
export default allReducers;
Notice the third argument passed to the AuditLoading reducer. No change is required to the other reducers, or to the code that invokes the action. Which is nice!
import store from '../../../redux/store'
console.log(store.getState().loginReducer.accessToken)
Through this statement we will get state of accessToken

Resources