Why does my state not change using immutableJS? - javascript

I'm trying to add immutableJS to Mern.io. When I try to remove a post from my list of post then set it back in my state the state doesn't update.
case ActionTypes.DELETE_POST :
const removeArray = state.get('posts')
.filter((post) => post._id !== action.post._id)
console.log(removeArray.length)
state.set('posts', removeArray)
console.log(state)
return state;
In this example if I have an array of 5 I should be able to filter it out then set "posts" again with the new array. What I don't understand is that I can remove the object from the array and the removeArray will be one less than state.posts. But when I console log state it's the same. What am I missing?

When you call state.set(...) it returns a new object. The original state is unchanged. I changed 3 lines in your snippet:
case ActionTypes.DELETE_POST :
const removeArray = state.get('posts')
.filter((post) => post._id !== action.post._id)
console.log(removeArray.length)
const newState = state.set('posts', removeArray) // assign to another variable
console.log(newState) // and log here
return newState; // and you'll probably want to return the new state

Related

How to fill a state with an array

I'm trying to add a list inside a state using the set method, but my state remains empty
App.js
// Starts the screts word game
const startGame = () => {
// pick word and pick category
const { word, category } = pickWordAndCategory();
// create array of letters
let wordLetters = word.split("");
wordLetters = wordLetters.map((l) => l.toLowerCase());
// Fill states
setPickedCategory(category);
setPickedWord(word);
setLettersList(wordLetters);
console.log('wordLetters', wordLetters);
console.log('lettersList', lettersList);
setGameState(stages[1].name);
};
const pickWordAndCategory = () => {
// pick a random category
const categories = Object.keys(words);
const category = categories[Math.floor(Math.random() * Object.keys(categories).length)];
console.log('category', category);
// pick a random word
const word = words[category][Math.floor(Math.random() * words[category].length)]
console.log(word);
return { word, category };
}
Here is the browser log
When looking at the log we find our state empty
When you use setLettersList(...), you are not actually changing lettersList variable itself. Notice how when you declare it, const [lettersList, setLettersList] = useState([]); letterList is actually constant, so that variable can't even be changed. Instead, it tells React to rerun the function component, this time giving lettersList the new value. Therefore, for the updated value, it will only come once const [lettersList, setLettersList] = useState([]); is rerun again from the entire function being rerun again.
If you want to monitor changes to lettersList, you can do:
useEffect(() => {
console.log(lettersList);
}, [lettersList]);
This will print lettersList every time it is updated.
States updates are asynchronous, so when you set a new state value and console.log() right after it, it's going to show the previous value, because the state hasn't been updated yet.
That's why your lettersList value show the old value of the state in the console (empty array).

React display not updating correctly

I'm updating an object within react's state which I use to display a list. The state updates correctly, however the display breaks.
This is the section of the code from inside my render function which produces the list.
this.state.shoppingItems ? this.state.shoppingItems.currentShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
Here is the code that produces the previous items list:
this.state.shoppingItems ? this.state.shoppingItems.previousShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
This is the method which removes the item from the current list and adds it to the previous list, where the issue occurs.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
let shoppingItems = this.state.shoppingItems;
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
Here is the list before I remove the item.
Here is the list after I remove the middle item:
Finally here is the console.log output which shows that the items have updated properly but the display hasn't updated:
I'm entirely new to react coming from angular so I have no idea if this is the correct way to do this or if there is a better way. But could somebody help me figure out why the display isn't updating?
The issue seemed to be the key on the item in the map. I replaced the index with the item's id from the database as below and now it renders properly.
return <ItemSummary key={task._id} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={task} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
Similar answer here:
Change to React State doesn't update display in functional component
The issue is the update for shoppingItems. You save a reference to the current state object, mutate it, and store it back in state. Spreading this.state.shoppingItems into a new object first will create a new object reference for react to pick up the change of.
React uses shallow object comparison of previous state and prop values to next state and prop values to compute what needs to be rerendered to the DOM and screen.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
const shoppingItems = {...this.state.shoppingItems};
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
I had a similar issue with my application in which I had to delete comments made.
<textarea disabled key={note._id} className="form-control">{note.note}</textarea>
But the issue got resolved when I added the Key attribute to the list item.

How can I update redux state in Reducer in ReactJS?

Please know that I am new to ReactJS with Redux.
I have list of passengers, and each passenger has list of flights. I would like to update the flight property, checkedIn with the action property isCheckedIn. How can I achieve that with in reducer?
reducer.js
export default function passengerReducer(
state = initialState.passengers,
action
) {
switch (action.type) {
case types.LOAD_PASSENGERS_SUCCESS:
return action.passengers;
case types.UPDATE_PASSENGER_SUCCESS:
console.log("action ", action.passengerData.passengerId);
console.log("state ", state);
return state
.filter(x => x.id == action.passengerData.passengerId)
.map(f => {
f.flights[0].checkedIn = action.passengerData.isCheckedIn
});
default:
return state;
}
}
The state contains array of objects. Each object also contains flights. At the moment, I am only focusing the first flight with in flights array.
The action contains isCheckedIn property. I would like to update checkedInproperty of the flights with isCheckedIn property from action.
So this is the piece of code in question I presume:
case types.UPDATE_PASSENGER_SUCCESS:
console.log("action ", action.passengerData.passengerId);
console.log("state ", state);
return state
.filter(x => x.id == action.passengerData.passengerId)
.map(f => {
f.flights[0].checkedIn = action.passengerData.isCheckedIn
});
You're (a) filtering the array only for the element you want to change, and (b) mapping that but not returning anything from your map function
Just (a) alone is bad -- you're going to change your entire state to only include the filtered items? I don't think that's what you intended. But then (b) means you're returning an array full of undefined
What you want to do, instead, is create a new array, var newArray = state.slice(0);
Then, find the index of the item you want to change the checked_in property of,
var index = newArray.findIndex(x => x.id == action.passengerData.passengerId);
var newPassenger = Object.assign({}, newArray[index]);
newPassenger.flights[0].checkedIn = action.passengerData.isCheckedIn;
newArray[index] = newPassenger;
return newArray;
So you've found the item you wanted to change, changed it, put it back in the array (this is the immutable way to do things, I think), and then returned the FULL array

