So, I've got a redux slice, where there are two reducers, setTodos and addTodo, the initial state is set to be empty, but soon updated using the reducer setTodos, the data of setTodos(dispatched from a component) is fetched by a network call, so it is an asynchronous task. The data is updated properly, but when I try adding a new todo, the addition is not reflected on the screen as it should, perhaps it is displayed only on reload because setTodos is executed again and the state is updated accordingly.
const myTodoSlice = createSlice({
name:'Todo',
initialState:{todos:[]},
reducers:{
addTodo:(state,action)=>{
state.todos.push(action.payload.todo)
},
setTodos:(state,action)=>{
//Approach 1 of setting the initial todo state.
state.todos = action.payload.arrayOfTodos
//Approach 2 of setting the initial todo state.
// state.todos.push(...action.payload.arrayOfTodos)
}
}
List rendering code:
const selector = useSelector((arg:RootStateOrAny)=>arg.todos.todos)
return(
<React.Fragment>
{selector?.map((item:RootStateOrAny) => <TodoItem key={Math.random()} title={item.todoTitle} desc={item.todoDescription} />)}
</React.Fragmnet>
)
Currently, I'm following approach 1, but by doing so, I'm unable to render new additions on the screen. When I tried using approach 2, I'm getting errors mostly related to re-render depth exceeded.
I'd want to know by following approach 1, why is the addition not rendered, and what changes can I make to get the desired result?
Thanks.
The issue is that React sees the same array reference (because the push is a mutation operation of the existing array instance) and ignores the rendering.
Instead of
addTodo:(state,action)=>{
state.todos.push(action.payload.todo)
},
Try like below which creates a new array instance with adding the new todo item (concat returns a new array with the new item without mutating the existing one).
addTodo:(state,action)=>{
state.todos = state.todos.concat([action.payload.todo])
},
Related
I am trying to update a nested state object (checkedObjects) in a react class component, to track when checkboxes are checked and unchecked. checkedObjects has the following structure:
checkedObjects: {
[assignmentName]: boolean,
}
verifyObjects is a local variable that checks if the new name property was actually received. When I console out the contents of these objects however, checkedObjects is empty, while the new property was added to verifyObjects (see screenshot below). Can anyone advise why the state variable checkedObjects is not updating immediately?
Screenshot:
Code Snippet:
this.state = {
checkedObjects: {},
};
incrementCount(totalCount, id, checked, assignmentName) {
console.log("increment: totalCount", totalCount, " ; id:", id, checked);
// If a checkbox is clicked with an assignment name store on the checkedObjects
if (assignmentName) {
let verifyObjects = { ...this.state.checkedObjects };
verifyObjects[assignmentName] = checked;
this.setState(prevState => {
let tempObj = {...prevState.checkedObjects}
tempObj[assignmentName] = checked;
return {checkedObjects: tempObj}
});
console.log("SelectedAssignmentsObj:", this.state.checkedObjects);
console.log("VerifiedObject:", verifyObjects);
} //if
}
State updates don't occur instantaneously. When we call the setState() function, it schedules a state update. Try console logging tempObj to see the value that is being put inside of this.state.checkedObjects.
In short, your code is working the way it should but you wont be able to see the state update right after calling this.setState() [because the state update is scheduled and didnt happen at that instant]. If you want to ensure that your state did update the way you wanted, can add a dummy button on the side that console logs the value of this.state.checkedObjects or you can use the chrome extension React Developer Tools to find out the values in the state object.
I have a state list that is called 'journal' and I want to add a state object that is called 'record' to the list after the user enters the data and set the state of the record.
Here's my states:
const [journal, setJournal] = useState([]);
const [record, setRecord] = useState({});
And here's the method that takes the data from user to set the record:
function AddRecord(debitAccount, debitValue, creditAccount, creditValue, description){
setRecord({date: new Date().getDate().toString(), debit: {[debitAccount]: debitValue},
credit: {[creditAccount]: creditValue}, description, id: new Date().getTime().toString()});
}
I'm using a useEffect to update the journal every time the record changes like this:
useEffect(()=>{
setJournal([...journal, record])
}, [record])
But it adds an empty object at the beginning of the array.
Can someone please tell me how to fix this, I'm still trying to figure my way around states in react, and they're just getting complicated
This is a misuse of useEffect. Effects should be used to react to, and tie together, things which happen outside the business logic of the component (i.e. prop changes, multiple concurrent fetch calls), or resubscribe listeners which are dependent on state values. Just move all the relevant code into AddRecord:
function AddRecord(debitAccount, debitValue, creditAccount, creditValue, description){
const newRecord = {
date: new Date().getDate().toString(),
debit: {
[debitAccount]: debitValue
},
credit: {
[creditAccount]: creditValue
},
description,
id: new Date().getTime().toString()
};
setRecord(newRecord);
setJournal([...journal, newRecord]);
}
useEffect will be called on component initialization try to use some other way maybe useCallback function
useEffect runs on mount, and each time record changes, that's why it runs when record is empty. Just add a condition:
Also do not use journal directly, it's not guarantee that it has the expected value. Use set state callback instead:
useEffect(() => {
if (record && record.id) {
setJournal(journals => ([...journals, record]))
}
}, [record])
I'm having a strange issue with state in my React app. I set initial state as an empty array and in the componentDidMount() method I retrieve some data from Firebase. I put the JSON objects into a new array and call setState(), passing it the new array.
The problem is that it doesn't seem to be updating state. From what I can tell:
Render() is being called after setState
My callback on setState() is being fired
When I console.log the array that I set the state to, it looks fine
The strangest thing, when I inspect state in the Chrome React Devtools, it shows empty but for some reason I can print the array to the console using $r.state.nameOfMyObject
If I change some other piece of state directly from the dev tools, the app immediately renders and finally displays the piece of data I've been struggling with all along.
I thought maybe there was some issue with updating the array; maybe the diffing algorithm didn't go deep enough to see that the data inside the array changed. To test this, I tried to set the initial state of the object in question to null, but then set off errors throughout the app that it was trying to call various array methods on null.
I've tried walking through the app, console logging each step, but I can't find the issue.
Snippet of initial state:
state = {
fullSchedule: [],
currentSet: [],
aCoupleOfOtherObjects,
}
componentDidMount():
componentDidMount() {
const activities = [];
const projectReference = firestoreDB.collection("project").doc("revision001").collection("activities");
projectReference.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
activities.push(doc.data());
});
});
console.log(activities);
this.setState({fullSchedule: activities});
this.setState({currentSet: activities}, () => {
console.log("State updated from DB");
});
console.log("called setstate");
}
I can't tell why the setState() method doesn't seem to be setting the state, any ideas?
Thanks!
projectReference.get() is asynchronous, and you are trying to set the state right after you call it, which won't work.
try setting the state inside then callback:
projectReference.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
activities.push(doc.data());
});
this.setState({fullSchedule: activities, currentSet: activities});
});
This should give you a better idea of what's going on.
I have an application which is using React, Redux, and Sagas. I have a reducer which has a state that is an array of objects. I want this reducer to handle a certain action by removing the first item of the array. I understand that state needs to be immutable and therefor I cannot simply call .shift.
Here is what I am trying to do now:
const flashcards = (state = [], action) => {
switch (action.type) {
case 'MAKE_FLASHCARD':
console.log(action.payload)
return action.payload;
case 'UPDATE_FLASHCARD_ARRAY':
return ({
...state.slice(1,state.length)
})
default:
return state;
}
};
export default flashcards;
The ...state.slice(1,state.length) works the first time UPDATE_FLASHCARD_ARRAY is called. However it stops working on future attempts. I discovered this is because state is not actually removing the first index with my slice method, rather it is setting the values of the first index equal to null.
Here is a stringify of the Redux stat to help illustrate this.
Prior to calling UPDATE_FLASHCARD_ARRAY :
[{"id":7,"account_id":1,"native_word":"pig","translation":"gris"},{"id":3,"account_id":1,"native_word":"cow","translation":"ku"},{"id":1,"account_id":1,"native_word":"cheese","translation":"ost"},{"id":2,"account_id":1,"native_word":"milk","translation":"melk"},{"id":8,"account_id":1,"native_word":"spider","translation":"ederkopp"}]
After calling UPDATE_FLASHCARD_ARRAY :
{"0":{"id":3,"account_id":1,"native_word":"cow","translation":"ku"},"1":{"id":1,"account_id":1,"native_word":"cheese","translation":"ost"},"2":{"id":2,"account_id":1,"native_word":"milk","translation":"melk"},"3":{"id":8,"account_id":1,"native_word":"spider","translation":"ederkopp"}}
The slice method is clearing returning a state different than the original. Could someone point out what I am doing wrong here? All I want to do is remove the first object from the state array every time UPDATE_FLASHCARD_ARRAY is dispatched.
.slice already returns a new array you don't need to spread it, you're also spreading it into an object that's why you see {"0":...:
case 'UPDATE_FLASHCARD_ARRAY':
return state.slice(1,state.length)
Have you tried to do return state.slice(1) instead return ({...state.slice(1,state.length)})? The latter creates an object, which has same keys as Array, but not actually as Array:
Array.isArray([1, 2, 3]) === true
Array.isArray({...[1, 2, 3]}) === false
({...[1, 2, 3]}).slice(1) -> TypeError
I am working on three properties of JSON object which returns boolean values.
updateChange=(value) =>{
//Making copy of existing json Object
let newState = JSON.parse(JSON.stringify(this.state.mainValue));
// chaning property of cellular over here
newState.cellular = value;
console.log(newState);
this.setState({mainValue:newState});
//I tried setState in many different ways. But Component is not changing state value
console.log(this.state.mainValue)
};
I found why this is happening. I am using getDerivedStateFromProps in this component to look at changes made by parent to update values. This is avoiding changes made to state of the child. So I have created a state called previous state to compare the previous props and present props from parent to render. This avoids component to refresh when ever it local state values changes.
static getDerivedStateFromProps(propsnow,state){
if(state.previous !== propsnow.detailSwitches){
return{
previous :propsnow.detailSwitches,
cellular: propsnow.detailSwitches.cellular,
wifi1: propsnow.detailSwitches.wifi1,
wifi2: propsnow.detailSwitches.wifi2
};
}
return null;
}
Any examples or better practices can be helpful. Thanks
You are ONLY setting the 'mainValue' variable of your state to the value of newstate, shouldn't you be updating the whole state?
this.setState({...newState})
You can try this
this.setState(prevState => ({
mainValue: {
...prevState.mainValue,
cellular: value
}}))
It is an immutable way to update state.