I have come across a serious performance problem. I have an application with a list of 2000 quite a big DOM structure list items and when I am changing one item in the list component like this:
changeOne:function(){
var permTodos = this.props.todos;
permTodos[Math.floor((Math.random() * 5) + 0)].todo=((Math.random() * 50) + 1);
this.setState({ todos: permTodos });
},
it rerenders all of the list items in the virtual DOM representation, and this is really slow. The same thing is done in Angular1 about 40 times faster. I have tried to implement shouldComponentUpdate for the list items
shouldComponentUpdate:function(nextProps, nextState){
return this.props.todo!==nextProps.todo;
},
but the this.props.todo is the same as nextProps.todo so nothing is changed. I have recreated a mini-version of my application in this fiddle https://jsfiddle.net/2Lk1hr6v/29/ and I am hoping there are som React experts out there who could help me with this unfortunate situation.
You need to add unique key for each item from list:
this.state.todos.map(function(todo) {
return <TodoItem key={todo.id} todo={todo} done={this.done} />
}.bind(this))
Key must be associated with item for example it may be its id. It must be not its index in list where are all items lies.
Same edit here too, as mentioned in another thread.
Also I noticed two more things that I noticed in the fiddle here. In the TodoItem class, you have to say this.props.done not this.done.
render: function() {
return <li onClick={this.props.done}>{this.props.todo}</li>
}
In the TodoList class, you are referring to this.done which is not declared. You should probably declare that function?.
{
this.state.todos.map(function(todo) {
return <TodoItem todo={todo} done={this.done} />
}.bind(this))
}
Also switch to ES6 arrow functions! You don't have to bind this at the end of it. Code is much smaller and cleaner!!
{
this.state.todos.map(todo => <TodoItem todo={todo} done={this.done} />)
}
Check out the edited fiddle: https://jsfiddle.net/2Lk1hr6v/33/
This should be now much faster than what it was!
PS: Explore other ES6 features here!!
Related
const [arrayOfQuestions, setArrayOfQuestions] = useState([
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
{
q: 'Are your products safe to use?',
a: 'Yes.',
hidden: true
},
])
const toggleItemOpenAndClose = (e) => {
let array = arrayOfQuestions
array[e.target.id].hidden = !array[e.target.id].hidden
setArrayOfQuestions(array)
}
return (
<div>
<Layout
bgImage={metaData.heroImage.childImageSharp.fluid}
header='Frequently Asked Questions'>
<div className='page-container'>
<div className="content-container">
{
arrayOfQuestions.map((question,i) => {
return (
<div id={i} key={`id${i}`} onClick={toggleItemOpenAndClose} className='block-container'>
<div id={i} className='white smallerheader'>
{question.q}
</div>
{
question.hidden ?
null :
<div id={i} className='white paragraph'>
<br/>
{question.a}
</div>
}
</div>
)
})
}
</div>
</div>
</Layout>
</div>
)
}
Im using Gatsby and react hooks.
Im trying to build a collapsible menu (an faq) sort of like a menu, so that when you click on one of the options, it expands and shows you the answer associated with the question. However, I'm having trouble making it work in real time. whenever i click on the option, my data updates, but the dom itself doesnt update when i click on the option. then, when i change something in the code, the app updates (hot reloader?) and then the dom updates. As far as i understand it, when i change state in real time, the dom should also update in real time, but I can't understand why its not in this case. Has anyone experienced something like this before?
You should not be updating state directly. This should be your toggle code
const toggleItemOpenAndClose = (e) => {
// This will create a new array and with all new objects to preserve immutability
let newArray = arrayOfQuestions.map(x => {
return {... x}
}
newArray[e.target.id].hidden = !newArray[e.target.id].hidden
setArrayOfQuestions(newArray)
}
Make a copy arrayOfQuestions like so,
let array = [...arrayOfQuestions]
Why ?
What you're updating is an object property but it's still belongs to the same array ref. When you spread over the array, you will unpack the object references in your array and pack them in a new array due to those [] and then setting this new array will actually trigger the render.
In case you want to make copy of the objects as well in your array, use map as suggested by #Doug Hill which I think looks better.
But in your case simply spreading will also work for now. Things get messy when there are nesting depths of objects and then consider using libraries which supply you with deep cloning utilities.
The componentWillReceiveProps is becoming deprecated, however, I am unclear as to how to migrate away from it. For example, a simplified version of my current looks something like this:
import Reorder, {reorder, reorderImmutale, reorderFromTo, reorderFromToImmutable} from 'react-reorder'
class ObjectsArea extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.objects ? this.props.objects.items : []
};
}
componentWillReceiveProps(nextProps){
//May have to do a deep compare between nextProps.items and current items?
if (nextProps.objects){
this.setState({items: this.nextProps.objects.items})
}
}
onReorder (event, previousIndex, nextIndex, fromId, toId) {
let new_items = reorder(this.state.items, previousIndex, nextIndex)
this.setState({
items: new_items
});
//call to parent function
}
render(){
orderable_items = <Reorder reorderId="objects" onReorder={this.onReorder.bind(this)}>
{
this.state.items.map(item => (
<div key={item.id}>
{item.text}
</div>
))
}
</Reorder>
return (
<div>{orderable_items}</div>
)
}
My requirements:
Sometimes there will be no objects property (there isn't one on initial load)
When there is an objects property a sortable/draggable list is created using the react-reorder component
When items in the list are dragged to be rearranged the onReorder function is called.
The onReorder function should do two things: update the list on the screen, call a parent function passed in from props.
Currently all of this will work with componentWillReceiveProps, however, what is the proper way to migrate away from componentWillReceiveProps based on the above requirements?
While Tolsee's answer is perfectly correct it is also worth mentioning that the react docs suggest removing derived state (state that is calculated based on props) altogether. There is a great article here that is a great read in my opinion.
Your example fits the Anti-pattern: Unconditionally copying props to state example perfectly.
Without knowing your environment I cannot recommend a solution certainly, but to me it looks like you will be able to use the Fully controlled component example.
In that case, you'd need to lift your state up, simply use objects.items to render your Reorder child, and during the onReorder event simply call a function that you received as a prop.
In your problem you can do.
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.objects){){
return {items: this.nextProps.objects.items};
}
else return null;
}
Please follow this post for better understanding
I am trying to build a simple dynamically updated, interactive list that styles each <li></li> according to the css rules of a .clicked class, when you click on them.
The app is composed of two components, a parent and a child and the code in question is the following (taken from the child):
handleClick(e) {
document.getElementById(e.currentTarget.id).setAttribute("class","clicked");
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x,i)=>{ return (<li id={i} key={i} className={i%2==0 ? "white" : "grey"}
onClick={this.handleClick}>{x}</li>); })
return (
<div>
<ul id="ul">{ pro }</ul>
</div>
What is happening here is basically that the parent is passing to the child a sentences prop (an array of sentences that will form the basis for the formation of a dynamic list).
The controversial part is me using DOM manipulation in the form of document.getElementById(e.currentTarget.id).setAttribute("class","two");
in order to change the class of the dynamically created html from jsx.
The code above works, however it does not feel as best practice. The whole advantage in using react is to use virtual dom and optimize the way the DOM is updated.
My questions are the following:
1) Am I right to feel this way? (that my solution is not best practice?)
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
If you know this question to be a duplicate, please leave a comment and I ll remove it.
1) Am I right to feel this way? (that my solution is not best practice?)
It is correct to assume that this is not an ideal approach, manipulating the DOM via vanilla js in React has its place (Example Use Cases) but should not be done unless absolutely necessary. Also, it is not ideal to use the index from Array.prototype.map as the key on your components as if they change order it can cause confusion for React as the keys would map differently in that case.
2) (If so, ) How can I structure my code in order to use the virtual dom machinery react offers?
You should make use of the component state. If you want each clicked element to maintain the clicked class then make a piece of state that caches the elements that have already recieved the clicked class. if only the most recently clicked element gets the clicked class then simply cache an identifier to the appropriate element in the state. You could also use refs for this purpose though the overusage of them is somewhat discouraged by facebook.
Here is a quick snipped that will toggle the click class on each <li>
class Test extends Component {
constructor() {
super();
this.state = {
clicked: {}
};
}
render() {
let ar = this.props.sentences;
let pro = ar.map((x, i) => {
const color_class = i % 2 === 0 ? "white" : "grey";
const clicked_class = this.state.clicked[i] === true ? "clicked" : "";
let clicked = Object.assign({}, this.state.clicked); // Dont mutate state!!!
return (
<li
id={i}
key={i}
className={`${color_class} ${clicked_class}`}
onClick={e => {
if (clicked.hasOwnProperty(i)) {
delete clicked[i];
} else {
clicked[i] = true;
}
this.setState({ clicked });
}}
>
{x}
</li>
);
});
return (
<div>
<ul id="ul">
{pro}
</ul>
</div>
);
}
}
I'm trying to swap two children of an element with a transition using React.
<div style={{position: 'relative'}}>
{this.state.items.map((item, index) => (
<div
key={item}
style={{position: 'absolute',
transform: `translateY(${index * 20}px)`,
transition: '1s linear transform'}}>
{item}
</div>
))}
</div>
state.items is an array of two items. When it is reordered, the two child divs should transition to their new positions.
What happens in reality is while the second element transitions as expected, the first one jumps instantly.
As far as I can tell, React thinks that it can reuse one of the child elements, but not the other, although the docs say that if we use the key attribute, it should always reuse elements: https://facebook.github.io/react/docs/reconciliation.html (at least, that's how I understand it).
What should I change in my code to make it work as expected? Or is it a bug in React?
Live example: http://codepen.io/pavelp/pen/jAkoAG
caveat: I'm making some assumptions in this answer, nevertheless it shines light some of your (and previously my) questions. Also my solution can almost certainly be simplified, but for the purposes of answering this question it should be adequate.
This is a great question. I was a bit surprised to open up the dev tools and see what's actually happening when swapping the items.
If you take a look, you can sort of see what React is up to. The second element is not changing its style prop at all and just swaps the inner text node, while the first element is dropped into the dom as a fresh element.
If I had to guess, this is because of the way swapping two items in a array works, where at least one item is copied to a temp variable and placed back into the array.
I thought that maybe if you make the translation random, both elements would get new style props and animate, but that only made it more clear this was not the intended behaviour.
On the way to finding a solution:
As an experiment, what if we created the nodes ahead of time, and pass the index prop in render via React.cloneElement. While we're at it, let's render a span if index === 0 and a div otherwise. No keys to worry about.
http://codepen.io/alex-wilmer/pen/pbaXzQ?editors=1010
Opening up the dev tools now illustrates exactly what React is intending. React is preserving the elements and only changing the relevant part, in this case the innerText node and the element type. Because the styles are swapped exactly 1 : 1, no style update is needed.
Solution:
You can generate your React elements ahead of time, keep those in an array, and as such there are no keys to shuffle around and figure out how to place back into the DOM. Then use a different array to keep track of the intended order. Possibly highly convoluted, but it works!
http://codepen.io/alex-wilmer/pen/kXZKoN?editors=1010
const Item = function (props) {
return (
<div
style={{position: 'absolute',
transform: `translateY(${props.index * 20}px)`,
transition: '0.5s linear transform'}}>
{props.children}
</div>
)
}
const App = React.createClass({
getInitialState () {
return {
items: [
{item: 'One', C: <Item>One</Item>},
{item: 'Two', C: <Item>Two</Item>}
],
order: ['One', 'Two']
};
},
swap () {
this.setState({
order: [this.state.order[1], this.state.order[0]]
});
},
render: function () {
return <div>
<button onClick={this.swap}>Swap</button>
<div style={{position: 'relative'}}>
{this.state.items.map(x =>
React.cloneElement(x.C, {
index: this.state.order.findIndex(z => z === x.item)
}))
}
</div>
</div>;
}
});
I want to display a selectable list of objects with react. I've got the list component, which owns several groups, which own several elements. Obviously, the list data itself should go into a store, but what about the UI state (e.g., which element is selected)? It's ostensively a property of the root list element, but to pass it down to the elements, each element along the chain (well, just 1 in this case- the group) would need to pass it along even if they don't care about it. And if I want to add another property I need to pass it down the chain rather verbosely.
Also, encapsulation. How do I make a component that might be instanced multiple times with different data?
In other languages I would just pass the list pointer down the chain, so that child elements could whatever they want from it, so everything stays encapsulated. Would the flux way be to pass the store down to child elements? Would you have a separate store for UI state vs the persistent data?
Let's design this from the bottom up. So, at the bottom we have a ListItem.
What does the list item need in order to render? Let's say it's a title, a body, and if it's selected or not.
Also what events make sense of the list item? Probably just onClick.
Does it have any state? No, unless we need to handle things like it being hovered, or other state specific to the ListItem, but not relevant to its parent.
var ListItem = React.createClass({
render(){
var classNames = ["list-item"];
if (this.props.selected) classNames.push("selected");
return (
<li className={classNames.join} onClick={this.props.onClick}>
<div className="title">{this.props.title}</div>
<div className="body">{this.props.body}</div>
</li>
);
}
});
The ListGroup is a bit less obvious.
For props we'll ask for a list of items, the selected index (if any), and an onSelectionChange callback, which is called with (nextIndex, lastIndex).
This component also has no state. For extra points, we'll allow specifying a custom renderer, making this more reusable. You can pass renderer={component} where component is something that implements the ListItem interface described above.
var ListGroup = React.createClass({
getDefaultProps: function(){
return {renderer: ListItem, onSelectionChange: function(){}}
},
render(){
return (
<div>{this.props.items.map(this.renderItem)}</div>
);
},
renderItem(data, index){
var selectedIndex = this.props.selectedIndex;
return (
<this.props.renderer
selected={selectedIndex === index}
key={i}
onClick={() => this.props.onSelectionChange(index, selectedIndex)}
{...data} />
}
});
And then we could render ListGroup like this:
<ListGroup
items={[{title: "foo", body: "bar"}, {title: "baz", body: "quux"]}
selectedIndex={this.state.i}
onSelectionChange={(index) => this.setState({i: index})} />
The data is passed down the tree, but only the essence of the data required to render each component. ListItem doesn't need the full list of items, nor the selected index. It doesn't care if multiple selections are allowed, or just one. All it knows is that it's selected when this.props.selected is truthy, and otherwise it's not.