Does React state get saved before setting it with useState? - javascript

Sorry for the confusing question. First I will like to share my code:
const intialState = {
output: null,
bookIdToDelete: null
}
const [state,setState] = useState(intialState)
useEffect(() => {
getDataFromServer().then(data => {
console.log(data);
if (data != 2) {
// job gonna be here
const output = data.map(book => {
return (
<div key={book._id} className="col-md-3">
<div className="item">
<img className="bookimage" src={book.imgs[0]} alt="img"/>
<h3>
<Link to={"/admin/mybook/" + book._id}>{book.title}</Link>
</h3>
<h6>
<Link to={"/admin/mybook/" + book._id}>Edit</Link>
<button id={book._id} onClick={deleteBtnClick} className="btn btn-danger">Delete</button>
</h6>
</div>
</div>
)
})
setState({...state, output})
}
}).catch(error => {
console.log(error);
})
}, [])
const deleteBtnClick = (e) => {
let bookId = e.target.id;
console.log(state)
setState({
...state,
bookIdToDelete: bookId
})
}
console.log('showmodal',state);
return (
<>
{state.output}
</>
)
}
export default MyBooks
Basically what this does is that I obtain an array with some information and create a list using a map. I do this all inside the useEffect. Every item has a delete button that calls another function that just saves the id of the element that I want to delete inside the state. What is happening is that when I click on this delete button the state is always equal to the initial state and not with the one I updated in the useEffect. It seems that as I created the array of elements inside the useEffect before I set the state to the its new value the state that is used in the "deleteBtnClick" function is the old one, in this case the initial one....
So I solved this by just saving the information in the useEffect and then creating the list with the map before the return of the component... But I still have the doubt of why it was happening... so if you know why I will appreciate the explanation. Thanks!

in general I would organize my code like this:
const intialState = {
output: [],
bookIdToDelete: null
}
const [state,setState] = useState(intialState)
useEffect(() => {
getDataFromServer().then(data => {
console.log(data);
if (data != 2) {
// job gonna be here
const output = data
setState({...state, output})
}
}).catch(error => {
console.log(error);
})
}, [])
const deleteBtnClick = (e) => {
let bookId = e.target.id;
console.log(state)
setState({
...state,
bookIdToDelete: bookId
})
}
return (
<>
{state.output.map(book => {
return (
<div key={book._id} className="col-md-3">
<div className="item">
<img className="bookimage" src={book.imgs[0]} alt="img"/>
<h3>
<Link to={"/admin/mybook/" + book._id}>{book.title}</Link>
</h3>
<h6>
<Link to={"/admin/mybook/" + book._id}>Edit</Link>
<button id={book._id} onClick={deleteBtnClick} className="btn btn- danger">Delete</button>
</h6>
</div>
</div>
)
})}
</>
)
}
export default MyBooks

Related

Why does my toast notification not re-render in React?

I am trying to create my own "vanilla-React" toast notification and I did manage to make it work however I cannot wrap my head around why one of the solutions that I tried is still not working.
So here we go, onFormSubmit() I want to run the code to get the notification. I excluded a bunch of the code to enhance readability:
const [notifications, setNotifications] = useState<string[]>([]);
const onFormSubmit = (ev: FormEvent<HTMLFormElement>) => {
ev.preventDefault();
const newNotifications = notifications;
newNotifications.push("success");
console.log(newNotifications);
setNotifications(newNotifications);
};
return (
<>
{notifications.map((state, index) => {
console.log(index);
return (
<ToastNotification state={state} instance={index} key={index} />
);
})}
</>
</section>
);
Inside the Toast I have the following:
const ToastNotification = ({
state,
instance,
}:
{
state: string;
instance: number;
}) => {
const [showComponent, setShowComponent] = useState<boolean>(true);
const [notificationState, setNotificationState] = useState(
notificationStates.empty
);
console.log("here");
const doNotShow = () => {
setShowComponent(false);
};
useEffect(() => {
const keys = Object.keys(notificationStates);
const index = keys.findIndex((key) => state === key);
if (index !== -1) {
const prop = keys[index] as "danger" | "success";
setNotificationState(notificationStates[prop]);
}
console.log(state);
}, [state, instance]);
return (
<div className={`notification ${!showComponent && "display-none"}`}>
<div
className={`notification-content ${notificationState.notificationClass}`}
>
<p className="notification-content_text"> {notificationState.text} </p>
<div className="notification-content_close">
<CloseIcon color={notificationState.closeColor} onClick={doNotShow} />
</div>
</div>
</div>
);
};
Now for the specific question - I cannot understand why onFormSubmit() I just get a log with the array of strings and nothing happens - it does not even run once - the props get updated with every instance and that should trigger a render, the notifications are held into a state and even more so, should update.
What is wrong with my code?

