React component is re-rendering items removed from state - javascript

This is a difficult one to explain so I will do my best!
My Goal
I have been learning React and decided to try build a Todo List App from scratch. I wanted to implement a "push notification" system, which when you say mark a todo as complete it will pop up in the bottom left corner saying for example "walk the dog has been updated". Then after a few seconds or so it will be removed from the UI.
Fairly simple Goal, and for the most part I have got it working... BUT... if you quickly mark a few todos as complete they will get removed from the UI and then get re-rendered back in!
I have tried as many different ways of removing items from state as I can think of and even changing where the component is pulled in etc.
This is probably a noobie question, but I am still learning!
Here is a link to a code sandbox, best way I could think of to show where I am at:
Alert Component State/Parent
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/layout/PageContainer.js
Alert Component
https://codesandbox.io/s/runtime-night-h4czf?file=/src/components/parts/Alert.js
Any help much appreciated!

When you call a set function to update state, it will update from the last rendered value. If you want it to update from the last set value, you need to pass the update function instead of just the new values.
For instance, you can change your setTodos in your markComplete function to something like this.
setTodos(todos => todos.map((todo) => {
if (id === todo.id) {
todo = {
...todo,
complete: !todo.complete,
};
}
return todo;
}));
https://codesandbox.io/s/jovial-yalow-yd0jz

If asynchronous events are happening, the value in the scope of the executed event handler might be out of date.
When updating lists of values, use the updating method which receives the previous state, for example
setAlerts(previousAlerts => {
const newAlerts = (build new alerts from prev alerts);
return newAlerts;
});
instead of directly using the alerts you got from useState.

In the PageContainer.js, modify this function
const removeAlert = (id) => {
setAlerts(alerts.filter((alert) => alert.id !== id));
};
to this
const removeAlert = (id) => {
setAlerts(prev => prev.filter((alert) => alert.id !== id));
};
This will also fix the issue when unchecking completed todos at high speed

Related

I try to display api data but nothing comes out, not even an error on React

I'm slowly understanding the services and fetch with React but when I try to show something, it shows me absolutely nothing. I put the code in case someone can help me. Maybe I have to look at how to work with JSON, I don't know.
let datosApi = [];
const recogerDatos = () => {
let json = "https://jsonplaceholder.typicode.com/albums";
let miApi = "http://localhost/dsw/api/";
fetch(json)
.then(data => data.json())
.then(info => {
console.log(info);
this.datosApi = info;
})
}
function Services() {
return (
<>
{datosApi.map(datos => (
<p>{datos.title}</p>
))}
</>
);
}
export default Services;
JSON data appears in https://jsonplaceholder.typicode.com/albums
I think your example is missing something or you've not done it.
Basically there's a few things wrong:
recogerDatos is never being called
datosApi is not declared, and even if it was, it's not stateful, thus won't cause a re-render of your items.
I've created a working sandbox here that shows it working, and the code is below:
const [result, setResult] = useState([]);
const recogerDatos = () => {
let json = "https://jsonplaceholder.typicode.com/albums";
fetch(json)
.then((data) => data.json())
.then((info) => {
console.log(info);
setResult(info);
});
};
useEffect(() => {
recogerDatos();
}, []);
return (
<div className="App">
{result.length > 0 && result.map((datos) => <p>{datos.title}</p>)}
</div>
);
The recogerDatos function is called on page load (see useEffect), and the result is updated when the fetch is successful. This causes a re-render and the items are shown.
You are displaying data from your list
let datosApi = [];
However it does not seem like you are populating your list with data from the API since the method recogerDatos() is never being called.
From your code it seems like you're missing some core React patterns like state management and the components lifecycle. Since React re-renders components a lot you want to store things like fetched data into state to avoid them constantly reset to their initial value. And when you want to fetch the data you usually don't want to do it at each re-render (that would cause A LOT of fetching), instead you usually want to trigger it based on different events, for example when component (that will be used to show this data) is mounted. Such things are usually using the components lifecycle methods / useEffect hooks to ensure that they happen at the right point in time. I recommend you to go into React documentation and study the basics a bit more so you can understand the main concepts when you're coding, but here's a quick sandbox with an example of how you could get the desired effect in React:
https://codesandbox.io/s/beautiful-frost-on9wmn?file=/src/App.js

React usestate not updating on first Click Or on First time

