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.
Related
I'm building a React component, which currently displays a list of technologies. The user can toggle which of these technologies are 'active' (i.e. selected) by clicking on them. They start as false, change to true, back to false and so on.
When the user does this, they get added to the overall technologies array and displayed on the frontend.
This is done through a 'toggleTechnology' function, which gets two properties passed in to it: item (the technology text) and index (where this item is in the array).
const toggleTechnology = (item, index) => {
console.log(item)
console.log(index)
if (technologies.includes(item)) {
let filteredArray = technologies.filter((currentItem, currentIndex) => currentIndex !== index)
setTechnologies(filteredArray)
} else {
setTechnologies(tech => [...tech, item])
}
}
The list of technologies is kept in a separate array, to be displayed dynamically on the frontend, like so:
const frontEndSklls = ['HTML', 'CSS', 'Javascript', 'React', 'Unit tests', 'Vue.js']
{ frontEndSklls.map((item, index) => {
return <span className={technologies.includes(item) ? "toggle-option active" : "toggle-option"} onClick={() => toggleTechnology(item, index)} key={index}>{item}</span>
}) }
My issue comes in when a user adds multiple skills and then removes some of them.
So for example, if they clicked on the following in this order, each item in the technologies array would have an index like this:
0: HTML
1: Unit tests
2: React
3: CSS
However, if they then clicked on 'React' to remove it, the array would go like this:
0: HTML
1: Unit tests
2: CSS
If they then click on CSS to remove it, I think because the new index of CSS is 2, it isn't removing the property. So what you need to do is click on another element first, say React again, and then CSS can be removed as the new array has CSS at index point 2.
So my question: is there a way for me to adjust my code so that the index remains the same for each of the options, even when you click on and off the elements?
The solution to me seems to be to turn each element in the array into an object, and assign a permanent index number and value to each element in the array - but this doesn't feel as clean as it could be.
Any suggestions would be appreciated.
You have two different arrays in the component:
frontEndSkills - static - used to render all technologies in the UI with activated/deactivated status.
technologies - dynamic - the filtered array used to mark the active technologies in the UI as such.
The problem is that you're supplying the index of an item from the frontEndSkills array to be used in the technologies array, which is not always the same as the former.
#Subham Jain's answer also would work perfectly, but here is my shot at it, without the need for the index at all:
const toggleTechnology = (item) => {
if (technologies.includes(item)) {
let filteredArray = [...technologies].filter(currentItem => currentItem !== item)
setTechnologies(filteredArray)
} else {
setTechnologies(tech => [...tech, item])
}
}
Note: The suggestion to not use the index as a key from the other answers/comments here is sensible and highly recommended. Please consider that seriously. If you do have to use it for any reason at all, make sure that you understand why it is anti-pattern to do so.
I would suggest that change 2 things.
Instead of using "index" for the "key" use "item".
Do not toggle the state based on the index. To achieve this you can modify the "toggletechnology" as below:-
const toggleTechnology = (item) => {
if (technologies.includes(item)) {
let indexOfItem = technologies.indexOf(item);
let filteredArray = [...technologies].splice(indexOfItem,1);
setTechnologies(filteredArray)
} else {
setTechnologies(tech => [...tech, item])
}
}
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!!
I am making a blog and I want to show a list of articles under each post so the user do not need to go back to the front page. But I do not want the article they are currently reading to be in the list.
I am using filter method and it's working, but only for a split second. As far as i understand it is happening because of the useContext. I do not want to change global state, only local.
const ArticleDetails = (props) => {
const {data} = useContext(ArticleContext); //data with all fetched articles from db
const article = props.location.state.article; //data for individual article
return (
<div>
//showing here data for individual post that i passed in props
{data.filter(item => {return item !== article})
.map(item => {return <ArticleList articleTitle={item.title} key={item.id} />})}
</div>
)
}
It can be that the useContext hooh has been completed some modification to the data you use in return after the render.
By filter or map you actually are not changing any global state or local state at all.
After that above mentioned update, your data items might be changing. Also your filter function needs to filter with a never changing id or something like that so your filter results stays consistent across renders or state updates.
data.filter(item => {return item.id !== article.id})
Also using equality for the objects like that will not work for your filter criteria. You use not equal criteria and yes after a render your item will not be equal to article in a subsequent render even they are same reference in one render. That kind of equality is checked by Object.is that would be a specific need. But I think you just need an id check.
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.
My question is just about idea and creativity of you.
I have react component that is getting information from SQL database:
const [offers,setOffers] = useState([])
useEffect(()=>{
axios.get(`http://localhost:8085/incoming`)
.then(res=>{
setOffers(res.data.recordset)
})
.catch(error=>console.log(error))},[])
just when page is loaded.
In JSX I put one function that is filling my table:
<tbody className="table-hover">
{OfferItem(offers)}
</tbody>
The function is:
const OfferItem = (array)=>{
if(array!==undefined){
return array.map((item,index)=>{
return <IncomingOffer singleitem={item} index={index+1} key={item.Number}/>
})
}}
So far so good but I want to have input field that is providing dynamic search and re-rendering. So I put:
<label>Относно:</label><input onChange={searchAbout} />
And created my function:
const searchAbout = (e)=>{
e.preventDefault(e)
const string = e.target.value.toUpperCase()
const res = offers.filter(el=>Object.values(el).some(val=>val.toUpperCase().includes(string)))
setOffers(res)
}
In the way it is wrote it is working when typing in input field but as you see it is completely filtering the array and I am loosing all other items in it. So I was wondering for a way that I am filtering and re-rendering items but for example when I use backspace or remove the whole search criteria to see again my whole array information. I must admit I know how my code is working and what is expected but I do not know how to do it. Any ideas?