Handle rendering array of editable components

I have a contentEditable component:
EditableComponent.js
const EditableComponent = (props) => {
return <p contentEditable>{props.children}</p>;
};
In the ParentComponent I can add EditableComponents to an array (someArr) with useState, and then I pass someArr and setSomeArray via props to another component (AllEditable) to render it:
ParentComponent.js
import EditableComponent from "./components";
import AllEditable from "./components";
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setContentArr((prevContentArr) => {
return [...prevContentArr, <EditableComponent />];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
Each rendered component (EditableComponent) have a span with the content 'X' that should delete the target onClick:
AllEditable.js
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.filter((_, idx) => idx !== index);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
);
})}
</div>
);
};
The problem:
It doesn't matter which component I'm trying to delete, it removes the last component (even in the Components section in the developer tools) and I'm pretty sure that the logic behind deleting (filter) works well.
I tried deleting the contentEditable attribute, and added some unique random text in each component and then it worked as expected!.
Things I tried
Creating a new array without the removed target
Nesting the components in someArr in objects with key: index, example: {idx: 0, content: <EditableComponent />}
Added a function - onDoubleClick for each EditableComponent to toggle the attribute contentEditable, true or false.
Replaced the element in EditableComponent to <textarea></textarea> instead of <p contentEditable></p>
Your problem is all your EditableComponent components have the same key (because you haven't declared key on them). React cannot identify which EditableComponent you want to delete. You can check this document.
I'd suggest you add a key attribute like below
<EditableComponent key={prevContentArr.length - 1}/>
For safer index reservation, you should use map instead filter
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.map((x, idx) => idx !== index ? x : null);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return content ? (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
) : null;
})}
</div>
);
};
const { useState } = React
const EditableComponent = (props) => {
return <p contentEditable>{props.children}</p>;
};
const AllEditable= (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.map((x, idx) => idx !== index ? x : null);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return content ? (
<div key={`content-${idx}`}>
<span onClick={() => {deleteContentHandler(idx);}}>
X
</span>
<div>{content}</div>
</div>
) : null;
})}
</div>
);
};
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setSomeArr((prevContentArr) => {
return [...prevContentArr, <EditableComponent key={prevContentArr.length - 1}/>];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
ReactDOM.render(
<ParentComponent/>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Side note that keys with index values are not the best because your array keeps changing that would make key changes as well. You can use some unique key generator to handle that situation instead.
You shluld change the way you set keys, setting the element key to: "content-index" is confusing for react, because once you remove an item all the indexes will change, and therefore your keys.
So you need to find a way to have static keys for your elements. That way everytime they render, the key will be the same.
The logic is working correctly and in fact it is deleting the correct elements, however since you are using the index to identify elements, you will never see this since it will always appear that only the last one is removed (when the array updates, the indices will update too).
So if you had 0,1,2 and removed 1 now you have an array with indices 0,1
To test this you can place a reference to the index when rendering the content editable div, based on testing you can see that the correct element is in fact being removed:
import "./styles.css";
import React, { useState } from "react";
const EditableComponent = (props) => {
return (
<p contentEditable>
{props.children}
{props.idtfy}
</p>
);
};
const AllEditable = (props) => {
const deleteContentHandler = (index) => {
props.setSomeArr((prevState) => {
return prevState.filter((_, idx) => idx !== index);
});
};
return (
<div>
{props.someArr.map((content, idx) => {
return (
<div key={`content-${idx}`}>
<span>{idx}</span>
<span
onClick={() => {
deleteContentHandler(idx);
}}
>
X
</span>
<div>{content}</div>
</div>
);
})}
</div>
);
};
const ParentComponent = () => {
const [someArr, setSomeArr] = useState([]);
const handleNewEditable = () => {
setSomeArr((prevContentArr) => {
return [
...prevContentArr,
<EditableComponent idtfy={prevContentArr.length + 1} />
];
});
};
return (
<div className="wrapper">
<AllEditable someArr={someArr} setSomeArr={setSomeArr} />
<div>
<button onClick={handleNewEditable}>Add</button>
</div>
</div>
);
};
That said, your key should probably point to some static value, but that will only impact rendering order, not which item is being clicked/closed.

Redux store is updated but view is not

I have the parent Posts.js component which map every object in posts array. In this function I try to filter all notes have same post_id as id of the current mapped post object. All stored in filteredNotes variable. Then I pass it to each child. Now the issue. When I want to add new note in specific post, the view doesn't update (new note was not added to the list) although the database and redux store has been updated successfully.
But when I try to remove that filter function, everything works just fine so I guess the main problem is there. Any idea how to fix this? Thanks
Posts.js
const posts = useSelector((state) => state.post.posts);
const notes = useSelector((state) => state.notes.notes);
useEffect(() => {
dispatch(getPosts());
dispatch(getNotes());
}, []);
const addNoteHandle = (val) => {
dispatch(addNote({new_note: val}));
}
return (
<div className="post__page">
<div className="post__list">
{posts.map((data) => {
let filteredNotes = notes.filter((i) => i.post_id === data.id);
return <Post data={data} notes={filteredNotes} />;
})}
</div>
<PostForm addNewNote={addNoteHandle} />
</div>
);
Post.js
export const Post = ({ data, notes }) => {
return (
<div className="post__item">
<div className="post__title">{data.title}</div>
<div className="post__note">
{notes.map(note => <div>{note.text}</div>)}
</div>
</div>
);
};
NoteForm.js
const NoteForm = ({ addNewNote }) => {
const [text, setText] = useState("");
return (
<div>
<Input value={text} onChange={(e) => setText(e.target.value)} />
<Button type="primary" onClick={() => addNewNote(text)} >
<SendOutlined />
</Button>
</div>
);
};
Action
export const addNote = ({ new_note }) => async (dispatch) => {
try {
const res = await axios.post("http://localhost:9000/api/note", new_note);
dispatch({ type: ADD_NOTE, payload: res.data });
} catch (err) {
dispatch({ type: NOTE_FAIL });
}
};
Reducer
case ADD_NOTE:
return {
...state,
notes: [...state.notes, payload]
};
use useSelector to get the component value from redux store. for some reason hook setText will not work to update the page component. I had a similar problem and could not find any solution. This code may help:
let text ='';
text = useSelector((state) =>
state.yourReducer.text);
Now show your text wherever you want
this will fix the issue until you find real solution

Reactjs hook useState weird behaviour

I'm trying to display a list of Item from an API call to a list of components.
Here's my code:
function Content({...props}) {
const [list, setList] = useState([])
const [loading, setLoading] = useState(true)
const [components, setComponents] = useState([])
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
setComponents([])
setList(data)
console.log(data)
})
})
useEffect(() => {
if (components.length > 0) {
return;
}
let tmp = [...components];
for (const elem in list) {
const info = list[elem]
API.getUserById(info.userid, (data) => {
tmp.push(<InfoItem id={info._id} key={info._id} info={info} module={info.module} since="N/A" user={data.initial ? data.initial : `${data.firstname} ${data.lastname}`} {...props}/>)
setComponents(tmp)
console.log(tmp)
})
}
}, [list])
console.log(components)
return(
<div className="container-fluid">
<div className="row">
<CardHeader title="My tittle"/>
<div className ="col-lg-12">
{loading ?
<Card content={"Loading..."}/>
:
<Card content={
<div style={{height: "62vh", overflow: "hidden"}}>
<div className="list-group h-100" style={{overflowY: "scroll"}}>
{components ? components : <p>Nothing</p>}
</div>
</div>
}/>
}
</div>
</div>
</div>
)
}
As you can see I use one useEffect to handle the result from the API and another one to update the components list. But when I display Content, it's always missing one or many item from the list, even when the list have only 2 elements. And when I display tmp, it's contain all the components as well as when I display the components list. I don't know why but it seems that the update of setComponents doesn't affect the return.
If I try to add some fake elements and fast reload, all the component are poping, I don't know how to force update the list component.
If someone know where that missing elements can came from it will be great. thank you.
I think you need to wait for the async task to finish. Try to fit an await or a .then in the API.getUserById. Your data probably has not yet been retrieved by the time the setComponents(tmp) is executed.
The error is because the tmp array stay the same, even when new item are push so the setComponents doesn't render because it's still the same array, here's what I've done to fix that:
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
let all = []
for (const elem in data) {
const info = data[elem]
API.getUserById(info.patientid, (data) => {
let tmp = [...all]
tmp.push(<InfoItem id={info._id} key={info._id} info={info} module={info.module} since="N/A" patient={data.initial ? data.initial : `${data.firstname} ${data.lastname}`} {...props}/>)
all.push(tmp[tmp.length - 1])
setComponents(tmp)
console.log(tmp)
})
}
})
})
useEffect(() => {
if (!loading) {
return;
}
API.getInfo((data) => {
setLoading(false)
setComponents([])
setList(data)
console.log(data)
})
},[]);