Please, See this - https://codesandbox.io/s/morning-grass-z8qrq
https://codesandbox.io/s/blue-flower-wl92u
** the second click, third, fourth, fifth click - menuOpen is true, then again click false - behaves as expected**
let [menuOpen, setMenuOpen] = useState(false);
<div
onClick={() => {
// setMenuOpen(true);
setMenuOpen(!menuOpen); // I's not updated in the First time.
console.log(menuOpen); // First time: false // not updating
>
.......// some code
</div>
Please give me, some answers. I have been trying to solve this problem for Two days. I just can't solve it.
Try this:
export default function App() {
const [menuOpen, setMenuOpen] = useState(false);
return (
<>
<button onClick={() => setMenuOpen(!menuOpen)}>Click</button>
Is menu Open: { menuOpen ? "True": "False"}
</>
);
}
Example demo can be found here.
useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.
Give this a try
setMenuOpen(prevMenuOpenState => !prevMenuOpenState);
or
<div
onClick={() => setMenuOpen(!menuOpen)}
>
I had even this problem in my code. My scenario is as follows:
Its hotel detail page. There is horizontal tab menu of room types. If a hotel has more than 3 types of room, then there is show room button. I am using React Functional components in through the code. hotel detail basic page and room section page are different components created. values are passed to room section components through props.
My problem: When I click to room type further 3rd type, then show room value in function (setSelectedTab()) room component doesn't set at an instant. And hence as function moves further, it doesn't set document.getElementById(id) since show room had not been set. As function (setSelectedTab()) completes in first click it sets the show room to true, but selected tab doesn't set. I had to click 2nd time to set the tab.
solution:
After a long try and error, I settle down to the following:
I declare the function as async and made await the setshowRoom() value.
This solved my complete problem.
async function setSelectedTab(e, data) {
firstScroll += 1;
data >= 2 && await setMenuOpen(true);
if (data >= 0) {
.................
const id = e.href;
const anchor = document.getElementById(id);
..............
..............
}
}
and in room component: showRoom, setshowRoom in useState and calling the setSelectedTab() using props. This solves problem of single click
Drawback: I found delay of 1 second to set this tab.
If anyone have better solution than this without making async await, then please post here.
Thanks.
The Answer is just refactoring the code into class Component without using hooks useState. Using state and setState to update. The Problem will solve.
But If I use useState hooks the problem remains the same Whatever I do with the code.

React 1000 checkboxes - clicking/re-render takes several seconds

So I am trying to learn React, and have a quite simple application I am trying to build.
So I have a backend API returning a list of say 1000 items, each item has a name and a code.
I want to put out all the names of this list into check boxes.
And then do X with all selected items - say print the name, with a selectable font to a pdf document.
With this I also want some easy features like "select all" and "deselect all".
So since I am trying to learn react I am investigating a few different options how to do this.
None seems good.
So I tried making a child component for each checkbox, felt clean and "react".
This seems to be really bad performance, like take 2-3 seconds for each onChange callback so I skipped that.
I tried making a Set in the class with excluded ones. This too seems to be really slow, and a bad solution since things like "select all" and "deselect all" will be really ugly to implement. Like looping through the original list and adding all of them to the excluded set.
Another solution I didn't get working is modifying the original data array. Like making the data model include a checked boolean to get a default value, and then modify that. But then the data needs to be a map instead of an array. But I have a feeling this solution will be really slow too whenever clicking the checkbox. I dont quite understand why it is so slow to just do a simple checkbox.
So please tell me how to approach this problem in react.
A few direction questions:
How do I modify an array when I fetch it, say add a checked: true variable to each item in the array?
async componentDidMount() {
const url = "randombackendapi";
const response = await fetch(url);
const data = await response.json();
this.setState({ data: data.data, loading: false });
}
Why is this so extremely slow? (Like take 3 seconds each click and give me a [Violation] 'click' handler took 3547ms) warning. And my version of each item being a sub function with callback being equally slow. How can I make this faster? - Edit this is the only question that remains.
{this.state.data.map((item, key) => (
<FormControlLabel
key={item.code}
label={item.name}
control={
<Checkbox
onChange={this.handleChange.bind(this, item.code)}
checked={!this.state.excludedSets.has(item.code)}
code={item.code}
/>
}
/>
))}
handleChange = (code, event) => {
this.setState({
excludedSets: event.target.checked
? this.state.excludedSets.delete(code)
: this.state.excludedSets.add(code)
});
};
I guess I don't understand how to design my react components in a good way.
How do I modify an array when I fetch it, say add a checked: true variable to each item in the array?
Once you have the array you can use a map to add a checked key, which will just make the process much easier by utilizing map on the main array to check and an easier implementation for the select-deselect all feature
let data = [{code: 1},{code: 2},{code: 3}]
let modifiedData = data.map(item => {
return {...item, checked: false}
})
//modifiedData = [ { code: 1, checked: false }, { code: 2, checked: false }, { code: 3, checked: false } ]
I would recommend to save the modified data inside the state instead of the data you fetched since you can always modify that array to send it back to the api in the desired format
now that you have the modified array with the new checked key you can use map to select and deselect like so
const handleChange = (code) => {
modifiedData = modifiedData.map(item => item.code === code ? {...item, checked: !item.checked}: item)
}
And as of the deselect all | select all you can use another map method to do this like so
const selectAllHandler = () => {
modifiedData = modifiedData.map(item => {
return {...item, checked: true}})
}
and vice-versa
const deselectAllHandler = () => {
modifiedData = modifiedData.map(item => {
return {...item, checked: false}})
}
It's a common rendering issue React will have, you can use virtualize technique to reduce the amount of DOM elements to boost the re-rendering time.
There're several packages you can choose, like react-virtuoso, react-window etc.
The main concept is to only render the elements inside your viewport and display others when you're scrolling.
So I was unable to get the React checkbox component performant (each click taking 2-3 seconds), so I decided to just go with html checkboxes and pure javascript selectors for my needs, works great.

React Router -- history push state not refreshing with new state object

When Promise.all resolves and the new activity is saved, the user should be routed to /activities to view their newly created activity. Everything works as expected, however I currently need to refresh /activities page (once) after being routed in order to view the new activity in the table.
const handleSaveActivity = e => {
e.preventDefault();
Promise.all([
addActivity(),
saveActivity()
]).then(() => {
props.history.push('/activities');
})
};
I'm not sure how to re-render the page automatically after pushing a new history state, so the user does not need to manually refresh the page to see the new state. Happy to provide more code snippets if I left out something critical.
Hi i must be a little late to answer this, but this issue can be due to the wrong use of useEffect, if you have lets say a todo list and you wanna fetch data with axios for example, it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
},[])
now as you can see we have initial value of an empty array, so this is acting as a ComponentDidMount, what you might want is to re render the component after it gets a new value, so you want to have a ComponentDidUpdate effect, so you would just not initialize the value as an empty array, therefore it would look like this:
useEffect(()=>{
axios.get(`${YOUR_URL}/todos`)
.then((res)=>{
setTodos(todos=res.data)
})
})
Hope this helps someone, couse i landed here due to the same issue and came to solve it this way.
just to run this.setState({whateverKey:whateverValue})?
In your activities page (call it Activities component) you should call API to get the updated data every time browser hit this component URL.
With class based style, you should do it in componentDidMount life cycle hook
class Activities extends Component {
// ...
componentDidMount() { loadActivities() }
// ...
}
With function based style, you should do it in useEffect hook
import React, { useEffect } from 'react'
const Activities = () => {
useEffect(() => { loadActivities() });
}
https://github.com/supasate/connected-react-router Please use this package, it solves the problem.
This issue I've faced a few minutes ago...however I finally found the solution by manually using the vanilla javascript. => for refreshing the page you can use
=> window.location.reload(false); after using the push property.

