React performance: rendering big list with PureRenderMixin - javascript

I took a TodoList example to reflect my problem but obviously my real-world code is more complex.
I have some pseudo-code like this.
var Todo = React.createClass({
mixins: [PureRenderMixin],
............
}
var TodosContainer = React.createClass({
mixins: [PureRenderMixin],
renderTodo: function(todo) {
return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>;
},
render: function() {
var todos = this.props.todos.map(this.renderTodo)
return (
<ReactCSSTransitionGroup transitionName="transition-todo">
{todos}
</ReactCSSTransitionGroup>,
);
}
});
All my data is immutable, and PureRenderMixin is used appropriately and everything works fine. When a Todo data is modified, only the parent and the edited todo is re-rendered.
The problem is that at some point my list grows big as the user is scrolling. And when a single Todo is updated, it takes more and more time to render the parent, call shouldComponentUpdate on all the todos, and then render the single todo.
As you can see, the Todo component has other component than the Todo data. This is data that is required for render by all the todos and is shared (for example we could imagine there's a "displayMode" for the todos). Having many properties makes the shouldComponentUpdate perform a little bit slower.
Also, using ReactCSSTransitionGroup seems to slow down a little too, as ReactCSSTransitionGroup have to render itself and ReactCSSTransitionGroupChild even before the shouldComponentUpdate of todos is called. React.addons.Perf shows that ReactCSSTransitionGroup > ReactCSSTransitionGroupChild rendering is time wasted for each item of the list.
So, as far as I know, I use PureRenderMixin but with a larger list this may be not enough. I still get not so bad performances, but would like to know if there are easy ways to optimize my renderings.
Any idea?
Edit:
So far, my big list is paginated, so instead of having a big list of items, I now split this big list in a list of pages. This permits to have better performances as each page can now implement shouldComponentUpdate. Now when an item changes in a page, React only has to call the main render function that iterates on the page, and only call the render function from a single page, which make a lot less iteration work.
However, my render performance is still linear to the page number (O(n)) I have. So if I have thousands of pages it's still the same issue :) In my usecase it's unlikely to happen but I'm still interested in a better solution.
I am pretty sure it is possible to achieve O(log(n)) rendering performance where n is the number of items (or pages), by splitting a large list into a tree (like a persistent data structure), and where each node has the power to short-circuit the computation with shouldComponentUpdate
Yes I'm thinking of something akin to persistent data structures like Vector in Scala or Clojure:
However I'm concerned about React because as far as I know it may have to create intermediate dom nodes when rendering the internal nodes of the tree. This may be a problem according to the usecase (and may be solved in future versions of React)
Also as we are using Javascript I wonder if Immutable-JS support this, and make the "internal nodes" accessible. See: https://github.com/facebook/immutable-js/issues/541
Edit: useful link with my experiments: Can a React-Redux app really scale as well as, say Backbone? Even with reselect. On mobile

In our product we also had issues related to the amount of code being rendered, and we started using observables (see this blog). This might partially solve your problem, as changing todo will no longer require the parent component that holds the list to be re-rendered (but adding still does).
It might also help you in re-rendering the list faster as your todoItem components could just return false when the props change on shouldComponentUpdate.
For further performance improvements when rendering the overview, I think your tree / paging idea is nice indeed. With observable arrays, each page could start to listen to array splices (using an ES7 polyfill or mobservable) in a certain range. That would introduce some administration, as these ranges might change over time, but should get you to O(log(n))
So you get something like:
var TodosContainer = React.createClass({
componentDidMount() {
this.props.todos.observe(function(change) {
if (change.type === 'splice' && change.index >= this.props.startRange && change.index < this.props.endRange)
this.forceUpdate();
});
},
renderTodo: function(todo) {
return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>;
},
render: function() {
var todos = this.props.todos.slice(this.props.startRange, this.props.endRange).map(this.renderTodo)
return (
<ReactCSSTransitionGroup transitionName="transition-todo">
{todos}
</ReactCSSTransitionGroup>,
);
}
});
The central problem with large lists and react seems that you cannot just shift new DOM nodes into the dom. Othwerwise you won't need the 'pages' at all to partition the data in smaller chunks and you could just splice one new Todo item into the dom, as done with JQuery in this jsFiddle. You could still do that with react if you use a ref for each todo item, but that would be working around the system I think as it might break the reconciliation system?

Here is a POC implementation I've done with ImmutableJS internal structure. This is not a public API so it is not ready for production and does not currently handle corner cases but it works.
var ImmutableListRenderer = React.createClass({
render: function() {
// Should not require to use wrapper <span> here but impossible for now
return (<span>
{this.props.list._root ? <GnRenderer gn={this.props.list._root}/> : undefined}
{this.props.list._tail ? <GnRenderer gn={this.props.list._tail}/> : undefined}
</span>);
}
})
// "Gn" is the equivalent of the "internal node" of the persistent data structure schema of the question
var GnRenderer = React.createClass({
shouldComponentUpdate: function(nextProps) {
console.debug("should update?",(nextProps.gn !== this.props.gn));
return (nextProps.gn !== this.props.gn);
},
propTypes: {
gn: React.PropTypes.object.isRequired,
},
render: function() {
// Should not require to use wrapper <span> here but impossible for now
return (
<span>
{this.props.gn.array.map(function(gnItem,index) {
// TODO should check for Gn instead, because list items can be objects too...
var isGn = typeof gnItem === "object"
if ( isGn ) {
return <GnRenderer gn={gnItem}/>
} else {
// TODO should be able to customize the item rendering from outside
return <span>{" -> " + gnItem}</span>
}
}.bind(this))}
</span>
);
}
})
The client code looks like
React.render(
<ImmutableListRenderer list={ImmutableList}/>,
document.getElementById('container')
);
Here is a JsFiddle that logs the number of shouldComponentUpdate calls after a single element of the list (size N) is updated: this does not require to call N times shouldComponentUpdate
Further implementation details are shared in this ImmutableJs github issue

Recently I had a performance bottleneck trying to render a table with 500+ records, the reducers were immutable and I was using reselect to memoize the complex selectors, after pulling some hair, I found the problem was solved memoizing all the selectors.

Related

Alter react component state properly

I'm working at a project in which I have to display graphs.
For displaying graphs I'm using vis.js in particular react-vis-network a implementation for using parts of vis.js in React with its stateful approaches.
Initial nodes and edges are loaded before my component is mounted and are passed as props for an initial state.
I attached two eventHandler one direct to a vis.js (the underlying DOM library) and the other at a decorator (button).
The desired/expected behaviour:
A node is removed by clicking either the node or the corresponding button.
Observed behavior:
Sometimes a node is removed and sometimes a node just disappears for a few ms and is reattached but without a decorator/button.
I already tried to start with an empty state and attaching the nodes,edges in componentDidMount() but I got the same result. I hope you can give me a hint.
BTW: Is the way I use to attach components a/the right way?
Every other help to improve my class is appreciated also
class MyNetwork extends Component {
constructor(props){
super(props);
let componentNodes = [];
for (let node of props.nodes){
componentNodes.push(this.createNode(node));
}
let componentEdges = [];
for (let edge of props.edges){
componentEdges.push(this.createEdge(edge));
}
this.state = {nodes:componentNodes,edges:componentEdges};
["_handleButtonClick"].forEach(name => {
this[name] = this[name].bind(this);
});
}
createNode(node){
const Decorator = props => {
return (
<button
onClick={() =>{this._handleButtonClick(props);}}
>
Click Me
</button>
);
};
node.decorator = Decorator;
return React.createElement(Node,{...node})
}
createEdge(edge){
return React.createElement(Edge,{...edge})
}
addNode(node){
this.setState({
nodes: [...this.state.nodes, this.createNode(node)]
})
}
_handleButtonClick(e) {
if(e){
console.log("clicked node has id:" +e.id);
this.removeNode(e.id);
}
}
onSelectNode(params){
console.log(params);
window.myApp.removeNode(params[0]);
}
removeNode(id) {
let array = [...this.state.nodes]; // make a separate copy of the array
let index = array.findIndex(i => i.props.id === id );
array.splice(index, 1);
this.setState({nodes: array});
}
render() {
return (
<div id='network'>
<Network options={this.props.options} onSelectNode={this.onSelectNode}>
{[this.state.nodes]}
{[this.state.edges]}
</Network>
</div>
);
}
}
export default MyNetwork
Before clicking node 2
After clicking node 2
Update 1
I created a live example at stackblitz which isn't working yet caused by other failures I make and can't find.
The components I use are:
Network
Node
Edge
Edge and Node are extending Module
I reworked my MyNetwork component according to some mistakes xadm mentioned.
Components (espacially dynamic) shouldn't be stored in state.
I implemented two new functions nodes() and edges() // line 15-41*
key prop should be used, too.
key is used now // line 18 + 32*
Passed props cannot be modified, you still have to copy initial data
into state. State is required for updates/rerendering.
line 9*
*line numbers in live example I mentioned above
Update 2
I reworked my code and now the life sample is working.
My hope is that I could use the native vis.js events and use them in MyNetwork or other Components I will write.
I read about using 3rd Party DOM event in this question can't figure out to adapt it for my particular case. Because I don't know how to attach the event handler to . Is this possible to do so I can use the event in other components?
Or should I open another question for this topic?
I see several possibilities of problems here.
<Decorator/> should be defined outside of <MyNetwork /> class. Click handler should be passed as prop.
Components (espacially dynamic) shouldn't be stored in state. Just render them in render or by rendering method (called from render). Use <Node/> components with decorator prop, key prop should be used, too.
Passed props cannot be modified, you still have to copy initial data into state. State is required for updates/rerendering. You probably need to remove edge(-es) while removing node.
Create a working example (on stackblitz?) if a problem won't be resolved.
It sounds like React is re-initializing your component when you are clicking a button. Maybe someone smarter than I am can figure out why that is happening...
But since no one has commented on this yet, one way I have handled these sorts of issues is to take the state management out of the display component. You say you are passing the nodes and edges via props from a parent component. You might consider moving the addNode, removeNode, createEdge, and other methods up to the parent component so that it is maintaining the state of the node/edge structure and your display component <MyNetwork/> is only displaying what it receives as props.
Perhaps this isn't an option in your app, but I generally use Redux to remove the state management from the components all together. I find it reduces situations like this where "who should own the state" isn't always clear.

What is a good pattern for caching DOM values in React classes

A common pattern we have evolved in our team over the years was to store DOM dimensions after a render so that we could access them later with less performance implications (as an example - figure out if a component should be rendered while scrolling through a large list). A basic example will be:
class List extends Component {
construct(props){
super(props)
this.elements = {};
this.heights = {};
}
render(){
return <ol>
{this.props.items.map((item,i)=>this.getItem(item,i))}
</ol>;
}
getItem(item){
return <li key={item.id} ref={(el)=>this.cacheElement(el, item.id)}>{item.name}</li>
}
cacheElement(el,id){
this.elements[id] = findDOMNode(el);
}
componentDidUpdate(){
this.items.forEach(item=>{
//in real examples this will probably implement some sort
//of memoization to prevent overpolling over existing values
this.heights[item.id] = elements[item.id].offsetHieght;
})
}
}
Note - I know that how I'm using refs here and storing DOM references is not ideal and that there might be other anti-patterns here - was aiming for code clarity
Historically the reason we did not save this in the setState was because we did not want to force a re-render due to a post render calculation. However from reading around I learned this approach is sort of an anti-pattern - specifically that storing data outside of setState might not play nicely with the async rendering features that started trickling out in the last few months.
Are there other patterns for these kind of challenges (post render values that need to be cached for future use)?

Big list performance with React

I am in the process of implementing a filterable list with React. The structure of the list is as shown in the image below.
PREMISE
Here's a description of how it is supposed to work:
The state resides in the highest level component, the Search component.
The state is described as follows:
{
visible : boolean,
files : array,
filtered : array,
query : string,
currentlySelectedIndex : integer
}
files is a potentially very large, array containing file paths (10000 entries is a plausible number).
filtered is the filtered array after the user types at least 2 characters. I know it's derivative data and as such an argument could be made about storing it in the state but it is needed for
currentlySelectedIndex which is the index of the currently selected element from the filtered list.
User types more than 2 letters into the Input component, the array is filtered and for each entry in the filtered array a Result component is rendered
Each Result component is displaying the full path that partially matched the query, and the partial match part of the path is highlighted. For example the DOM of a Result component, if the user had typed 'le' would be something like this :
<li>this/is/a/fi<strong>le</strong>/path</li>
If the user presses the up or down keys while the Input component is focused the currentlySelectedIndex changes based on the filtered array. This causes the Result component that matches the index to be marked as selected causing a re-render
PROBLEM
Initially I tested this with a small enough array of files, using the development version of React, and all worked fine.
The problem appeared when I had to deal with a files array as big as 10000 entries. Typing 2 letters in the Input would generate a big list and when I pressed the up and down keys to navigate it it would be very laggy.
At first I did not have a defined component for the Result elements and I was merely making the list on the fly, on each render of the Search component, as such:
results = this.state.filtered.map(function(file, index) {
var start, end, matchIndex, match = this.state.query;
matchIndex = file.indexOf(match);
start = file.slice(0, matchIndex);
end = file.slice(matchIndex + match.length);
return (
<li onClick={this.handleListClick}
data-path={file}
className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
key={file} >
{start}
<span className="marked">{match}</span>
{end}
</li>
);
}.bind(this));
As you can tell, every time the currentlySelectedIndex changed, it would cause a re-render and the list would be re-created each time. I thought that since I had set a key value on each li element React would avoid re-rendering every other li element that did not have its className change, but apparently it wasn't so.
I ended up defining a class for the Result elements, where it explicitly checks whether each Result element should re-render based on whether it was previously selected and based on the current user input :
var ResultItem = React.createClass({
shouldComponentUpdate : function(nextProps) {
if (nextProps.match !== this.props.match) {
return true;
} else {
return (nextProps.selected !== this.props.selected);
}
},
render : function() {
return (
<li onClick={this.props.handleListClick}
data-path={this.props.file}
className={
(this.props.selected) ? "valid selected" : "valid"
}
key={this.props.file} >
{this.props.children}
</li>
);
}
});
And the list is now created as such:
results = this.state.filtered.map(function(file, index) {
var start, end, matchIndex, match = this.state.query, selected;
matchIndex = file.indexOf(match);
start = file.slice(0, matchIndex);
end = file.slice(matchIndex + match.length);
selected = (index === this.state.currentlySelected) ? true : false
return (
<ResultItem handleClick={this.handleListClick}
data-path={file}
selected={selected}
key={file}
match={match} >
{start}
<span className="marked">{match}</span>
{end}
</ResultItem>
);
}.bind(this));
}
This made performance slightly better, but it's still not good enough. Thing is when I tested on the production version of React things worked buttery smooth, no lag at all.
BOTTOMLINE
Is such a noticeable discrepancy between development and production versions of React normal?
Am I understanding/doing something wrong when I think about how React manages the list?
UPDATE 14-11-2016
I have found this presentation of Michael Jackson, where he tackles an issue very similar to this one: https://youtu.be/7S8v8jfLb1Q?t=26m2s
The solution is very similar to the one proposed by AskarovBeknar's answer, below
UPDATE 14-4-2018
Since this is apparently a popular question and things have progressed since the original question was asked, while I do encourage you to watch the video linked above, in order to get a grasp of a virtual layout, I also encourage you to use the React Virtualized library if you do not want to re-invent the wheel.
As with many of the other answers to this question the main problem lies in the fact that rendering so many elements in the DOM whilst doing filtering and handling key events is going to be slow.
You are not doing anything inherently wrong with regards to React that is causing the issue but like many of the issues that are performance related the UI can also take a big percentage of the blame.
If your UI is not designed with efficiency in mind even tools like React that are designed to be performant will suffer.
Filtering the result set is a great start as mentioned by #Koen
I've played around with the idea a bit and created an example app illustrating how I might start to tackle this kind of problem.
This is by no means production ready code but it does illustrate the concept adequately and can be modified to be more robust, feel free to take a look at the code - I hope at the very least it gives you some ideas...;)
react-large-list-example
My experience with a very similar problem is that react really suffers if there are more than 100-200 or so components in the DOM at once. Even if you are super careful (by setting up all your keys and/or implementing a shouldComponentUpdate method) to only to change one or two components on a re-render, you're still going to be in a world of hurt.
The slow part of react at the moment is when it compares the difference between the virtual DOM and the real DOM. If you have thousands of components but only update a couple, it doesn't matter, react still has a massive difference operation to do between the DOMs.
When I write pages now I try to design them to minimise the number of components, one way to do this when rendering large lists of components is to... well... not render large lists of components.
What I mean is: only render the components you can currently see, render more as you scroll down, you're user isn't likely to scroll down through thousands of components any way.... I hope.
A great library for doing this is:
https://www.npmjs.com/package/react-infinite-scroll
With a great how-to here:
http://www.reactexamples.com/react-infinite-scroll/
I'm afraid it doesn't remove components that are off the top of the page though, so if you scroll for long enough you're performance issues will start to reemerge.
I know it isn't good practice to provide a link as answer, but the examples they provide are going to explain how to use this library much better than I can here. Hopefully I have explained why big lists are bad, but also a work around.
First of all, the difference between the development and production version of React is huge because in production there are many bypassed sanity checks (such as prop types verification).
Then, I think you should reconsider using Redux because it would be extremely helpful here for what you need (or any kind of flux implementation). You should definitively take a look at this presentation : Big List High Performance React & Redux.
But before diving into redux, you need to made some ajustements to your React code by splitting your components into smaller components because shouldComponentUpdate will totally bypass the rendering of children, so it's a huge gain.
When you have more granular components, you can handle the state with redux and react-redux to better organize the data flow.
I was recently facing a similar issue when I needed to render one thousand rows and be able to modify each row by editing its content. This mini app displays a list of concerts with potential duplicates concerts and I need to chose for each potential duplicate if I want to mark the potential duplicate as an original concert (not a duplicate) by checking the checkbox, and, if necessary, edit the name of the concert. If I do nothing for a particular potential duplicate item, it will be considered duplicate and will be deleted.
Here is what it looks like :
There are basically 4 mains components (there is only one row here but it's for the sake of the example) :
Here is the full code (working CodePen : Huge List with React & Redux) using redux, react-redux, immutable, reselect and recompose:
const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ })
const types = {
CONCERTS_DEDUP_NAME_CHANGED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED',
CONCERTS_DEDUP_CONCERT_TOGGLED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED',
};
const changeName = (pk, name) => ({
type: types.CONCERTS_DEDUP_NAME_CHANGED,
pk,
name
});
const toggleConcert = (pk, toggled) => ({
type: types.CONCERTS_DEDUP_CONCERT_TOGGLED,
pk,
toggled
});
const reducer = (state = initialState, action = {}) => {
switch (action.type) {
case types.CONCERTS_DEDUP_NAME_CHANGED:
return state
.updateIn(['names', String(action.pk)], () => action.name)
.set('_state', 'not_saved');
case types.CONCERTS_DEDUP_CONCERT_TOGGLED:
return state
.updateIn(['concerts', String(action.pk)], () => action.toggled)
.set('_state', 'not_saved');
default:
return state;
}
};
/* configureStore */
const store = Redux.createStore(
reducer,
initialState
);
/* SELECTORS */
const getDuplicatesGroups = (state) => state.get('duplicatesGroups');
const getDuplicateGroup = (state, name) => state.getIn(['duplicatesGroups', name]);
const getConcerts = (state) => state.get('concerts');
const getNames = (state) => state.get('names');
const getConcertName = (state, pk) => getNames(state).get(String(pk));
const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk));
const getGroupNames = reselect.createSelector(
getDuplicatesGroups,
(duplicates) => duplicates.flip().toList()
);
const makeGetConcertName = () => reselect.createSelector(
getConcertName,
(name) => name
);
const makeIsConcertOriginal = () => reselect.createSelector(
isConcertOriginal,
(original) => original
);
const makeGetDuplicateGroup = () => reselect.createSelector(
getDuplicateGroup,
(duplicates) => duplicates
);
/* COMPONENTS */
const DuplicatessTableRow = Recompose.onlyUpdateForKeys(['name'])(({ name }) => {
return (
<tr>
<td>{name}</td>
<DuplicatesRowColumn name={name}/>
</tr>
)
});
const PureToggle = Recompose.onlyUpdateForKeys(['toggled'])(({ toggled, ...otherProps }) => (
<input type="checkbox" defaultChecked={toggled} {...otherProps}/>
));
/* CONTAINERS */
let DuplicatesTable = ({ groups }) => {
return (
<div>
<table className="pure-table pure-table-bordered">
<thead>
<tr>
<th>{'Concert'}</th>
<th>{'Duplicates'}</th>
</tr>
</thead>
<tbody>
{groups.map(name => (
<DuplicatesTableRow key={name} name={name} />
))}
</tbody>
</table>
</div>
)
};
DuplicatesTable.propTypes = {
groups: React.PropTypes.instanceOf(Immutable.List),
};
DuplicatesTable = ReactRedux.connect(
(state) => ({
groups: getGroupNames(state),
})
)(DuplicatesTable);
let DuplicatesRowColumn = ({ duplicates }) => (
<td>
<ul>
{duplicates.map(d => (
<DuplicateItem
key={d}
pk={d}/>
))}
</ul>
</td>
);
DuplicatessRowColumn.propTypes = {
duplicates: React.PropTypes.arrayOf(
React.PropTypes.string
)
};
const makeMapStateToProps1 = (_, { name }) => {
const getDuplicateGroup = makeGetDuplicateGroup();
return (state) => ({
duplicates: getDuplicateGroup(state, name)
});
};
DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn);
let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => {
return (
<li>
<table>
<tbody>
<tr>
<td>{ toggled ? <input type="text" value={name} onChange={(e) => onNameChange(pk, e.target.value)}/> : name }</td>
<td>
<PureToggle toggled={toggled} onChange={(e) => onToggle(pk, e.target.checked)}/>
</td>
</tr>
</tbody>
</table>
</li>
)
}
const makeMapStateToProps2 = (_, { pk }) => {
const getConcertName = makeGetConcertName();
const isConcertOriginal = makeIsConcertOriginal();
return (state) => ({
name: getConcertName(state, pk),
toggled: isConcertOriginal(state, pk)
});
};
DuplicateItem = ReactRedux.connect(
makeMapStateToProps2,
(dispatch) => ({
onNameChange(pk, name) {
dispatch(changeName(pk, name));
},
onToggle(pk, toggled) {
dispatch(toggleConcert(pk, toggled));
}
})
)(DuplicateItem);
const App = () => (
<div style={{ maxWidth: '1200px', margin: 'auto' }}>
<DuplicatesTable />
</div>
)
ReactDOM.render(
<ReactRedux.Provider store={store}>
<App/>
</ReactRedux.Provider>,
document.getElementById('app')
);
Lessons learned by doing this mini app when working with huge dataset
React components work best when they are kept small
Reselect become very useful to avoid recomputation and keep the same reference object (when using immutable.js) given the same arguments.
Create connected component for component that are the closest of the data they need to avoid having component only passing down props that they do not use
Usage of fabric function to create mapDispatchToProps when you need only the initial prop given in ownProps is necessary to avoid useless re-rendering
React & redux definitively rock together !
Like I mentioned in my comment, I doubt that users need all those 10000 results in the browser at once.
What if you page through the results, and always just show a list of 10 results.
I've created an example using this technique, without using any other library like Redux.
Currently only with keyboard navigation, but could easily be extended to work on scrolling as well.
The example exists of 3 components, the container application, a search component and a list component.
Almost all the logic has been moved to the container component.
The gist lies in keeping track of the start and the selected result, and shifting those on keyboard interaction.
nextResult: function() {
var selected = this.state.selected + 1
var start = this.state.start
if(selected >= start + this.props.limit) {
++start
}
if(selected + start < this.state.results.length) {
this.setState({selected: selected, start: start})
}
},
prevResult: function() {
var selected = this.state.selected - 1
var start = this.state.start
if(selected < start) {
--start
}
if(selected + start >= 0) {
this.setState({selected: selected, start: start})
}
},
While simply passing all the files through a filter:
updateResults: function() {
var results = this.props.files.filter(function(file){
return file.file.indexOf(this.state.query) > -1
}, this)
this.setState({
results: results
});
},
And slicing the results based on start and limit in the render method:
render: function() {
var files = this.state.results.slice(this.state.start, this.state.start + this.props.limit)
return (
<div>
<Search onSearch={this.onSearch} onKeyDown={this.onKeyDown} />
<List files={files} selected={this.state.selected - this.state.start} />
</div>
)
}
Fiddle containing a full working example: https://jsfiddle.net/koenpunt/hm1xnpqk/
Try filter before loading into the React component and only show a reasonable amount of items in the component and load more on demand. Nobody can view that many items at one time.
I don't think you are, but don't use indexes as keys.
To find out the real reason why the development and production versions are different you could try profiling your code.
Load your page, start recording, perform a change, stop recording and then check out the timings. See here for instructions for performance profiling in Chrome.
React in development version checks for proptypes of each component to ease development process, while in production it is omitted.
Filtering list of strings is very expensive operation for every keyup. it might cause performance issues because of single threaded nature of JavaScript.
Solution might be to use debounce method to delay execution of your filter function until the delay is expired.
Another problem might be the huge list itself. You can create virtual layout and reuse created items just replacing data. Basically you create scrollable container component with fixed height, inside of which you will place list container. The height of list container should be set manually (itemHeight * numberOfItems) depending on the length of visible list, to have a scrollbar working. Then create a few item components so that they will fill scrollable containers height and maybe add extra one or two mimic continuous list effect. make them absolute position and on scroll just move their position so that it will mimic continuous list(I think you will find out how to implement it:)
One more thing is writing to DOM is also expensive operation especially if you do it wrong. You can use canvas for displaying lists and create smooth experience on scroll. Checkout react-canvas components. I heard that they have already done some work on Lists.
Check out React Virtualized Select, it's designed to address this issue and performs impressively in my experience. From the description:
HOC that uses react-virtualized and react-select to display large lists of options in a drop-down
https://github.com/bvaughn/react-virtualized-select
For anyone struggling with this problem I have written a component react-big-list that handles lists to up to 1 million of records.
On top of that it comes with some fancy extra features like:
Sorting
Caching
Custom filtering
...
We are using it in production in quite some apps and it works great.
React has recommend react-window library: https://www.npmjs.com/package/react-window
It better than react-vitualized. You can try it
I recently developed multi-select input for React and tested it with 48.000 records. It's working without any problem.
https://www.npmjs.com/package/react-multi-select-advanced
Here's my attempt on this
react-async-lazy-list
OR,
https://github.com/sawrozpdl/react-async-lazy-list
you can give it a shot. it's pretty fast as it uses windowing/virtualization and comes with lazy loading and full customization.