useState changing even without setting the value

I am totally confused about this scenario , I am having a state variable called listItems setting the value for listItems using the api call inside useEffect now in the handleChange I am changing the particular object value inside the listItems but I didn't change the actual listItems value but if i console the listItems it's showing as updated value even without setList how come it happens?
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import OrderSummary from './orderSummary'
export default function displayItems() {
const [listItems, setList] = useState([]);
const [order, setorder] = useState([]);
var newarr = [];
useEffect(() => {
axios.post('http://localhost:3006/listItem', {
})
.then(function (resp) {
let res = resp.data.sendList.response;
let newRes = res.map((item) => {
return item;
})
setList(newRes);
})
.catch(function (error) {
console.log(error);
});
}, [])
function handleChange(type,item) {
var arrList=item;
var newAr=[];
if (type === 1) {
arrList.quantity=arrList.quantity+1;
}
else if (type === 0 && item.quantity > 1) {
arrList.quantity=arrList.quantity-1;
}
newAr.push(arrList);
console.log("test",listItems) // value changes here dont know how
// setList(listItems);
}
function placeOrder(item) {
newarr.push(...order, item);
setorder(newarr)
}
return (
<div className="col">
<div className="row">
<div classname="col-md-6">
<p><b>Available Items</b> </p>
{listItems && listItems.map((item) => {
return (
<div key={item._id}>
<p>Name:{item.name}</p>
<p>Description:{item.description}</p>
<p>Cost:{'₹'}{' '}{item.cost}</p>
<p>Quantity:{' '}
<i onClick={() => handleChange(1,item)} className="fa fa-plus-circle" />
<span className="text-center"><b>{item.quantity}</b></span><i onClick={() => handleChange(0,item)} className="fa fa-minus-circle" /></p>
<div>
<button onClick={() => placeOrder(item)}>Add to order</button>
</div>
</div>)
})}
</div>
{order && <OrderSummary orderItems={order} />}
</div>
</div>
)
}
sandox
The following code var arrList=item; is an assignment by reference, it means that arrList and item are both references to the same object which explains the modification of the second when modifying the first, if you want to clone an object you can use Object.assign() or the Spread operator or another solution:
var arrList = Object.assign({}, item);
// Or
var arrList = {...item};
Working demo:

Categories