How to change screen and setState at the same time on first click in React Native

I have a function that changes the screen and sets the state at the same time that works given below (initial state of weapon is null):
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
navigate("Second", {weapon: this.state.weapon});
}
And I am calling this on my second screen via {this.props.navigation.state.weapon}, but the state doesn't seem to update to M16 until I go back and click the button again.
I have console logged both above and below the setState function and on the first click it always gives me null but M16 when I go back and click it again.
Can I not run setState at the same time as navigating between screens? If I can what am I doing wrong.
TLDR: I'm trying to set state and change page in same function so I can then display the new state on the new page as text. The state change doesn't happen until the second click of the button.
Try putting a small timeout for the navigate. The state change may not be complete when you hit the navigate instruction
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
setTimeout(() => navigate("Second", {weapon: this.state.weapon}),20);
}
State is supposed to be used as a helper to handle a small amount of data inside your component. The state life cycle ends as the component it belongs completely unmount. Also, note that setState is an asynchronous function, so you must not rely on React to handle sync situations for you. Updating your state will also make your component rerender, so you should use it carefully to avoid loss memory unnecessarily.
If you just want to pass data from a component to another, in this case using navigation props is enough, like this navigate("Second", {weapon: 'M16'});. You don't need to update your state to then be able to pass this data further. In fact, it makes no sense to update your state before navigation, since the current state itself will be lost in the next screen.
If you need to share the exact same state prop between more components, which doesn't seem to be the case, maybe you should consider using another approach, like Redux (https://redux.js.org/).
I recommend you to read the official docs for more detailed info:
https://reactjs.org/docs/react-component.html#state
https://reactjs.org/docs/state-and-lifecycle.html
Hope it helps
Edit:
Based on the information you provided below, if weapon will be an array, for example, and you need to push a new value to it before navigation, you should not use setState, try this instead:
const { navigate } = this.props.navigation;
clickM16 = () => {
const { weapon } = this.state;
weapon.push('M16');
navigate("Second", { weapon });
}
Hope it helps
I will give another suggestion:
var { navigate } = this.props.navigation;
clickM16 = () => {
this.setState({
weapon: "M16"
});
let sendPara = this.state.weapon
navigate("Second", {weapon: sendPara});
}
Recive parameter in respective component.
catName={this.props.navigation.state.params.sendPara}
I hope this may help you.

Categories

Resources