I have created an array as follows :
const title = ['hello', 'secondTitle', 'thirdTitle']
And I have a component where I need to pass title as property,
I also have a service that returns an object with members : HelloContent, secondtitleContent, thirdTitleContent
<CustomComponent listTitle = {title} toggleExpanded= {toggleExpanded} pass corresponding data?>
I also have a state variable :
constructor(props) {
state: {
expanded: false
}
}
toggleExpanded = () => {
this.setState({expanded: !this.state.expanded})
}
So what I want to do is :
Iterate through the array,
Pass the title to the CustomComponent and I'm also passing toggleExpanded ( which expands and collapses the view).
But how do I pass for every item in the array to expand its current view, because this way, if I expand one, all components expand.
Do I change expanded to be an array [] ?
Any suggestions ?
I am confused as to what approach to use, because I did not want to repeat the customComponent and call it 5-6 times ( as many items on the list and data),
but how do I pass and iterate the right ones ?
I wouldn't use boolean in this case. Simply set expanded to the title you need to expand and update your component to compare if expanded is equals to own title. If so, expand it.
Related
My array state is rendering and i can to push new elements without problem, but, when i trying add in specific index, react is changing the state of the previous elements.
Example of the previous state:
{this.state.numOfRows.map((v, k) => (
<Item add={this.add.bind(this)} key={`item-${k}`} index={k} item={v} />
))}
This will render a html element with option = 0;
So, when i call this.add(index) prop i can push 2 or 3 more elements on my array and i change options for 1 and 2.
But, when i call this.add(index) to add a new element in index 2, for example, then the previous options that i selected are updated and the new element with 0 in option is displayed in the last index.
enter image description here
When rendering lists in React, you should specify a key that is based on the unique identity of the item being rendered, rather than the (current) index/position of that item in the source array.
In your case, if each items in the numOfRows array had a unique identifier (ie someUniqueId as shown below) then this would be a better candidate for use with the key prop of each <Item /> that is rendered:
{
this.state.numOfRows.map((v, k) =>
(<Item add={this.add.bind(this)}
key={`item-${ v.someUniqueId }`}
index={k}
item={v} />))
}
The reason for this is that React determines if an item in a rendered list needs to be updated based on that items key. Rendering anomalies will arise when an item's key (based on it's index) remains the same despite the actual data for that item changing.
For more information on this, see React's Lists and Keys documentation.
Also, see this in-depth article on the key prop and specifically, this summary of when an index based key prop is okay.
Semantic's Transition.Group example shown here https://react.semantic-ui.com/modules/transition/#types-group only animates the bottom most element. How would I change it so the top most element gets animated instead.
If I press the add or subtract button in the example above the animation only applies to the last element in the list, how would I make it only animate the top most element. Or essentially make the selector opposite of default so it goes from top to bottom
If you read the sample code you realize all it does is to display a slice of the array based on the current length in the state. It appears to be appending / removing from the bottom because it's taking elements starting from the index 0. If you want to show it as if it's animating at the top, you just need to take the elements starting from the last element. For example, you can do this with reverse:
#8 state = { items: users.slice(0, 3).reverse() }
#10 handleAdd = () => this.setState({ items: users.slice(0, this.state.items.length + 1).reverse() })
#12 handleRemove = () => this.setState({ items: this.state.items.slice(1) })
An easier solution using reverse would be to just reverse the array when you map through it and create the children.
render() {
const { items } = this.state;
return (<div>
{items.clone().reverse().map((item, index) => <div key={item}>
{ /* Stuff */ }
</div>)}
</div>);
}
Yes, it would create a new array on each call to render, but the key prop identifies an element and prevents unnecessary re-renders of the child elements, because though it is a new array, object references will still be the same. But I would recommend to use a more identifiable and unique value for the key prop, e.g. IDs' or slugs.
Also this way you wouldn't have to recalculate the array using reverse everywhere you use it, just at one point in your code.
How can I make dynamic row (add and delete row), and input binding (bind onChange event)
to work?, If it's just single input then it's easy, but a certain structure to defined
the intial form element.
this.state = {
rowCount: 1,
rows: [{
id: 1,
structure: [
{
type: 'input',
value
},
{
type: 'textarea',
value:
}
]
}]
}
render() {
return(
<div>{this.state.rows.map(o => <div>
{o.structure.map(s => {
let block = ''
if(s.type === 'input') {
block = <div><input /></div>
}else if(s.type === 'textarea') {
block = <div><textarea /></div>
}
return block
})}
<button onClick={this.addBlock}>Add block</button>
</div>)}</div>
)
}
https://codesandbox.io/s/nr88plwqj4
I modified your code so we can add blocks (delete have a similar approach), with the asumption that all the blocks will have the same structure. Also, I split your "render" in several "renders" that display different parts of the program, to make it easier to read.
https://codesandbox.io/s/xjzlzqj04z
The idea is the following: Everytime you click to Add block, you check all the rows, making a copy into a "newRows" variable. Finnaly, I do a push to newRows with the new data we want to add, in this case and adding the suitable id.
In the case of this structure in particular, I highly recommend that each row is a separate react component, more if the structure is the same for each row (text + input). If you do it with an extra react component, you can manage the add or delete block with the state being a simple array of ids instead of adding all the structure, and all the and all the logic (onchange value, delete) can be manage inside the other component. If you want to access to the value of all the components in the base component, you can add as a prop an "updateValue" that will get the id, and will update the state. I will try to make an example and update it later
edit:
here I have an example with the rows added as a different component. This simplify a lot the state logic, but that also depends on your needs.
https://codesandbox.io/s/4xq4w37v1w
I must say, codesandbox is a bit buggy with react, so If it doesnt work I recomend to "select all -> cut -> paste"
Here's an example of adding new row and deleting the last row without changing the initial state rows object structure:
https://codesandbox.io/s/vm4lqnjyz7
A few points worth to be mentioned:
1) don't forget to add keys to elements when rendering a list, here's an explanation why: https://reactjs.org/docs/lists-and-keys.html#keys
2) "rowCount" state property seems to be redundant as it equals the rows array length
3) "add row" button probably should be placed outside of the map method if you're going just to add a new row to existing array (instead of building an independent tree of input blocks inside each box).
I am building aReact Application.I have a state array which holds array of blogs.
I am rendering each blog using a BlogDisplay component.
Following is my code :
render() {
for( var i = 0;i < this.state.blogData.length;i++){
blogDataToRender.push(<BlogDisplay blogData = {this.state.blogData[i]} onUserTriggerForDetailedView ={this.props.onUserTriggerForDetailedView}/>);
}
return (
<div>
{blogDataToRender}
</div>
);
}
I am getting a warning like this :
index.js:5279 Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of `BloggerHome`. See https://facebook.github.io/react/docs/lists-and-keys.html#keys for more information.
in BlogDisplay (created by BloggerHome)
in BloggerHome (created by Blogger)
in div (created by Blogger)
in div (created by Blogger)
in Blogger
Is my modelling correct ? How can I correct this?
Thanks a lot.
You need to add a key for each BlogDisplay component:
for( var i = 0;i < this.state.blogData.length;i++){
blogDataToRender.push(<BlogDisplay key={i} blogData = {this.state.blogData[i]} onUserTriggerForDetailedView ={this.props.onUserTriggerForDetailedView}/>);
}
The key is important for child components in an array. Because when one of those child components is under operation, React needs the key to determine which one is operated. Also, when the source array data is changed, React needs the key to check the diff of components.
For in-depth explanation about why keys are necessary, please refer to the official document.
What should be the value of key for each child component?
According to the above document:
The key only has to be unique among its siblings, not globally unique.
Also, blogData[i].id is the best choice if there is a unique id in the source data. Indexes as in the above example is not recommended, if the child components would reorder (performance issue).
Each element in React needs to have a unique key, which React uses to identify it. When you just render an element overtly, React automatically assigns one. However, when you assign an ARRAY of elements, each element in that array isn't assigned a key by default, so you need to. Something like this:
blogDataToRender.push(<BlogDisplay key={i} blogData={this.state.blogData[i]} onUserTriggerForDetailedView={this.props.onUserTriggerForDetailedView}/>);
Check out Chris' reply on Understanding unique keys for array children in React.js.
To fix this, you must set a unique id for each of your elements and then add a key prop to each element. You can read more about how React uses keys to identify elements in lists in the official documentation.
This is how your render() method should look:
render() {
return (
<div>
{this.state.blogData.map((item) => {
return (<BlogDisplay key={item.id} blogData={item} onUserTriggerForDetailedView={this.props.onUserTriggerForDetailedView} />)
})}
</div>
);
}
To answer your question quickly all you need to do is read the error and fix it! Add a unique key value to each component like so:
...
for( var i = 0;i < this.state.blogData.length;i++){
blogDataToRender.push(
<BlogDisplay
key={this.state.blogData[i].id}
blogData={this.state.blogData[i]}
onUserTriggerForDetailedView{this.props.onUserTriggerForDetailedView}
/>
);
}
...
In this case I assume that each one of your blog entries has a unique id, the thing you need to be aware of here is that the key value needs to be unique and not depending on the position of that element inside the array. Every time you render these elements that key needs to be the same, this value is used by React to optimize re rendering and avoid rendering items that did not change. If you want to understand why this is necessary here is a good link. Next time I recommend reading the docs because they are very well done and you can learn a lot from it.
Now the way I would do it is using map:
...
const blogDataToRender = this.state.blogData.map(blogData =>
<BlogDisplay
key={blogData.id}
blogData={blogData}
onUserTriggerForDetailedView{this.props.onUserTriggerForDetailedView}
/>
);
...
This helps readability and leverages an iterator so you don't need to keep track of the index. It is a widely used pattern when developing in React.
I'm building a table control which dynamically generates keys (which I understand may not be a good idea - I imagine the key should be uniquely associated with the data it represents otherwise React could just generate unique ids for us?), but either way it seems the keys are not being set and I have no idea why. The rows in the table are generated with a function that can be found here. Basically I have a helper component which takes an optional component to transform all child elements - here is one of those transform functions (from the TableBody component):
const transformRows = (keyPrefix) => (children, passthroughProps) => (React.Children.map(children, (c, i) => {
return React.cloneElement(c, {
key: `${keyPrefix}-${i}`,
style: rowStyle,
className: rowClassName,
columnDefinitions: columnDefinitions,
rowData: tableData[i],
includeVerticalScrollbar,
...passthroughProps
});
}));
the weird thing is that when I step through the code it seems the key is being assigned, but then I get a warning in the browser Each child in an array or iterator should have a unique "key" prop which traces back to here in the stack trace.
Any ideas?
One way to overcome this is to wrap each cloned element with React.Fragment and set the key on the latter.
Like so:
<>
{connections.map(connection => (
<React.Fragment key={connection.id}>
{React.cloneElement(element, connection)}
</React.Fragment>
))}
</>
from the docs React.cloneElement
Unlike React.addons.cloneWithProps, key and ref from the original
element will be preserved.
So if you set key for Cell, than each clone get key automaticaly.