Is connect() in leaf-like components a sign of anti-pattern in react+redux?

Currently working on a react + redux project.
I'm also using normalizr to handle the data structure and reselect to gather the right data for the app components.
All seems to be working well.
I find myself in a situation where a leaf-like component needs data from the store, and thus I need to connect() the component to implement it.
As a simplified example, imagine the app is a book editing system with multiple users gathering feedback.
Book
Chapters
Chapter
Comments
Comments
Comments
At different levels of the app, users may contribute to the content and/or provide comments.
Consider I'm rendering a Chapter, it has content (and an author), and comments (each with their own content and author).
Currently I would connect() and reselect the chapter content based on the ID.
Because the database is normalised with normalizr, I'm really only getting the basic content fields of the chapter, and the user ID of the author.
To render the comments, I would use a connected component that can reselect the comments linked to the chapter, then render each comment component individually.
Again, because the database is normalised with normalizr, I really only get the basic content and the user ID of the comment author.
Now, to render something as simple as an author badge, I need to use another connected component to fetch the user details from the user ID I have (both when rendering the chapter author and for each individual comment author).
The component would be something simple like this:
#connect(
createSelector(
(state) => state.entities.get('users'),
(state,props) => props.id,
(users,id) => ( { user:users.get(id)})
)
)
class User extends Component {
render() {
const { user } = this.props
if (!user)
return null
return <div className='user'>
<Avatar name={`${user.first_name} ${user.last_name}`} size={24} round={true} />
</div>
}
}
User.propTypes = {
id : PropTypes.string.isRequired
}
export default User
And it seemingly works fine.
I've tried to do the opposite and de-normalise the data back at a higher level so that for example chapter data would embed the user data directly, rather than just the user ID, and pass it on directly to User – but that only seemed to just make really complicated selectors, and because my data is immutable, it just re-creates objects every time.
So, my question is, is having leaf-like component (like User above) connect() to the store to render a sign of anti-pattern?
Am I doing the right thing, or looking at this the wrong way?
I think your intuition is correct. Nothing wrong with connecting components at any level (including leaf nodes), as long as the API makes sense -- that is, given some props you can reason about the output of the component.
The notion of smart vs dumb components is a bit outdated. Rather, it is better to think about connected vs unconnected components. When considering whether you create a connected vs unconnected components, there are a few things to consider.
Module boundaries
If you divided your app into smaller modules, it is usually better to constrain their interactions to a small API surface. For example, say that users and comments are in separate modules, then I would say it makes more sense for <Comment> component to use a connected <User id={comment.userId}/> component rather than having it grab the user data out itself.
Single Responsibility Principle
A connected component that has too much responsibility is a code smell. For example, the <Comment> component's responsibility can be to grab comment data, and render it, and handle user interaction (with the comment) in the form of action dispatches. If it needs to handle grabbing user data, and handling interactions with user module, then it is doing too much. It is better to delegate related responsibilities to another connected component.
This is also known as the "fat-controller" problem.
Performance
By having a big connected component at the top that passes data down, it actually negatively impacts performance. This is because each state change will update the top-level reference, then each component will get re-rendered, and React will need to perform reconciliation for all the components.
Redux optimizes connected components by assuming they are pure (i.e. if prop references are the same, then skip re-render). If you connect the leaf nodes, then a change in state will only re-render affected leaf nodes -- skipping a lot of reconciliation. This can be seen in action here: https://github.com/mweststrate/redux-todomvc/blob/master/components/TodoItem.js
Reuse and testability
The last thing I want to mention is reuse and testing. A connected component is not reusable if you need to 1) connect it to another part of the state atom, 2) pass in the data directly (e.g. I already have user data, so I just want a pure render). In the same token, connected components are harder to test because you need to setup their environment first before you can render them (e.g. create store, pass store to <Provider>, etc.).
This problem can be mitigated by exporting both connected and unconnected components in places where they make sense.
export const Comment = ({ comment }) => (
<p>
<User id={comment.userId}/>
{ comment.text }
</p>
)
export default connect((state, props) => ({
comment: state.comments[props.id]
}))(Comment)
// later on...
import Comment, { Comment as Unconnected } from './comment'
I agree with #Kevin He's answer that it's not really an anti-pattern, but there are usually better approaches that make your data flow easier to trace.
To accomplish what you're going for without connecting your leaf-like components, you can adjust your selectors to fetch more complete sets of data. For instance, for your <Chapter/> container component, you could use the following:
export const createChapterDataSelector = () => {
const chapterCommentsSelector = createSelector(
(state) => state.entities.get('comments'),
(state, props) => props.id,
(comments, chapterId) => comments.filter((comment) => comment.get('chapterID') === chapterId)
)
return createSelector(
(state, props) => state.entities.getIn(['chapters', props.id]),
(state) => state.entities.get('users'),
chapterCommentsSelector,
(chapter, users, chapterComments) => I.Map({
title: chapter.get('title'),
content: chapter.get('content')
author: users.get(chapter.get('author')),
comments: chapterComments.map((comment) => I.Map({
content: comment.get('content')
author: users.get(comment.get('author'))
}))
})
)
}
This example uses a function that returns a selector specifically for a given Chapter ID so that each <Chapter /> component gets its own memoized selector, in case you have more than one. (Multiple different <Chapter /> components sharing the same selector would wreck the memoization). I've also split chapterCommentsSelector into a separate reselect selector so that it will be memoized, because it transforms (filters, in this case) the data from the state.
In your <Chapter /> component, you can call createChapterDataSelector(), which will give you a selector that provides an Immutable Map containing all of the data you'll need for that <Chapter /> and all of its descendants. Then you can simply pass the props down normally.
Two major benefits of passing props the normal React way are traceable data flow and component reusability. A <Comment /> component that gets passed 'content', 'authorName', and 'authorAvatar' props to render is easy to understand and use. You can use that anywhere in your app that you want to display a comment. Imagine that your app shows a preview of a comment as it's being written. With a "dumb" component, this is trivial. But if your component requires a matching entity in your Redux store, that's a problem because that comment may not exist in the store yet if it's still being written.
However, there may come a time when it makes more sense to connect() components farther down the line. One strong case for this would be if you find that you're passing a ton of props through middle-man components that don't need them, just to get them to their final destination.
From the Redux docs:
Try to keep your presentation components separate. Create container
components by connecting them when it’s convenient. Whenever you feel
like you’re duplicating code in parent components to provide data for
same kinds of children, time to extract a container. Generally as soon
as you feel a parent knows too much about “personal” data or actions
of its children, time to extract a container. In general, try to find
a balance between understandable data flow and areas of responsibility
with your components.
The recommended approach seems to be to start with fewer connected container components, and then only extract more containers when you need to.
Redux suggests that you only connect your upper-level containers to the store. You can pass every props you want for leaves from containers. In this way, it is more easier to trace the data flow.
This is just a personal preference thing, there is nothing wrong to connect leaf-like component to the store, it just adds some complexity to your data flow, thus increase the difficulty to debug.
If you find out that in your app, it is much easier to connect a leaf-like component to the store, then I suggest do it. But it shouldn't happen very often.

