I'm working on a React/Redux application and I got the following problem:
I got a reducer like this:
export default (state = initialState, action) => {
switch (action.type) {
case action.type === FILTER.LIST_TYPES:
return Object.keys(state);
default:
return state;
}
};
This is the action types object:
export const FILTER = {
LIST_TYPES: "FILTER/LIST_TYPES"
};
export const listFilterTypes = () => ({
type: FILTER.LIST_TYPES
});
Now, when I dispatch this code, I receive false on the equality check like this (because they are both a string, but not the same type):
console.log(action.type === FILTER.LIST_TYPES);
I could change this for example to ==, but ESLINT does not like that and it can have some funky behaviour.. Is there a better approach to compare these object strings?
EDIT:
They are both the same value when I console.log them:
action.type: "FILTER/LIST_TYPES"
FILTER.LIST_TYPES: "FILTER/LIST_TYPES"
Related
I'm trying to push a new value in the store's state. It works fine the first time I click on the button "Add item", but the second time I got the following error: "state.basket.push is not a function". I configure the action to console log the state and got the following results:
1st click: {...}{basketItems: Array [ "44" ]}
2nd click: Object {basketItems: 0 }
Why the variable type is changing from array to an int?
Here is the code for the rendered component:
function Counter({ basketItems,additem }) {
return (
<div>
<button onClick={additem}>Add item</button>
</div>
);
}
const mapStateToProps = state => ({
basketItems: state.counterReducer.basketItems,
});
const mapDispatchToProps = dispatch => {
return {
additem: ()=>dispatch({type: actionType.ADDITEM, itemName:'Dummy text' }),
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
And the reducer looks like this:
import {ADDITEM} from "../actions/types";
const initialState = { basket: [], };
export default function reducer(state = initialState, action) {
switch (action.type) {
case ADDITEM:
console.log(state);
// let newBasket = state.basket.push('44');
return {
...state,
basket: state.basket.push('44')
};
default:
return state;
}
}
I'm copying the state before updating the basket to prevent weird behaviors.
There's two problems here:
state.basket.push() mutates the existing state.basket array, which is not allowed in Redux
It also returns the new size of the array, not an actual array
So, you're not doing a correct immutable update, and you're returning a value that is not an array.
A correct immutable update here would look like:
return {
...state,
basket: state.basket.concat("44")
}
Having said that, you should really be using our official Redux Toolkit package, which will let you drastically simplify your reducer logic and catch mistakes like this.
I am trying to insert an array questions into a state array at a certain index in my array, however it is not always getting the order correct. I am expecting something like this:
[[/*arr 0*/], [/*arr 1*/], [/*arr 2*/], [/*arr 3*/], ...]
But I keep getting something like this:
[[/*arr 0*/], [/*arr 2*/], [/*arr 1*/], [/*arr 3*/], ...]
I tried following this guide from the official Redux docs, but to no avail. My reducer is the following:
export const questions = (state = [], action) => {
switch (action.type){
case SET_QUESTIONS:
const {questions, index} = action.payload;
let newArray = state.slice()
newArray.splice(index, 0, questions);
return newArray
case RESET_QUESTIONS:
return [];
default:
return state;
}
};
What am I doing wrong?
EDIT:
I have been asked to show how the actions are called, so here is the snippet where the actions are called. This loops about 7 times or so, depending on the length necessary. These calls are asynchronous, but I don't think this should necessarily change how the reducer functions.
axios.post(`${process.env.REACT_APP_SERVER_ENDPOINT}/getQuestionnaireData`, data).then(res => {
store.dispatch(setQuestions(res.data, index));
resolve();
}).catch(err => {
store.dispatch(setError(true));
});
The dispatched action looks like this:
export const setQuestions = (questions, index) => ({
type: SET_QUESTIONS,
payload: {
questions,
index
}
})
EDIT 2:
Because there was no way around the way that the dispatch calls are made (can't force insertions to be in order), and unfortunately none of the responses I got were able to solve my problem, I opted for a different solution. I ended up changing my reducer to the following:
export const questions = (state = {}, action) => {
switch (action.type){
case SET_QUESTIONS:
const {questions, index} = action.payload;
//Retrieve the previously stored state
let newObj = {
...state,
}
//Create a new object at the step key if it doesn't exist
if (!newObj[index]) newObj[index] = {};
//Assign the value at the id key in the step object
newObj[index] = questions;
return newObj;
case RESET_QUESTIONS:
return {};
default:
return state;
}
};
From there, I just ended up using Lodash to iterate over the object like an array. This approach proved to be pretty reliable, so that's what I stuck with.
Thanks to everyone for their answers. I hope they work for someone else who might come across this problem later.
Actually, you are not using Spread operator so Use spread operator and you can read about spread operator from following this link
Try the following code
export const questions = (state = [], action) => {
switch (action.type){
case SET_QUESTIONS:
const {questions, index} = action.payload;
return [
...state.slice(0,index),
questions,
...state.slice(index)
]
case RESET_QUESTIONS:
return [];
default:
return state;
}
};
You need to take account of the occasions (including the first time SET_QUESTIONS is dispatched) when your state array has fewer items in it than the new index.
Bearing that in mind, I'd probably do something like this:
export const questions = (state = [], action) => {
switch (action.type) {
case SET_QUESTIONS:
const { questions, index } = action.payload
const stateCopy = [...state]
stateCopy[index] = payload
return stateCopy
case RESET_QUESTIONS:
return []
default:
return state
}
}
I have a company reducer that I'd want to contain an array of companies. Since each object is fairly large when user goes on /company/name I only want to fetch that one object from my API and add it to this array, so if user visits the same company page multiple times I won't have to call again the api.
My reducer:
const initialState = {
companies: []
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_COMPANIES:
return {
...state
};
case GET_COMPANY:
return {
...state,
companies: [action.payload, ...state.companies]
};
default:
return state;
}
};
Is there a way for me to update the state in reducer and just return 1 company? Like shown above this would return all companies in this state, though logically it does not make sense with 'GET_COMPANY'
My action:
export const getCompany = name => dispatch => {
axios.get("/companies/" + name).then(res =>
dispatch({
type: GET_COMPANY,
payload: res.data
})
);
};
You need some 'id' or 'name' or other property of the company object you want to retrieve,
and than you can use find method to do this:
action.payload.find(company=>company.id===id)
According to the MDN:
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
I have an object that trying too deeply clone it before mutate in Redux but the nested objects become empty after deep cloning it with lodash or json
const initial = {
infamy: {a: 1}
}
export const playerReducer = (state = initial, action) => {
switch (action.type) {
case SET_DATA:
console.log("III", state);
state = cloneDeep(state); //Why the neseted obj becomes empty?
console.log("JJJ", state);
break;
}
};
Edit:
looks look the issue was the condition i had for checking if the object was empty wasn't working so the empty data from the api was replacing the initial values but im wounding why the console.log was showing the post made mutation rather than pre made mutation
case SET_DATA:
console.log("III", state);
const nextState = cloneDeep(state);
console.log("JJJ", nextState); //why the log shows the change in line 10 made? shouldn't it log the values then change happen?
nextState.membershipId = action.payload.membershipId;
nextState.membershipType = action.payload.membershipType;
nextState.displayName = action.payload.displayName;
console.log(action.payload.gambitStats);
if (action.payload.gambitStats.allTime !== "undefined") { //the bug is here
nextState.gambitStats = cloneDeep(action.payload.gambitStats);
nextState.infamy = cloneDeep(action.payload.infamy);
}
return nextState;
You are checking for undefined as a string "undefined" instead of:
if (action.payload.gambitStats.allTime !== undefined) { ...
or just:
if (!!action.payload.gambitStats.allTime)
In principal I would say that the state would not be emptied by the use of cloneDeep alone.
In the other hand, I see that you are using a Redux pattern and you should not directly manipulate the state.
Instead you should return the next state and also return the current state by default.
const initial = {
infamy: {a: 1}
}
export const playerReducer = (state = initial, action) => {
switch (action.type) {
case SET_DATA:
const nextState = cloneDeep(state);
// Modify nextState according to the intent of your action
return nextState;
default:
return state;
}
};
Hope it helps. :)
Both updateUiActive and updateData return new copy of state.
I am using updateData(updateUiActive(state, action), action) to return a new version of state which contain first the properties updated with updateUiActive and after with updateData.
I would like to know if there is a more elegant and efficient way to do so.
Maybe using JS carry?
const updateUiActive = (state, action) => {
return dotProp.set(state, 'navigation.ui.active', action.payload)
}
const updateData = (state, action) => {
const updatedData = state.navigation.data.map((x) => {
x.isActive = x.id === action.payload
return x
})
return dotProp.set(state, 'navigation.data', updatedData)
}
function navigationReducer (state = initialState, action) {
switch (action.type) {
case types.SET_ACTIVE:
return updateData(updateUiActive(state, action), action)
default:
return state
}
}
first of all, redux relies heavily on the state being immutable. You should not modify it, but return a new modified copy of it.
One option is to do it yourself (the spread operator from ES is usually very helpful for this).
Another option is using immutability-helper. The syntax might be hard to get in the beginning, but for more complex state it helps you getting a better structure.