Avoid to rerender on every change event in React - javascript

I am working on an existing React code that someone else wrote, and facing some performance issues in it. Consider the following code snippet:
//ComponentA.js
class ComponentA extends React.Component {
this.state = { someValue : 'dummy' }
// Other code
// We are using Babel, so class fields are OK
updateVal = e => this.setState({ someValue : e.target.value})
// fetchData makes an ajax call
fetchData = () => { fetch(this.state.someValue) }
render() {
return (
<ComponentB val={this.state.someValue}
updateVal={this.updateVal}
fetchData={this.fetchData}/>
)
}
//ComponentB.js
class ComponentB extends React.Component {
render() {
return (
// Other code
//Input is a component from a library
<Input onChange={(e) => { this.updateValue(e) } }
onBlur={this.props.fetchData} />
value={this.props.val}
)
}
}
Now the problem is that whenever user types in Input, the value gets printed after a few seconds. This is because ComponentA is actually a pretty big component (I know it's bad, but I don't want to refactor it now as it's huge and we don't have much time) and it gets re-rendered every time user types. To avoid this, I can make Input an uncontrolled component and update the ComponentA's someValue onBlur. Another way is to have an initialState in ComponentB which is equal to the val prop. And onChange, this.setState is called for ComponentB only. Then onBlur, I can update ComponentA's this.state.someValue.
However, in both these approaches, the principle of single source of truth of React is lost. So what will be the best solution in this case?
Here I would also like to ask, what's the harm in using Uncontrolled Component here?

A quickfix could be to debounce the onChange handler.
I usually use debounce from Lodash but you can use another one, or write your own version.
import { debounce } from 'lodash'
class ComponentB extends React.Component {
// componentA will rerender only every 300ms instead of every time user types
handleChange = debounce(e => this.props.updateVal(e), 300)
render() {
return <Input onChange={this.handleChange} value={this.props.val} />
}
}
The best solution would be to split/refactor componentA though.

The re-rendering is optimized (I don't think that the problem is the related to the component size).
I think that the issue is not caused by your structure (That is the one adviced by React itself) but from the Input component taken from the library.
You could also keep the state in the ComponentB and expose function to get/set values but then, as you said, you will lose the single source of trouth but you will avoid re-rendering every input change.
Hope this will help!

Related

React: Storing static variable in state vs render variable

I found a similar discussion to my question here but wanted to dig deeper.
I have yet to find any React documentation that discusses whether static variables (that are unchanging but need to be used for a React view) should be stored as a state variable or be rebuilt each time the component is rerendered.
Let's say that I have a React component that takes in a prop, which is a list, and I want to map this list to a certain output. In my use case I am turning the list into the format for the options my component's select picker will use. Once this list is made, it does not need to change. All options will remain the same for the rest of the component's use.
It feels weird to throw it into state, although it just seems more efficient. Let's go through two approaches.
Approach #1: Store option list in component state.
class Component extends React.Component {
constructor(props) {
this.state = {
options: props.list.map(some => computation)
}
}
render() {
return ( <SelectPicker options={this.state.options} /> )
}
}
Approach #2: Recreate variable upon each rerender.
class Component extends React.Component {
constructor(props) {
...
}
render() {
const options = this.props.list.map(some => computation)
return ( <SelectPicker options={options} /> )
}
}
The latter seems more correct, in that since the options array is never meant to change, it is only meant to be initialized once, and it doesn't make sense to put a watcher on it for being part of state. The former just seems more efficient, as we only compute this value once, rather than recomputing it every single time the component is rerendered. Yes React will compare the state between rerenders, but won't it compare the options list references? Whereas the latter example will completely rebuild a new list? Tbh, neither of these seem like the "clean" approaches to this.
The better option is neither. In your constructor function, set this.options to the mapped array and then access it with this.options in your render method.
class Component extends React.Component {
constructor(props) {
this.options = props.list.map(some => computation);
}
render() {
return ( <SelectPicker options={this.options} /> )
}
}
This avoids creating the same array over and over again. It's perfectly fine to set values on this. directly, the point of state is that if a state variable changes the render method is called again.

React changing text of child on button hover

I want to change the text of a child function component when I hover over a button of my parent class component. I'm having trouble accessing the prop though in the child component and getting null. Any help is appreciated
parent component:
export default class PathfindingVisualizer extends React.Component {
constructor(props) {
super(props)
this.state = {
AlgoDef: null,
};
}
render() {
return (
<React.Fragment>
<div className="button-bar"> //buttons that change state
<button
onClick={() => this.helperDikjstras()}
onMouseOver={() => this.setState({ AlgoDef: "Dikj"})}
>Dikjstras</button>
<button
onClick={() => this.helperAstar()}
onMouseOver={() => this.setState({ AlgoDef: "Astar"})}
>A*</button>
</div>
<div>
<AlgoExplaination algoName={this.AlgoDef} /> //changes its text based on state of parent
</div>
</React.Fragment>
);
}
}
my child component:
export default function AlgoExplaination(props) {
const [text, setText] = useState("default");
useEffect(() => {
switch (props) {
case "Dikj":
setText("Dikjstra");
break;
case "Astar":
setText("Astar");
break;
default:
setText("useEff");
}
//console.log(`text: ${text}`);
//console.log(props);
// console.log(props.algoName);
});
return (
<div>
<p>{text}</p>
</div>
)
}
both console logging props gives me: {algoName: null}. and props.algoName gives me null
As #Rajesh has mentioned in a comment, you are passing props to your AlgoExplaination (sic) component incorrectly like this:
<AlgoExplaination algoName={this.AlgoDef} />
You intended to pass the AlgoDef property of your state, which is this.state.AlgoDef, so change accordingly to this:
<AlgoExplaination algoName={this.state.AlgoDef} />
Furthermore, when you access the algoName property of your props, you currently attempt to access it as if it were the props object itself incorrectly like this:
switch (props) {
The props object for AlgoExplaination will be an object with an algoName property that looks (partially) like this:
{ algoName: "Dikj" }
So, the value you need is actually stored in props.algoName. Therefore, please change accordingly to this:
switch (props.algoName) {
As #Wyck has addressed most of the points, I'll focus this answer on the last point:
Third, why do you have both class component and functional component with hooks? Please use 1 way
Class vs Functional component
In theory, a class component has lifecycle events and state, where as a functional component is just a function that returns JSX.Element. Such components were called stateless.
Because of this, class component had a bit of overhead over functional component, and as a performant option functional component were preferred.
Hooks
Hooks are a way in react to give functional components access to have its own state and few major lifecycle events. This is achieved using closure (not going in full depth) and craftsmanship. This also makes class components obsolete as everything can be achieved in functional component.
Which one to use?
As a preference, its suggested to use functional component with hooks as they are easy to use and are performant as well, in comparison.
Why to use one?
In general programming practice, as a developer, you should use a single way to do things. Benefit of this is, it helps in fast reading.
If I use a for loop in one section and a Array.forEach in next, as a developer, I will be asked to read and understand the purpose. This adds overhead and reduces readability. Having same approach moves the focus of reader to just the logic.
I recomend you to destructuring the props, to be more clear.
In
export default function AlgoExplaination(props)
Can be something like:
export default function AlgoExplaination({algoName})
So you can use it in your switch statment.
switch (algoName) {
case "Dikj":
setText("Dikjstra");
Right now, you are passing all props but you will have to access as props.algoName in the switch statment.

Why aren't my props available to use in my component when I need them?

I keep running into similar issues like this, so I must not fully understand the lifecycle of React. I've read a lot about it, but still, can't quite figure out the workflow in my own examples.
I am trying to use props in a child component, but when I reference them using this.props.item, I get an issue that the props are undefined. However, if the app loads and then I use the React Browser tools, I can see that my component did in fact get the props.
I've tried using componentDidMount and shouldComponentUpdate in order to receive the props, but I still can't seem to use props. It always just says undefined.
Is there something I'm missing in React that will allow me to better use props/state in child components? Here's my code to better illustrate the issue:
class Dashboard extends Component {
state = { reviews: [] }
componentDidMount () {
let url = 'example.com'
axios.get(url)
.then(res => {
this.setState({reviews: res.data })
})
}
render() {
return(
<div>
<TopReviews reviews={this.state.reviews} />
</div>
);
}
}
export default Dashboard;
And then my TopReviews component:
class TopReviews extends Component {
state = { sortedReviews: []}
componentDidMount = () => {
if (this.props.reviews.length > 0) {
this.sortArr(this.props.reviews)
} else {
return <Loader />
}
}
sortArr = (reviews) => {
let sortedReviews = reviews.sort(function(a,b){return b-a});
this.setState({sortedReviews})
}
render() {
return (
<div>
{console.log(this.state.sortedReviews)}
</div>
);
}
}
export default TopReviews;
I'm wanting my console.log to output the sortedReviews state, but it can never actually setState because props are undefined at that point in my code. However, props are there after everything loads.
Obviously I'm new to React, so any guidance is appreciated. Thanks!
React renders your component multiple times. So you probably see an error when it is rendered first and the props aren't filled yet. Then it re-renders once they are there.
The easy fix for this would be to conditionally render the content, like
<div>
{ this.props.something ? { this.props.something} : null }
</div>
I would also try and avoid tapping into the react lifecycle callbacks. You can always sort before render, like <div>{this.props.something ? sort(this.props.something) : null}</div>
componentDidMount is also very early, try componentDidUpdate. But even there, make your that your props are present.
For reference: see react's component documentation

One time action in React Components

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

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