React: target.dataset.key sometimes returns undefined - javascript

I'm working on a form view with a lot of fields being displayed dynamically given some other parameters. I have this very long form divided in different components.
In this part of the form, the user can define as many tasks as needed, and for each task there are some fields and buttons displayed, which are all managed on an object array inside the component's state (each task is an object included in the array).
I'm showing this by mapping the tasks array inside the render and everything is working fine, except for one thing, which are some toggle buttons. This is the code for each toggle button:
<Toggle label='¿Requiere registro?' data-key={e.numero} inlineLabel onText=' ' offText=' ' checked={e.regBool} onChange={this.checkRegistro} />
And this is the code of the function that is called:
public checkRegistro = (ev, checked: boolean) => {
const temp = checked;
ev.target.dataset ? console.log(ev.target.dataset.key) : console.log('no toma key');
const numero = ev.target.dataset ? ev.target.dataset.key : '1';
var arr = [...this.state.tareas];
const index = arr.map((e) => {return e.numero;}).indexOf(parseInt(numero));
if(index !== -1){
arr[index].regBool = temp;
this.setState({tareas: [...arr]})
console.log(arr[index].regBool);
}
}
As you can see, I specified data-key so I could access to the correct task (this.state.tareas) given the number of the task (tareas[index].numero).
The problem here is, sometimes (just sometimes) ev.target.dataset.key returns undefined. I try pressing the same toggle a couple of times, but only after I stop pressing it and wait a second it returns the correct value again
I suppose it's because as there is much to render, it doesn't get to specify the correct value on the data-key all the time, but I have no idea on how to make it work consistently. I've thought on separating it on another component being called for each task, but I'm not quite sure how that could help.
Any idea is well received, if you need more information you could also let me know. Please consider this is my first post here.
Thank you!!

Related

Why am I getting an empty array?

I am working on a product feedback app (with react) for my portfolio and came across an unexpected problem. The issue takes place in my SuggestionDetails component where I am getting the current id with useParams and filtering out the current product based on that id. Everything works and renders perfectly fine with the pre-existing suggestions array, but the problem starts when I try to render a new suggestion that I have created and added to the array.
This is how I am getting the current suggestion:
// I am getting the suggestions array through props
const { id } = useParams();
const [suggestion, setSuggestion] = useState(() => {
const currentSuggestion =
suggestions &&
suggestions.filter((suggestion) =>
suggestion.id === parseInt(id) ? suggestion : null
);
return currentSuggestion;
});
This is what the return value of the current suggestion should be (the new suggestion is still not created here):
Here when I try to filted out the new suggestion (the last element) I get an empty array:
I am still kind of new to this stuff and dont understand why this is happening. I have not added the code where I am creating a new suggestion and adding it to the current state, but I don't think the issue is there since it has clearly been created and added to current list of suggestion requests. Any information on this would be greatly appreciated, thank you.
The problem was that I was using parseInt instead of parseFloat to parse the id generated by Math.random()

React component is re-rendering items removed from state

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

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.

Accessing state using a variable

I have started learning React, and decided to create my first app similar to online food ordering cart.
My problem started with accessing the state of the current number of the particular dish in the shopping cart ( if I want to order 2 pizzas it would show '2x Pizza' in the shopping cart ).
As I don't really want to use if method as it would make my code not really professional, I wanted to ask you if do you know how to sort out my problem.
What I've got for the moment is that part of the code:
this.state = {
values: {
pizza:0
}
}
<div className="item" onClick={(e) => this.addItem(e)} itemprice={1250} foodtype="values.pizza"><p className="itemDesc">Pizza</p></div>
addItem(e) {
let foodType = clickObj.getAttribute("foodtype"); // values.pizza
this.setState({
[foodType]: [foodType] + 1
});
}
I expected to increase this.state.value.pizza by 1 on each click of the button. I have to specify the variable with an attribute name because there would be quite a few of positions to order and I want to keep my code as tidy as I can.
Unfortunately, I can access the state but the value of that state is not being changed after click. (checked using console.log)
If you want to use previous state value to increment, you can use the following code.
this.setState(prevState => ({[foodType]: prevState[foodType] + 1}))
Also you can use https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en extention for chrome which will give you details about the state and other values.
Hope that answers your question.

React - How to add "key" when pushing to an array

React v15.1
I am creating a table with a variable number of rows, depending on the data provided via props. Here is where I am (and it works fine):
getRows = () => {
const rows = [];
for (const col in this.props.tableRows) {
if ({}.hasOwnProperty.call(this.props.tableRows, col)) {
rows.push(React.cloneElement(this.props.tableRows[col], {}));
}
}
return rows;
}
I am working on making the console warnings disappear. This old favorite is appearing Each child in an array or iterator should have a unique "key" prop. Check the render method of 'TableData'.
What is the best way to add a key to cloned elements that I am pushing to an empty array?
OR is there a better way to handle this data on dynamically-generated table rows?
This is for an upload dialog for contact information I am using in many places. It successfully performs as expected, just need to add a key...or make it even better.
#medet-tleukabiluly has the right answer:
keyed_items = items.map((item, key) => React.cloneElement(item, {key}))
as long as each item is an element (not just a string).
It would probably make more sense to use the .map function and render JSX so you can easily assign a key to each table row.
function getRows() {
const {tableRows} = this.props;
return tableRows.map((row, index) =>
<tr key={index}>
// whatever else you wanted to add inside each row
</tr>
);
}
This is mostly just a guess for what you're trying to do - if it doesn't work for you, please post a comment that describes your problem in more detail and I can edit this answer to provide a better solution for you.

Categories

Resources