How to go from jQuery to React.js? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I have been reading up on React for a few days nows. I can understand most of what I'm looking at, but I'm not entirely confident in my ability to write it. I have been working on a small web app that does all of its html generation through jQuery and appending elements to each other. I'd like to try and rebuild this with React because I believe that it would be faster. This JSFiddle is a small example of the sort of thing I am working on. How would you write it with React?
JS:
function remove() {
this.remove();
}
function timed_append_new_element() {
setTimeout(function () {
var added_content = $("<span />", {
class: "added_content",
text: "Click to close",
click: remove
});
container.append(added_content);
}, 3000);
}
function append_new_element() {
var added_content = $("<span />", {
class: "timed_added_content",
text: "Click to close",
click: remove
});
container.append(added_content);
}
var container = $("<div />", {
class: "container"
});
var header = $("<span />", {
class: "header",
text: "jQuery to React.js Header"
});
var add_button = $("<button />", {
class: "add_button",
text: "Add Element",
click: append_new_element
});
var timed_add_button = $("<button />", {
class: "add_button",
text: "Add Element in 3 seconds",
click: timed_append_new_element
});
container.append(header);
container.append(add_button);
container.append(timed_add_button);
$("body").append(container);
There are a few basic tenets to keep in mind that may help you build a good React application:
Your UI should be a function of the data
In many "jQuery soup" style applications, the business logic for the application, the app's data, and the UI interaction code are all intermingled. This makes these sorts of applications difficult to debug and, especially, difficult to grow. React, like many modern client-side application frameworks, enforce the idea that the UI is just a representation of your data. If you want your UI to change, you should change a piece of data and allow whatever binding system the framework uses to update the UI for you.
In React, each component is (ideally) a function of two pieces of data–the properties passed to the component instance, and the state that the component manages internally. Given the same properties (or "props") and state, the component should render in the same way.
This can be a bit of an abstract idea without concrete examples, so keep it in mind as we move on for now.
Don't touch the DOM
In React, even more so than other data-bound frameworks, you should try not to manipulate the DOM directly if at all possible. A lot of React's performance and complexity characteristics are only possible because React uses a virtual DOM with diffing algorithms internally to operate on the real DOM. Any time you build a component that reaches out and does its own DOM manipulation, you should ask yourself if you could build the same feature more idiomatically with React's virtual DOM features.
Of course, sometimes you'll need to access the DOM, or you'll want to incorporate some jQuery plugin without rebuilding it in React. For times like these, React gives you good component lifecycle hooks that you can use to ensure that React's performance doesn't suffer too much (or, in some cases, to keep your component from plain breaking).
Not manipulating the DOM goes hand-in-hand with "UI as a function of the data," above.
Invert the data flow
In a large React application, it can be difficult to keep track of which sub-component is managing a certain piece of application data. For this reason, the React team recommends keeping data manipulation logic in a central location. The most straightforward way to do this is to pass callbacks into child components; there's also an architecture developed at Facebook called Flux which has its own website.
Create composable components
A lot of times, it can be tempting to write a large component that manages several pieces of state or several pieces of UI logic. Where possible (and within reason), you should consider breaking larger components into smaller ones that operate on a single piece of data or UI logic. This makes it much easier to extend and move around pieces of your application.
Beware mutable data
Since component state should only be updated via calls to this.setState from within the component, it's helpful to be wary of mutable data. This is doubly true when multiple functions (or components!) might update the mutable object in the same tick; React might try to batch state changes, and you could lose updates! As mentioned in the comments by Eliseu Monar, consider cloning mutable objects before mutating them. React has immutability helpers that can assist.
Another option is to forgo keeping mutable data structures directly in state at all; the Flux pattern, mentioned above, is an interesting take on this idea.
There's a great article on the React site called Thinking in React which goes over how you might take an idea or a mockup and turn it into a React application, and I strongly encourage going over it. As a concrete example, let's take a look at the code you provided. You essentially have one piece of data to manage: a list of content that exists inside the container element. All the changes to your UI can be represented by additions, removals, and changes to that data.
By applying the tenets above, your final application might look something like this:
/** #jsx React.DOM */
var Application = React.createClass({
getInitialState: function() {
return {
content: []
};
},
render: function() {
return (
<div className="container">
<span className="header">jQuery to React.js Header</span>
<button className="add_button"
onClick={this.addContent}>Add Element</button>
<button className="add_button"
onClick={this.timedAddContent}>Add Element in 3 Seconds</button>
{this.state.content.map(function(content) {
return <ContentItem content={content} removeItem={this.removeItem} />;
}.bind(this))}
</div>
);
},
addContent: function() {
var newItem = {className: "added_content", text: "Click to close"},
content = this.state.content,
newContent = React.addons.update(content, {$push: [newItem]});
this.setState({content: newContent});
},
timedAddContent: function() {
setTimeout(function() {
var newItem = {className: "timed_added_content", text: "Click to close"},
content = this.state.content,
newContent = React.addons.update(content, {$push: [newItem]});
this.setState({content: newContent});
}.bind(this), 3000);
},
removeItem: function(item) {
var content = this.state.content,
index = content.indexOf(item);
if (index > -1) {
var newContent = React.addons.update(content, {$splice: [[index, 1]]});
this.setState({content: newContent});
}
}
});
var ContentItem = React.createClass({
propTypes: {
content: React.PropTypes.object.isRequired,
removeItem: React.PropTypes.func.isRequired
},
render: function() {
return <span className={this.props.content.className}
onClick={this.onRemove}>{this.props.content.text}</span>;
},
onRemove: function() {
this.props.removeItem(this.props.content);
}
});
React.renderComponent(<Application />, document.body);
You can see the code in action in this JSFiddle: http://jsfiddle.net/BinaryMuse/D59yP/
The application is made of two components: a top-level component called Application, which manages (in its state) an array called content, and a component called ContentItem, which represents the UI and behavior of a single item from that array. Application's render method returns a ContentItem element for each item in the content array.
One thing to notice is that all of the logic for managing the values inside the content array are handled in the Application component; the ContentItem components are passed a reference to Application's removeItem method, which the ContentItem delegates to when clicked. This keeps all the logic for manipulating state inside the top-level component.

Categories

Resources