How to change the property of an object inside an array propertly in react

this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(item){
item.expand = true;
this.setState({})
}
this.state.myArray.map((item) =>{
return <div onClick={()=>this.clickItem(item)}>{item.name}</div>
})
In React, i have a simple array of objects,
when i click on one of theses object, i want to change their prop and update the state, what is the proper way of doing this.
i feel like there could be a better way
You need to copy your state, update the copied state and the set the state.
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(key){
let items = this.state.myArray;
items[key].expand = true;
this.setState({items})
}
this.state.myArray.map((key, item) =>{
return <div onClick={()=>this.clickItem(key)}>{item.name}</div>
})
Okay, a couple of things.
You're mutating the state directly which is going to fail silently and you're also missing the key prop on your <div.
This is easily resolved though by using the data you have available to you. I don't know whether each name is unique but you can use that as your key. This helps React decide which DOM elements to actually update when state changes.
To update your item in state, you need a way to find it within the state originally, so if name is unique, you can use Array.prototype.find to update it.
clickItem(item) {
const targetIndex = this.state.items.find(stateItem => stateItem.name === item.name)
if (targetIndex === -1)
// Handle not finding the element
const target = this.state.items[targetIndex]
target.expand = !target.expand // Toggle instead of setting so double clicking works as expected.
this.setState({
items: this.state.items.splice(targetIndex, 1, target) // This replaces 1 item in the target array with the new one.
})
}
This will update state and re-render your app. The code is untested but it should work.

Why do I lose the current object state when 'this.props.actions.updateComment(event.target.value)' is called?

I trying trying to achieve the following: There is a textfield and once a user enters in a text, an object is created with the text assigned to a state property called 'commentText' which is located inside the 'comments' array which is inside the object (todo[0]) of 'todos' array. 'commentInput' is just a temporary storage for the input entered in the textfield, to be assigned to the 'commentText' of 'todo[0]' object's 'comments' array.
I retrieve the current state object via following:
const mapStateToProps=(state, ownProps)=>({
todo:state.todos.filter(todo=>todo.id==ownProps.params.id)
});
and dispatch and actions via:
function mapDispatchToProps(dispatch){
return{
actions: bindActionCreators(actions, dispatch)
}
So the retrieved object 'todo' has an array property named comments. I have a text field that has:
onChange={this.handleCommentChange.bind(this)}
which does:
handleCommentChange(event){
this.props.actions.updateComment(event.target.value)
}
Before handleCommentChange is called, the object 'todo[0]' is first fetched correctly:
But as soon as a text is inputted into the text field, onChange={this.handleCommentChange.bind(this)} is called and all of a sudden, 'todo[0]' state is all lost (as shown in the 'next state' log):
What may be the issue? Tried solving it for hours and hours but still stuck. Any guidance or insight would be greatly appreciated. Thank you in advance.
EDIT **:
{
this.props.newCommentsArray.map((comment) => {
return <Comment key={comment.id} comment={comment} actions={this.props.actions}/>
})
}
EDIT 2 **
case 'ADD_COMMENT':
return todos.map(function(todo){
//Find the current object to apply the action to
if(todo.id === action.id){
//Create a new array, with newly assigned object
return var newComments = todo.comments.concat({
id: action.id,
commentTxt: action.commentTxt
})
}
//Otherwise return the original array
return todo.comments
})
I would suspect that your reducer is not correctly updating the todo entry. It's probably replacing the contents of the entry entirely, rather than merging the incoming value in in some fashion.
edit:
Yup, after seeing your full code, your reducer is very much at fault. Here's the current code:
case 'ADD_COMMENT':
return todos.map(function(todo){
if(todo.id === action.id){
return todo.comments = [{
id: action.id,
commentTxt: action.commentTxt
}, ...todo.comments]
}
})
map() should be returning one item for every item in the array. Instead, you're only returning something if the ID matches, and even then, you're actually assigning to todo.comments (causing direct mutation) and returning the result of that statement (which might be undefined?).
You need something like this instead (which could be written shorter, but I've deliberately written it out long-form to clarify what's happening):
case 'ADD_COMMENT':
return todos.map(function(todo) {
if(todo.id !== action.id) {
// if the ID doesn't match, just return the existing objecct
return todo;
}
// Otherwise, we need to return an updated value:
// Create a new comments array with the new comment at the end. concat() will
// You could also do something like [newComment].concat(todo.comments) to produce
// a new array with the new comment first depending on how you want it ordered.
var newComments = todo.comments.concat({
id : action.id,
commentTxt : action.commentTxt
});
// Create a new todo object that is a copy of the original,
// but with a new value in the "comments" field
var newTodo = Object.assign({}, todo, {comments : newComments});
// Now return that instead
return newTodo;
});

Categories