How to design my React project better without using shouldComponentUpdate

Viewed 143

I am trying to construct 1-minute candlestick.

I have a component that will continuously passing a number (the trade price) to his child component.

This child component will keep update its state: (High, Low, Open, Close) base on the new number he gets from the parent. (e.g. if the number coming in, is higher than the current this.state.high, it will update this.state.high to the new number) After every minute a setInterval function it will take the states and construct a candle and pass it down to its own children.

the state are: high, low, open, close, newCandle

I got it working by using

shouldComponentUpdate(nextProps:props, nextState:state){
        
        if(this.props !== nextProps)
          this.updateStates(nextProps.newTradePrice); //will update the high, low, open, close state 
        if(JSON.stringify(nextState.nextMinuteCandle) !== JSON.stringify(this.state.nextMinuteCandle)  ) //once the one minute interval is up, there will be a function that will auto set the newCandle state to a new Candle base on the current high, low, open, close state
          return true;
        return false;
}

I read in the document that shouldComponentUpdate should only be used for optimization not to prevent something to reRender. I am using this to prevent reRender and infinite loop.

I've been stuck on this for days, I cant figure out a way to design this better. Any advice on how to design this better?

2nd related question:

In fact I am relying on shouldComponentUpdate for almost ALL my component too. This can't be right. e.g. I have a CalculateAverageVolume child component, that takes in the this.state.newCandle. And update the volume every time the newCandle changes (i.e. every minute)

constructor(props: props) {
  super(props);
  this.state = {
    [...],
    currentAverage: 0,
    showVolume: true
  };
}

onCloseHandler()
{
    this.setState({showVolume: false});
}

updateAvg(newCandleStick: CandleStick){
   //do caluation and use this.setState to update the this.state.currentAverage
}

shouldComponentUpdate(nextProps:props, nextState:state)
{
    if(JSON.stringify(this.props.candleStick) !== JSON.stringify(nextProps.candleStick) || this.state.showVolume !== nextState.showVolume){
        this.updateAvg(nextProps.candleStick);
        return true;
    }
    return false;
}

render() {
    return (
        <>
        {(this.state.showVolume &&
                <IndicatorCard 
                    cardHeader="Volume"
                    currentInfo={this.state.currentAverage.toString()}
                    onCloseHandler={()=>this.onCloseHandler()}>
                </IndicatorCard>
        )}
        </>
    );
}

}

Can someone please teach me how to design this or restructure this? This works perfectly, but doesn't seem like the right way to do it

3 Answers

I would simplify the component like below.

import { useMemo, useState, memo, useCallback } from "react";

function Component({ candleStick }) {
  // use props here to calculate average
  const updateAverage = () => 0; // use candleStick props to calculate avg here
  const [showVolume, setShowVolume] = useState();
  // Compute the average from prop when component re-renders
  // I would also add useMemo if `updateAverage` is an expensive function
  // so that when prop remains same and `showVolume` changes we don't need to calculate it again
  const currentAverage = useMemo(updateAverage, [candleStick]);
  const onCloseHandler = useCallback(() => setShowVolume(val => !val), []);

  return showVolume ? (
    <IndicatorCard
      cardHeader="Volume"
      currentInfo={currentAverage}
      onCloseHandler={onCloseHandler}
    />
  ) : null;
}

// If true is returned, component won't re-render. 
// Btw React.memo by default would do shallow comparison
// But if deep comparison function is required, I would use lodash or other utility to do the check instead of JSON.stringify.
const arePropsEqual = (prev, next) =>
  isEqual(prev.candleStick, next.candleStick);

export default memo(Component, arePropsEqual);

shouldComponentUpdate is usually reserved for discrete events that you can control. Howevr, it seems like you are dealing with a continuous stream of data.

Two ways to handle it:

  • Pass down a function reference that handles a stream to the child component and let that handle you state updates in your child component.
  • Use the context API to inform child component about the changes

Reference implementation :

I hit the same spot you're in when starting React. The problem here is that React, at least the basic aspects of it, isn't enough when you're talking about data flow. What you need to look into is a React data management framework, of which Redux is probably the most popular. Go look at Redux and make sure you're looking at the latest documentation based around hooks. You'll say to yourself "Oh! That makes perfect sense" - I know I did.

Other, similar frameworks are React Query and React's own Context API. The main point I'm trying to make is that you really need data management to do the thing you're looking for.

Related