skip to Main Content

What I’m developing: library for plotting charts|

Goal: when updating props in child Curve component, context in CurveTrack must be updated

Problem: when using setAxis in useEffect, following error appears: "Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn’t have a dependency array, or one of the dependencies changes on every render"

How to fix the problem?

Sandbox: https://codesandbox.io/s/fervent-cdn-2h37cd?file=/src/App.tsx

Code:

const CurveTrack: FC<CurveTrackProps> = ({ children, className, ...props }) => {
  const [axes, setAxes] = useState<{ [key: string]: Axis }>({});

  const setAxis = useCallback((axis: Axis) => {
    setAxes((oldAxes) => ({ ...oldAxes, [axis.name]: axis }));
  }, []);

  const deleteAxis = (axis: Axis) => {
    const updatedAxes = { ...axes };
    delete updatedAxes[axis.name];
    setAxes(updatedAxes);
  };

  return (
    <CurveTrackContext.Provider value={{ axes, setAxis, deleteAxis }}>
      <VictoryChart width={1500} height={400}>
        {Object.values(axes).map((axis) => (
          <VictoryAxis key={axis.name} domain={axis.domain} style={{ axis: { stroke: axis.color } }} />
        ))}
        {children}
      </VictoryChart>
    </CurveTrackContext.Provider>
  );
};
const Curve: FC<any> = ({ className, children, ...props }) => {
  const { depth, range: xRange } = useLogView();
  const { setAxis } = useAxes();

  const curve = { ...defaultCurve, ...props };
  const curveData = createChart(depth, curve.data);
  const rangedData = rangeData(curveData, xRange);

  const defaultRange = getRange(rangedData.map((data) => data.y));
  const yRange: Range = { ...defaultRange, ...curve.scope };

  useEffect(() => {
    const domain: ForAxes<DomainTuple> = [yRange.min, yRange.max];
    const axis = { name: curve.name, color: curve.style.color, domain };
    setAxis(axis);
  }, [curve, yRange]);

  return <VictoryLine {...props} data={rangedData} style={{ data: { stroke: curve.style.color } }} />;
};

I’ve tried:

  1. Added and removed setAxis from the array of dependencies
  2. Used useCallback
  3. Passed setAxes directly (without wrappers setAxis and deleteAxis)
  4. Used prevState in setAxes
  5. Split the yRange and curve objects into primitive data variables (name, color, min, max)
  6. Used useMemo for context value

2

Answers


  1. I think I managed to solve the issues by doing the following the next changes:

    First of all, as mentioned in a comment above is not a good idea to put in the useEffect dependencies simple local defined variables because they are recreated at every render so everytime the hook will be triggered causing an infinite loop. To make the current logic work you could put the props directly in the dependencies, but you should make sure there are at least default values in place in case the props are not passed. Another solution would be to transform the curve and yRange variables in state to not be recreated at every render.

    const Curve: FC<any> = ({ className, children, ...props }) => {
      const { depth, range: xRange } = useLogView();
      const { setAxis } = useAxes();
    
      const curve = { ...defaultCurve, ...props };
      const curveData = createChart(depth, curve.data);
      const rangedData = rangeData(curveData, xRange);
    
      const defaultRange = getRange(rangedData.map((data) => data.y));
      const yRange: Range = { ...defaultRange, ...curve.scope };
    
      useEffect(() => {
        const domain: ForAxes<DomainTuple> = [yRange.min, yRange.max];
        const axis = { name: props.name, color: props.style.color, domain };
        setAxis(axis);
      }, [props.name, props.style.color]);
    
      return <VictoryLine {...props} data={rangedData} style={{ data: { stroke: curve.style.color } }} />;
    };
    

    Also in the sandbox you provided I had to give each Curve a name prop because by default you use the defaultCurve constant for both of them and the name provided to the Curves its later used in rendering key for VictoyAxis components in CurveTrack file, and having two components with the same key will cause issues.

        export default function App() {
          return (
            <div className="page">
              <LogView
                depth={depth}
                orientation="horizontal"
                grid={{
                  main: { style: { color: "red" }, interval: 100 },
                  secondary: { style: { color: "red" }, lines: 1 }
                }}
              >
                <CurveTrack>
                  <Curve data={ropAvg} name="test1" style={{ color: "red" }} />
                  <Curve data={actecdx} name="test2" style={{ color: "green" }} />
                </CurveTrack>
              </LogView>
            </div>
          );
        }
    
    Login or Signup to reply.
  2. I’ve added "name" prop to every "Curve" and error is gone
    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search