One time action in React Components - javascript

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} />
}

Related

Render only components with changes

I have an array with thousands of strings and is passed to a component:
Main component:
const array = ['name1', 'name2', 'name3'];
const [names, setNames] = useState(array);
const onClick = (index) => {
setNames(names.map((name, i) => {
if (i === index) {
return 'name changed';
}
};
};
return (
<ul>
{array.map((name, index) => (
<li key={index}>
<ShowName name={name} key={index} onClick={() => onClick(index)} />
</li>
)}
</ul>
);
ShowName component:
let a = 0;
export default function ShowName({ name, onClick }) {
a += 1;
console.log(a);
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
There's also a button which changes a name randomly. But whenever the button is pressed, all the ShowName components are rerendering. I've been trying to use useCallback and useMemo, but the components are still rerendering x times (x is the length of the array).
const ShowNameHoc = ({ name }) => {
return <ShowName name={name} />
};
return (
<div>
{array.map((name, index) => <ShowNameHoc name={name} key={index} />)}
</div>
);
What should I do if I only want to rerender the component with a change?
You have a misunderstanding in the concepts here. The default is for React to call render on all children, regardless of whether the props changed or not.
After that happened, React will compare that new Virtual DOM to the current Virtual DOM and then only update those parts of the real DOM that changed.
That's why the code in a render method should be quick to execute.
This behavior can be changed by using features like useMemo, PureComponents or shouldComponentUpdate.
References:
https://reactjs.org/docs/rendering-elements.html (Bottom):
Even though we create an element describing the whole UI tree on every tick, only the text node whose contents have changed gets updated by React DOM.
https://reactjs.org/docs/optimizing-performance.html#avoid-reconciliation
Even though React only updates the changed DOM nodes, re-rendering still takes some time. In many cases it’s not a problem, but if the slowdown is noticeable, you can speed all of this up by overriding the lifecycle function shouldComponentUpdate, which is triggered before the re-rendering process starts.
...
In most cases, instead of writing shouldComponentUpdate() by hand, you can inherit from React.PureComponent. It is equivalent to implementing shouldComponentUpdate() with a shallow comparison of current and previous props and state.
Also, read this for some more background info: https://dev.to/teo_garcia/understanding-rendering-in-react-i5i
Some more detail:
Rendering in the broader sense in React means this (simplified):
Update existing component instances with the new props where feasible (this is where the key for lists is important) or create a new instance.
Calling render / the function representing the component if shouldComponentUpdate returns true
Syncing the changes to the real DOM
This gives you these optimization possibilities:
Ensure you are reusing instances instead of creating new ones, e.g. by using a proper key when rendering lists. Why? New instances always result in the old DOM node to be removed from the real DOM and a new one to be added. Even when unchanged. Reusing an instance will only update the real DOM if necessary. Please note: This has no effect on whether or not render is being called on your component.
Make sure your render method doesn't do heavy lifting or if it does, memoize those results
Use PureComponents or shouldComponentUpdate to prevent the call to render altogether in scenarios where props didn't change
Answering your specific question:
To actually prevent your ShowName component from being rendered - into the Virtual DOM - if their props changed, you need to perform the following changes:
Use React.memo on your ShowName component:
function ShowName({ name, onClick }) {
return (
<button type="button" onClick={onClick}>{name}</button>
);
}
export default memo(ShowName);
Make sure the props are actually unchanged by not passing a new callback to onClick on each render of the parent. onClick={() => onClick(index)} creates a new anonymous function every time the parent is being rendered.
It's a bit tricky to prevent that, because you want to pass the index to this onClick function. A simple solution is to create another component that is passed the onClick with the parameter and the index and then internally uses useCallback to construct a stable callback. This only makes sense though, when rendering your ShowName is an expensive operation.
That is happening because you are not using the key prop on <ShowName/> component.
https://reactjs.org/docs/lists-and-keys.html
it could look something like this
return (
<div>
{array.map(name => <ShowName key={name} name={name} />)}
</div>
);

Why does render not erase the state of children in Reactjs?

According to the react documentation,
when setState is called by the user, render is soon after called "behind the scenes". render usually returns a JSX element (though a couple other types are allowed). My question is, since render creates a new JSX element each time, and since setState called on a certain component then calls render on the same component, how is the old state of the components children retained?
I think this question is clear in and of itself,
but to illustrate exactly what i mean i will give a toy example.
export class App extends Component {
constructor (props) {
super(props);
this.state = {
foo: [null]
};
this.addToFoo = this.addToFoo.bind(this);
}
addToFoo() {
this.setState(state => {
return {foo: [...state.foo, null]};
});
}
render() {
return (<div onClick={this.addToFoo}>{this.state.foo.map( el => (<Bar></Bar>))}</div>);
}
}
with Bar defined as
export class Bar extends Component {
constructor(props) {
super(props);
this.state = {color: "#111111"};
this.changeColor = this.changeColor.bind(this);
}
changeColor() {
this.setState(state => {
if (state.color === "#111111")
return { color: "#dddddd"}
else
return { color: "#111111"};
})
}
render() {
return (
<div style={{color: this.state.color}} onClick={this.changeColor}>
Howdy!
</div>
);
}
}
Here, when the child is clicked, it toggles its color between a light gray and a dark gray.
When the parent is clicked it changes its state by adding an element to foo.
render is then called on parent behind the scenes, which returns one JSX element that contains foo.length child components of type Bar. However, somehow, the previous state of the children is retained (meaning the colors of the previously included children stay the same, and only the new one is default constructed), even though the foo.length components were newly returned and did not reference the old children at all. How is this possible?
Sandbox
React does lots of work to check if new render returns absolutely new component, or if it is the same component. Their diffing algorithm include comparisons and some heuristics for this, but you are not required to know in details how it works, since it can change internally. In your particular case this work may result in creating absolutely new component instead of reusing previous, because you don't use keys.
More in-depth info on diffing you can read in react documentation
render() is indeed called "behind the scenes" when there is a change to the state of the parent element. But it doesn't simply instantiate all the class components again and output the result of the render() call to the browser.
Instead, React maintains a virtual DOM, a copy of the DOM visible in the browser. It compares the output of the render() call to that virtual DOM, and it is only the changes that will be output to the real DOM.
As part of carrying out this comparison, for each component it will determine if it is the same component that was there in the previous render by comparing to the virtual DOM (exactly how it does this would require a close inspection of the algorithm).
If a class component is determined to be the same component as before, it will not instantiate it again but instead call the component's render() method using the same class instance and thus the same state as before. (As this documentation explains, a class component's constructor is only called when it is mounted.)
In this way children class components can retain state.

Functions inside and outside render()

I just begun to learn React and JavaScript in general. After I read the documentation and tutorials, I took a look at example projects and try to sort out what I didn't get yet.
And then I saw that there are functions that are defined inside the render()functions, and some that are outside of the render() function.
E.g. for outside of render():
handleClick(e) {
e.preventDefault();
e.target.parentElement.classList.toggle('open');
}
and inside render()...
const divider = (divider, key) => {
const classes = classNames( 'divider', divider.class);
return (<li key={key} className={ classes }></li>);
};
Why do they look so different and why would you like to have some functions inside and some outside of render()?
EDIT:
Another example for a function outside of render():
hideMobile() {
if (document.body.classList.contains('sidebar-mobile-show')) {
document.body.classList.toggle('sidebar-mobile-show')
}
}
EDIT2: In another thread someone answered that, if the logic behind the function is heavy, it should be outside of render(). But why would you like to have function inside render() anyways?
render() is called everytime the state change. So every function that is kept inside render function will be created as a new function each time the state change. Which means that divider will be created newly every time react re-renders.
handleClick is a normal object function.
Functions written inside render function are usually those dealing with rerendering of components.
From example on official site:
First, if we want to build a Clock in the beginning, this how we tried to make a component that is object-oriented, maintainable factor with a stateless function object.
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}
setInterval(tick, 1000);
quote from doc
to make the Clock component truly reusable and encapsulated. It will
set up its own timer and update itself every second.
...
Ideally we want to write this once and have the Clock update itself...
So here is the spirit of React, we want to convert this function object to a class, which could maintain itself, so now we involve render() in, more specifically we involve stateful component in:
Add a single empty method to it called render()
...
Clock is now defined as a class rather than a function.
Then we get:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
this.clockCore = this.clockCore.bind(this);
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
tick() {
this.setState({
date: new Date()
});
}
clockCore() {
return (<div>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>);
}
render() {
return this.clockCore();
}
}
As you known, render() be triggered again an again if the state of component need to be refreshed by setState().
Update
In my opinion, it's unnecessary to define the function in render().
I have made a little revisions to the original example above to show that.
From example you provided, the usage of divider may be like:
const divider = (divider, key) => {
const classes = classNames( 'divider', divider.class);
return (<li key={key} className={ classes }></li>);
};
return (<ul>{this.state.dividerList?
this.state.dividerList.forEach(divider) : null}</ul>);
I think the reason of this is just for maintainability, someone may want all DOM creating code inside the render() for easy tracing when DOM structure returned is really complicated (and arrow function is lightweight), but as I said it's subjective, it really could be defined outside.
In this kind of case, I used to do below instead, and it seems what you provided is more elegant, but if you defined that function outside of render(), things become distractive to me.
let dividers = [];
if (this.state.dividerList) {
this.state.dividerList.forEach((divider, key) => {
let classes = classNames( 'divider', divider.class);
dividers.push((<li key={key} className={ classes }></li>));
});
}
return (<ul>{dividers}</ul>);
So another function you provided which aims at DOM manipulations feature is totally proper and well to be defined outside.
Besides that, handleClick is a function created and accessible for every Object/Component and divide is a locally scoped function, their functionality can be pretty much the same. However, divider will be created as new function on every render which might have later performance implications, while handleClick is created once for a defined component (Object).

Conditionally rendering a react component

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
)
}

React: update one item in a list without recreating all items

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.

Categories

Resources