How to update state in a parent where all children update simultaneously

Viewed 22

I'm working with a graph (recharts) in React. I need to find the positions of the x-axis ticks so that I can find where to draw a line representing today's date. To do this I pass state to the CustomizedTick component and attempt to add the x-axis value of each one to the state of the parent. However, when the code runs only one of the tick values makes it into the parent state. So at the end 'bars' is something like {4: 352} but I would expect to get something like {0: 32, 1: 98, 2: 203, 3, 278, 4: 352}

import React, { FC, useEffect } from 'react';

export const CustomizedTick: FC<any> = (props: any) => {
  const { x, y, payload, index, barStates, setBars } = props;

  useEffect(() => {
    setBars({ ...barStates, [index]: x - 60 });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [x]);
  const parts = payload.value.split(SPLIT_PARAM);

  return (
    <g transform={`translate(${x},${y})`}>
      <text style={{ fontSize: '10px' }} x={0} y={0} dy={16} fill="#666">
        <tspan textAnchor="middle" x="0">
          {parts[0]}
        </tspan>
        <tspan textAnchor="middle" x="0" dy="20">
          {parts[1].slice(4)}
        </tspan>
      </text>
    </g>
  );
};

import React, { FC, useEffect, useMemo, useState } from 'react';
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  ResponsiveContainer,
  ReferenceLine,
  LabelList,
} from 'recharts';

const Graph = () => {
const splitData = [
    {
        "createdAt": "2022-08-12T09:39:31.796234+00:00",
        "creditorcode": "CRED1",
        "costamount": 10,
        "costinvoicenumber": "100001",
        "type": "cost",
        "updatedAt": "2022-08-12T09:39:31.796238+00:00",
        "displayDate": "2022-08-17T14:51:59.014Z"
    },
    {
        "createdAt": "2022-08-12T09:39:31.796234+00:00",
        "debtorcode": "DEBT1",
        "sellamount": 11.5,
        "sellcurrency": "USD",
        "sellinvoicedate": "2022-02-01 00:00:00",
        "type": "sell",
        "updatedAt": "2022-08-12T09:39:31.796238+00:00",
        "displayDate": "2022-08-17T14:51:59.014Z"
    },
    {
        "createdAt": "2022-08-12T09:46:38.379872+00:00",
        "debtorcode": "DEBT2",
        "sellamount": 57.5,
        "sellcurrency": "USD",
        "type": "sell",
        "updatedAt": "2022-08-12T09:46:38.379875+00:00",
        "displayDate": "2022-08-17T14:51:59.014Z"
    },
    {
        "createdAt": "2022-08-12T09:46:38.379872+00:00",
        "creditorcode": "CRED3",
        "costamount": 30,
        "costinvoicenumber": "100003",
        "type": "cost",
        "updatedAt": "2022-08-12T09:46:38.379875+00:00",
        "displayDate": "2022-08-17T14:51:59.014Z"
    }
]

const [bars, setBars] = useState<any>({});

 useEffect(() => {
    const dateNow: Date = new Date(Date.now());
    let dataItemIndex: number | string;
    for (dataItemIndex in splitData) {
      if (!bars[dataItemIndex] || bars[dataItemIndex] === 0) {
        break;
      }

      if (
        splitData[dataItemIndex].displayDate &&
        // @ts-ignore
        splitData[dataItemIndex].displayDate > dateNow
      ) {
        if (parseInt(dataItemIndex) === 0) {
          setCurrentDate(15);
          break;
        }
        const col2 = bars[dataItemIndex];
        const col1 =
          parseInt(dataItemIndex) > 0 ? bars[parseInt(dataItemIndex) - 1] : 0;
        setCurrentDate((col2 + col1) / 2);
        break;
      }
    }
  }, [bars, splitData]);
           return ( <BarChart
              width={width}
              height={height}
              data={data}
              margin={{
                top: 20,
                right: 20,
                left: 0,
                bottom: 20,
              }}
            >
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis
                dataKey="name"
                type="category"
                dy={16}
                tick={<CustomizedTick barStates={barStates} />}
                minTickGap={0}
                interval={0}
              />
              <YAxis type="number" domain={['auto', 'auto']} />
              <ReferenceLine
                y={0}
                label={
                  currentDate !== 0 ? (
                    <TimeLine
                      xoffset={currentDate}
                      yheight={1200}
                      stroke={'#0079FF'}
                      strokeWidth={2}
                      strokeOpacity={1}
                    />
                  ) : undefined
                }
                stroke={colors.black5}
              />
              <Bar dataKey="y">
                <LabelList
                  fill={colors.black3}
                  position="insideTop"
                  formatter={(v: number) => v.toFixed(2)}
                  offset={8}
                />
              </Bar>
            </BarChart> )
}
1 Answers

Ok dummy alert! I totally forgot that setState can be passed a function in which case it queues the calls to setState so they will happen synchronously. So simply changing:

setBars({ ...barStates, [index]: x - 60 });

to

setBars((state: any) => ({ ...state, [index]: x - 60 }));

Works a charm.

Related