For a component that is hidden at some point in its lifecycle, what is the best way to render it?
1) render the component, but do not show it (display:none).
2) render the component only when needed.
What is better for performance? If the component's props and state update later, would it be better to have the component present, but hidden in the virtual DOM?
render() {
return (
<div style={{display: this.props.visible ? 'block' : 'none'}}>
<RestofComponentHere />
</div>
);
}
or this:
render() {
if (!this.props.visible) {
return null;
}
return (
<div>
<RestofComponentHere />
</div>
);
}
Just go with what works best for that situation. Sometimes it's method 1 and sometimes method 2. If you find out that it's slowing down your app then it's pretty easy to convert to method 1, but this should only if happen if you are conditionally rendering a ton of times. When you have a ref to the component then you want to always render it so that you can access the ref in componentDidMount and so you would have it hidden.
First method is faster as shown in answer here but don't do micro optimizations for the sake of it if conditionally rendering is cleaner code.
I'v used a mixture in my apps.
I would suggest to go with the state values and have a condition based on state values to decide whether the component to be shown or hidden.
constructor(props){
//Set some initial condition for display depending on prop or default value
//Something similar to this:
display: props.toDisplay === undefined ? true : props.toDisplay
}
componentDidMount(){
//update the state depending on the response or change
}
onClick(event){
//It might depend on someOther component's click action.
}
The render method would just have the following:
render(){
const toDisplay = this.state.display
if(toDisplay &&
// Component To render
)
}
Related
So i have this react component which contains a conditional rendering in some part. so far so good and this practice has been acting as expected all throughout my app. but surprisingly the element we're talking is not getting hidden as a result of change in condition.let me provide you with a minimal representation of relevant code because the original component is too lengthy and cumbersome.
import React from 'react';
import AnotherComponent from '../components/AnotherComponent';
export class TheComponent extends Component {
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === 'AAA';
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent(this.props.data) && <AnotherComponent />}
</div>
</div>
);
}
}
export default TheComponent;
Unfortunately creating a fully working sample is a bit hard, considering all the third party dependencies and the redux wiring. Nevertheless, if you have ever run into similar situation and got to the bottom of it, please share your experience with me.
note: Updated props.data is passed to the component normally. in the react dev tools it shows up and in redux dev tools history of state is quite healthy. the only problem here is that the conditional won't hide the element in a falsy state.
UPDATE. the reason for this weird rendering was a dynamic loop in the same component rendering this AnotherComponent regardless of the flag value. what made it hard to pin down was that it was rendering it in map and passing index of dynamic string content. anyhow, thank you all and sorry for the possible misleading question.
It is working fine here.
You should check the data in your props if there is any flag key or not and if there is a flag key check that if it is AAA or not.
class App extends React.Component {
constructor() {
super();
this.state = {
data: "aa"
};
}
/*
1. props.data is coming from mapping component state to redux connect
2. connect file and selectors are alright, because other parts of this component
work as expected, and even same props.data is used elsewhere in the component
3. a method wihtout input as in showAnotherComponent = () => false; will hide the
AnotherComponent element successfully. but even sth like
showAnotherComponent = (data) => false; will not work!!!
4. props.data is properly injected to the render method, console.log(props.data) in reder
method will display updated value.
5. props.data is never null or undefined and so on ..
*/
showAnotherComponent = data => data.flag === "AAA";
render() {
console.log(this.props.data); // will show the updated props.data
return (
<div className="row">
<div className="col-md-10">
<h1>Some heading</h1>
</div>
<div className="col-md-2">
{/* the element in next line will always show up invariably, whatever the
content of props.data. tried ternary, same result. */}
{this.showAnotherComponent({ flag: this.state.data }) && (
<div>asdsdasd</div>
)}
</div>
<button onClick={() => this.setState({ data: "AAA" })}>Toggle</button>
</div>
);
}
}
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Click on Toggle and the Data will be displayed.
So let's say there is acomponent which displays 2 child components: a document list and the selected document. By default the selected document component is not rendered, only when a document is selected from the list. And i also want this whole thing work when a new document is selected from the list.
There is a state which holds the document content and responsible for the selected document rendering, so i thought i'm going to set it to null in the method which handles the list item selection in order to unmount the previously created child component. Like this (excerpts from the parent class):
handleResultListItemClick(docname) {
if (this.state.sectioncontainer != null) this.setState({sectioncontainer: null},()=>{console.log("muhoo");});
var selected_doc = this.state.resultlist.filter((doc) => {
return docname === doc.properties.title;
});
this.setState({sectioncontainer: selected_doc[0].content.sections},()=>{console.log("boohoo");});
}
...
render() {
return (
...
{this.state.sectioncontainer != null && <SectionContainer listOfSections={this.state.sectioncontainer}/>}
);
}
The only problem is that state handling is not fast enough (or something) in react, because putting the state nullification and its new value setting in the same method results in no change in ReactDOM.
With the above code, the component will be created when the parent component first rendered, but after selecting a new doc in the list results in no change.
How should i implement this in way which works and also elegant?
I found this: ReactDOM.unmountComponentAtNode(container) in the official react docs. Is this the only way? If yes, how could i get this container 'name'?
Edit:
Based on the answers and thinking the problem a bit more through, i have to explain more of the context.
As kingdaro explained, i understand why there is no need to unmount a child component on a basic level, but maybe my problem is bit more sophisticated. So why did i want to unmount the child?
The documents consist of several subsections, hence the document object which is passed to the child component is an array of objects. And the document is generated dynamically based on this array the following way (excerpt from the SectionContainer class which is responsible to display the document):
buildSectionContainer() {
return this.props.listOfSections.map((section, index) =>
{
if (section.type === 'editor') return (
<QuillEditor
key={index}
id={section.id}
modules={modules}
defaultValue={section.content}
placeholder={section.placeholder}
/>
);
else if (section.type === 'text') return (
<div key={index}>{section.value}</div>
);
}
);
}
render() {
return (
<div>
{this.buildSectionContainer()}
</div>
);
}
The SectionContainer gets the array of objects and generate the document from it according to the type of these sections. The problem is that these sections are not updated when a different doc is selected in the parent component. I see change only when a bigger length array is passed to the child component. Like the firstly selected doc had an array of 2 elements, and then the newly selected doc had 3 elements array of sections and this third section is added to the previously existing 2, but the first 2 sections remained as they were.
And that’s why i though it’s better to unmount the child component and create a new one.
Surely it can happen that i miss something fundamental here again. Maybe related to how react handles lists. I just dont know what.
Edit2:
Ok, figured out that there is a problem with how i use the QuillEditor component. I just dont know what. :) The document updates, only the content of QuillEditors doesnt.
The reason your current solution doesn't actually do anything is because React's state updates are batched, such that, when setState is called a bunch of times in one go, React "combines" the result of all of them. It's not as much of a problem with being "not fast enough" as it is React performing only the work that is necessary.
// this...
this.setState({ message: 'hello', secret: 123 })
this.setState({ message: 'world' })
// ...becomes this
this.setState({ message: 'world', secret: 123 })
This behavior doesn't really have much to do with the problem at hand, though. As long as your UI is a direct translation of state -> view, the UI should simply update in accordance to the state.
class Example extends React.Component {
state = {
documentList: [], // assuming this comes from the server
document: null,
}
// consider making this function accept a document object instead,
// then you could leave out the .find(...) call
handleDocumentSelection = documentName => {
const document = this.state.documentList.find(doc => doc.name === documentName)
this.setState({ document })
}
render() {
const { document } = this.state
return (
<div>
<DocumentList
documents={this.state.documentList}
onDocumentSelection={this.handleDocumentSelection}
/>
{/*
consider having this component accept the entire document
to make it a little cleaner
*/}
{document && <DocumentViewer document={document.content.sections} />}
</div>
)
}
}
I have the following "Buy" button for a shopping cart.
I also have a component called Tooltip, which will display itself for error/success messages. It uses the button's width to determine it's centre point. Hence, I use a `ref since I need to access it's physical size within the DOM. I've read that it's bad news to use a ref attribute, but I'm not sure how else to go about doing the positioning of a child component that is based off the physical DOM. But that's another question... ;)
I am persisting the app's state in localStorage. As seen here:
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
The issue I'm running into is that I have to clear the state's success property before rendering. Otherwise, if I have a success message in the state, on the initial render() the Tooltip will attempt to render as well. This won't be possible since the button it relies on is not yet in the DOM.
I thought that clearing the success state via Redux action in componentWillMount would clear up the success state and therefore clear up the issue, but it appears that the render() method doesn't recognize that the state has been changed and will still show the old value in console.log().
My work-around is to check if the button exists as well as the success message: showSuccessTooltip && this.addBtn
Why does render() not recognize the componentWillMount() state change?
Here is the ProductBuyBtn.js class:
import React, { Component } from 'react';
import { connect } from 'react-redux'
// Components
import Tooltip from './../utils/Tooltip'
// CSS
import './../../css/button.css'
// State
import { addToCart, clearSuccess } from './../../store/actions/cart'
class ProductBuyBtn extends Component {
componentWillMount(){
this.props.clearSuccess()
}
addToCart(){
this.props.addToCart(process.env.REACT_APP_SITE_KEY, this.props.product.id, this.props.quantity)
}
render() {
let showErrorTooltip = this.props.error !== undefined
let showSuccessTooltip = this.props.success !== undefined
console.log(this.props.success)
return (
<div className="btn_container">
<button className="btn buy_btn" ref={(addBtn) => this.addBtn = addBtn } onClick={() => this.addToCart()}>Add</button>
{showErrorTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--error'} messageObjects={this.props.error} />
}
{showSuccessTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--success'} messageObjects={{ success: this.props.success }} />
}
</div>
);
}
}
function mapStateToProps(state){
return {
inProcess: state.cart.inProcess,
error: state.cart.error,
success: state.cart.success
}
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (siteKey, product_id, quantity) => dispatch(addToCart(siteKey, product_id, quantity)),
clearSuccess: () => dispatch(clearSuccess())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductBuyBtn)
Well, it seems to be a known problem that's easy to get into (harder to get out of, especially in a nice / non-hacky way. See this super-long thread).
The problem is that dispatching an action in componentWillMount that (eventually) changes the props going in to a component does not guarantee that the action has taken place before the first render.
So basically the render() doesn't wait for your dispatched action to take effect, it renders once (with the old props), then the action takes effect and changes the props and then the component re-renders with the new props.
So you either have to do what you already do, or use the components internal state to keep track of whether it's the first render or not, something like this comment. There are more suggestions outlined, but I can't list them all.
I have a question regarding "one time actions" in react components. Imagine for example I want to scroll some element to certain position, or reset the internal react state.
So far I've been doing this by using a combination of a boolean flag (e.g. doAction: true) and an update action (e.g. setDoActionBackToFalse), but this seems too complex. Does anyone have any nice solution to this?
Note: The action can actually happen multiple times during the lifetime of the component but each time it has to be specifically triggered and happen only once (not keep happening on every rerender). E.g. scroll to every newly added item in scrollpane.
I created small fiddle to make the problem more obvious:
https://jsfiddle.net/martinkadlec/et74rkLk/1/
This uses the boolean flag approach.
It has been some time since I asked this question and since then I found that as long as the "one time action" doesn't actually rerender the component, but instead just modifies some browser state (e.g. focus, scroll position, etc.) people generally tend to solve this by having a class method and calling it from the parent component using refs.
To illustrate on the focus example:
class Input extends React.Component {
inputElRef = React.createRef();
focus = () => this.inputElRef.current.focus();
render() {
return (
<input ref={this.inputElRef} />
);
}
}
class Parent extends React.Component {
inputRef = React.createRef();
render() {
return (
<div>
<button onClick={() => this.inputRef.current.focus()}>Focus input</button>
<Input ref={this.inputRef} />
</div>
);
}
}
I think that you can use componentDidMount lifecycle hook. This hook is invoked only once immediately after a component is mounted and the DOM can be accessed in it.
You can also call your 'one time action' in component constructor but it's called before component is mounted and before initial render so you can't access DOM there.
So you can initialize component state in a constructor (according to React docs: constructor is the right place to initialize state) but you can't scroll some element to certain position in constructor because you can't access component DOM elements in it.
Summing up: state initialization should be done in constructor while 'one time actions' manipulating DOM should be done in componentDidMount.
Wrap your action handlers inside a higher order function which invokes them only once. Lodash has once. Ramda has it too.
Updates for your scrolling scenario.... Scrolling is a side effect which must be initiated by the DOM API. You can write an HOC which wraps any component inside it -
function OnFocusExtender(Wrapped){
return class ExtendedFocus{
focus = _.once(elem => elem && elem.focus && elem.focus());
render(){
return <Wrapped ref={this.focus} {...this.props} />;
}
}
}
Then you can use it in your code like -
render(){
let FocusedComponent = FocusExtender(YourComponent);
return <FocusedComponent a={"blah"} b={blah} />
}
Updated for a generic side-effects approach:
The HOC:
function WelcomingParty(...party)=>(Wrapped)=>{
return class ExtendWelcome{
// Every host in the welcoming party greets
// the guest :)
welcome = (ref) => party.forEach(host => host(ref));
render(){
return <Wrapped ref={this.welcome} {...this.props} />;
}
}
}
Usage:
let hostFn = (fn)=>(ref)=> ref && (typeof ref[fn] == "function") && ref[fn](),
hosts = ["focus", "scrollIntoView"].map(hostFn);
render(){
let Component = WelcomingParty(...hosts)(YourComponent);
return <Component a={"blah"} b={blah} />
}
Let's say I have a list of 1000 items. And I rendering it with React, like this:
class Parent extends React.Component {
render() {
// this.state.list is a list of 1000 items
return <List list={this.state.list} />;
}
}
class List extends React.Component {
render() {
// here we're looping through this.props.list and creating 1000 new Items
var list = this.props.list.map(item => {
return <Item key={item.key} item={item} />;
});
return <div>{list}</div>;
}
}
class Item extends React.Component {
shouldComponentUpdate() {
// here I comparing old state/props with new
}
render() {
// some rendering here...
}
}
With a relatively long list map() takes about 10-20ms and I can notice a small lag in the interface.
Can I prevent recreation of 1000 React objects every time when I only need to update one?
You can do it by using any state management library, so that your Parent doesn't keep track of this.state.list => your List only re-renders when new Item is added. And the individual Item will re-render when they are updated.
Lets say you use redux.
Your code will become something like this:
// Parent.js
class Parent extends React.Component {
render() {
return <List />;
}
}
// List.js
class List extends React.Component {
render() {
var list = this.props.list.map(item => {
return <Item key={item.key} uniqueKey={item.key} />;
});
return <div>{list}</div>;
}
}
const mapStateToProps = (state) => ({
list: getList(state)
});
export default connect(mapStateToProps)(List);
// Item.js
class Item extends React.Component {
shouldComponentUpdate() {
}
render() {
}
}
const mapStateToProps = (state, ownProps) => ({
item: getItemByKey(ownProps.uniqueKey)
});
export default connect(mapStateToProps)(Item);
Of course, you have to implement the reducer and the two selectors getList and getItemByKey.
With this, you List re-render will be trigger if new elements added, or if you change item.key (which you shouldn't)
EDIT:
My inital suggestions only addressed possible efficiency improvements to rendered
lists and did not address the question about limiting the re-rendering
of components as a result of the list changing.
See #xiaofan2406's answer for a clean solution to the original question.
Libraries that help make rendering long lists more efficient and easy:
React Infinite
React-Virtualized
When you change your data, react default operation is to render all children components, and creat virtual dom to judge which component is need to be rerender.
So, if we can let react know there is only one component need to be rerender. It can save times.
You can use shouldComponentsUpdate in your list component.
If in this function return false, react will not create vitual dom to judge.
I assume your data like this [{name: 'name_1'}, {name: 'name_2'}]
class Item extends React.Component {
// you need judge if props and state have been changed, if not
// execute return false;
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.name === this.props.name) return false;
return true;
}
render() {
return (
<li>{this.props.name}</li>
)
}
}
As react just render what have been changed component. So if you just change one item's data, others will not do render.
There are a few things you can do:
When you build, make sure you are setting NODE_ENV to production. e.g. NODE_ENV=production npm run build or similar. ReactJS performs a lot of safety checks when NODE_ENV is not set to production, such as PropType checks. Switching these off should give you a >2x performance improvement for React rendering, and is vital for your production build (though leave it off during development - those safety checks help prevent bugs!). You may find this is good enough for the number of items you need to support.
If the elements are in a scrollable panel, and you can only see a few of them, you can set things up only to render the visible subset of elements. This is easiest when the items have fixed height. The basic approach is to add firstRendered/lastRendered props to your List state (that's first inclusive and last exclusive of course). In List.render, render a filler blank div (or tr if applicable) of the correct height (firstRendered * itemHeight), then your rendered range of items [firstRendered, lastRendered), then another filler div with the remaining height ((totalItems - lastRendered) * itemHeight). Make sure you give your fillers and items fixed keys. You then just need to handle onScroll on the scrollable div, and work out what the correct range to render is (generally you want to render a decent overlap off the top and bottom, also you want to only trigger a setState to change the range when you get near to the edge of it). A crazier alternative is to render and implement your own scrollbar (which is what Facebook's own FixedDataTable does I think - https://facebook.github.io/fixed-data-table/). There are lots of examples of this general approach here https://react.rocks/tag/InfiniteScroll
Use a sideways loading approach using a state management library. For larger apps this is essential anyway. Rather than passing the Items' state down from the top, have each Item retrieve its own state, either from 'global' state (as in classical Flux), or via React context (as in modern Flux implementations, MobX, etc.). That way, when an item changes, only that item needs to re-render.
One way to avoid looping through the component list every render would be to do it outside of render function and save it to a variable.
class Item extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item;
}
render() {
return <li>{this.props.item}</li>;
}
}
class List extends React.Component {
constructor(props) {
super(props);
this.items = [];
this.update = this.update.bind(this);
}
componentWillMount() {
this.props.items.forEach((item, index) => { this.items[index] = <Item key={index} item={item} /> });
}
update(index) {
this.items[index] = <Item key={index} item={'item changed'} />
this.forceUpdate();
}
render() {
return <div>
<button onClick={() => { this.update(199); }}>Update</button>
<ul>{this.items}</ul>
</div>
}
}
There is a way to do this, but I don't think the React team would call it Kosher so to speak. Basically, instead of the parent having state, it has lists of refs. Your components have their own state. Your components are created once in the parent and stored in a parent's ref property which holds an array of all those components. So the components are never recreated on each rerender, and instead are persisted. You also would need a list of refs that attach to a function in each component to allow the parent to call individual components (in hooks you can use imperativehandle to do this).
Now, when the user does something that would cause the data to change for a specific component in that list, you would find that component's ref in the list of attached functions and call the function on it. The component could then update and rerender itself based off that function call from its parent, without other components being affected/recreated/rerendered.
I believe this is called imperative programming rather than declarative, and React doesn't like it. I personally have used this technique in my own projects for similar reasons to you, and it worked for me.