How change React state if it is multidimensional array and contain objects?

I have dummy data that save in React state.
If I change state the chart also change . It’s ok.
The problem how to drill my data?
The stackedPrimaryXAxis object’s one attribute easy to change.

** But My questions:**

** 1. But stackedCustomSeries background property?**

** 2. But stackedChartData x and y property?**

export const stackedChartData = [
  [
    { x: 'Jan', y: 111.1 },
    { x: 'Feb', y: 127.3 },
    { x: 'Mar', y: 143.4 },
    { x: 'Apr', y: 159.9 },
    { x: 'May', y: 159.9 },
    { x: 'Jun', y: 159.9 },
    { x: 'July', y: 159.9 },
  ],
  [
    { x: 'Jan', y: 111.1 },
    { x: 'Feb', y: 127.3 },
    { x: 'Mar', y: 143.4 },
    { x: 'Apr', y: 159.9 },
    { x: 'May', y: 159.9 },
    { x: 'Jun', y: 159.9 },
    { x: 'July', y: 159.9 },
  ],
];

export const stackedCustomSeries = [

  { dataSource: stackedChartData[0],
    xName: 'x',
    yName: 'y',
    name: 'Budget',
    type: 'StackingColumn',
    background: 'blue',

  },

  { dataSource: stackedChartData[1],
    xName: 'x',
    yName: 'y',
    name: 'Expense',
    type: 'StackingColumn',
    background: 'red',

  },

];


export const stackedPrimaryXAxis = {
 
  minorGridLines: { width: 0 },
  majorTickLines: { width: 0 },
  minorTickLines: { width: 0 },
  interval: 1,
  lineStyle: { width: 0 },
  labelIntersectAction: 'Rotate45',
  valueType: 'Category',
};

export const stackedPrimaryYAxis = {
  lineStyle: { width: 0 },
  minimum: 100,
  maximum: 400,
  interval: 100,
  majorTickLines: { width: 0 },
  majorGridLines: { width: 1 },
  minorGridLines: { width: 1 },
  minorTickLines: { width: 0 },
  labelFormat: '{value}',
}

FreeCodeCamp.js

import React, { useState } from "react";

import {
  stackedCustomSeries,
  stackedPrimaryXAxis,
  stackedPrimaryYAxis,
} from "../../data/dummy";

const FreeCodeCamp = ({ height, width }) => {
  const [customSeries, setCustomSeries] = useState(stackedCustomSeries);
  const [xaxis, setXaxis] = useState(stackedPrimaryXAxis);
  const [yaxis, setYaxis] = useState(stackedPrimaryYAxis);

  const handleClick = () => {
    setXaxis({ ...xaxis, majorGridLines: { width: 0 } });

    setCustomSeries((prevCustomSeries) => {
      //missing part...
      return prevCustomSeries;
    });
  };

  const handleClick2 = () => {
    setXaxis({ ...xaxis, majorGridLines: { width: 1 } });
  };
  return (
    <>
      <h2>MyComponent</h2>
      <div className="m-5">
        <button type="button" onClick={handleClick2}>
          ChangeButton
        </button>
        <div className="mt-3">
          <button type="button" onClick={handleClick}>
            ChangeButton 2
          </button>
        </div>
      </div>
    </>
  );
};

export default FreeCodeCamp;

I’m not completely clear what you are asking. But off the top of my head, this:

    setCustomSeries((prevCustomSeries) => {
      //missing part...
      return prevCustomSeries;
    });

… concerns me.

I don’t know what you plan to do in the “missing part”, but it should not be changing previousCustomSeries. For example, if I wanted to change the "name on the first element, I might do:

    setCustomSeries((prevCustomSeries) => {
      const newCustomSeries = [
        {
          ...prevCustomSeries[0],
          name: 'new name',
        },
        prevCustomSeries[1],
      ]

      return newCustomSeries;
    });

Or something like that. I haven’t tried that out, that’s just off the top of my head, but that’s the idea. You may need to make that dynamic if you want to specify which element you want to alter and if there are variable lengths to the array, whatever.

Do some googling about react and redux and not mutating state. I know that this isn’t redux, but they deal with that a lot too.

1 Like

I’m not familiar with the specific problem, but I had some general thoughts about this.

Multidimensional data structures are hard. I’d consider what your inputs are and what your expected outputs are here, in terms of data structures. One approach might be to visualize the data. Actually draw out the data structure on paper or a whiteboard. Draw a circle around the parts you want to change.

How could you do this if the data wasn’t nested? Unwrap the structure and think about modifying it if it weren’t multidimensional. Now add back in the next level of nesting.

Try doing it with for loops, first. Don’t worry about the spread syntax. It’s kind of magic. After you’ve successfully made the change with nested loops, you can try it with a reducer or spread syntax.

Eventually, you’ll be able to kind of visualize in your mind what needs to happen intuitively to make a change to a nested structure.

And an important point on mutations. If you did try this with for loops, because the data structure is nested, you’ll ultimately need to be careful about mutations. Making a copy of the parent array isn’t enough. Each sub-array also needs to be copied.

This is because of how JavaScript handles object references vs values.

Anyway, for the data transformation here, I’d write down what you have, what you want to change, and what that output needs to look like. Then I’d experiment with drawing the structure and the values on paper. Then I’d try to make the change with nested for loops. Then explore how to achieve that, if you want, with more advanced methods like reducers or the spread operator.

Good luck!

1 Like

I am a little bit confuse. Please show me an example. Thank you

For exmapIe I want stackedChartData[0] y coordinate be 120 and not 111.1.
The stackedChartData[0] is the stackedCustomSeries dataSource property.

The missing part shows where should be write the code.
I return now because don’t want get error.
But this is not the solution.

I not ask stackedCustomSeries outer property for example: name, type, background but I ask the inside property. The stackedCustomSeries have property >> datasource that contain array and objects. I would change those… for example: { x: ‘Jan’, y: 111.1 }, to : { x: ‘Jan’, y: 120 }

I not ask stackedCustomSeries outer property for example: name, type, background but I ask the inside property. The stackedCustomSeries have property >> datasource that contain array and objects. I would change those… for example: { x: ‘Jan’, y: 111.1 }, to : { x: ‘Jan’, y: 120 }

Perhaps here a clear what I would like ask

Don’t put complex objects in useState, you’re going to have a Bad Time, as you are discovering. It’s designed for primitive values (a number, a string, etc). You can use it for objects, but even for simple objects you need to really careful w/r/t updating them.

useReducer is an answer here: it’s designed to handle data that’s a bit more complicated than that for just a basic useState. That still isn’t ideal, you’re going to need same amount of faffing on, it’s just easier to control.

Other answer would be to split the parts into seperate components, so you have an XAxis component and a YAxis component, and then their state is just plain primitive values (you might have some object that contains the initial state). You then adjust each component’s state individually, each is responsible for state closest to it. If there is state global to the whole chart, can use a Context provider as the top-level component in your chart and have that handle the values that affect every child component (e.g. altering the unit of measurement). This is imo the best approach.

Yet another answer is to use a state management library (eg Zustand or Valtio or Redux etc), but that means learning another API

I don’t know, I somewhat disagree with this. I looked through the React docs and can’t find this. I did find in the useReducer docs,

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

So the emphasis is on the “logic”. I keep complex data structures in useState all the time, but I agree that if the logic to update it gets complex (drilling down and changing nested variables, as opposed to just replacing it) it can get unwieldy pretty fast. useReducer is one way to abstract that out. Often I just create a helper function to clone/change the data or abstract it out into a custom hook (and might use useState or useReducer inside that, depending on need). Really, you encounter the same issues, you are just moving it somewhere else - but that is often a good thing.

2 Likes

To directly answer your question, here is how I would handle it if I were using useState:

const { useState } = React;

const RAW_DATA = {
  foo: {
    bar: {
      baz: [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
      ]
    }
  }
};

const App = () => {
  const [data, setData] = useState(RAW_DATA);

  return (
    <div>
      <button
        onClick={() =>
          setData((state) => ({
            ...state,
            foo: {
              ...state.foo,
              bar: {
                ...state.foo.bar,
                baz: [
                  state.foo.bar.baz[0],
                  [
                    ...state.foo.bar.baz[1].slice(0, 2),
                    state.foo.bar.baz[1][2] + 1
                  ],
                  state.foo.bar.baz[2]
                ]
              }
            }
          }))
        }
      >
        Increment last element of second array
      </button>

      <button onClick={() => setData(incrementBaz_2_2)}>
        Increment last element of third array
      </button>

      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

const root = ReactDOM.createRoot(document.getElementById("container"));
root.render(<App />);

const incrementBaz_2_2 = (obj) => ({
  ...obj,
  foo: {
    ...obj.foo,
    bar: {
      ...obj.foo.bar,
      baz: [
        ...obj.foo.bar.baz.slice(0, 2),
        [...obj.foo.bar.baz[2].slice(0, 2), obj.foo.bar.baz[2][2] + 1]
      ]
    }
  }
});

You can see it in a pen, here.

You can see what Dan is talking about - it gets ugly. I suppose a cleaner solution could be to use lodash’s cloneDeep to make a true copy (with a new reference) and then mutate and return that.

But again, to me the issue here isn’t that it is a complex object but that you are trying to deep manipulations on it. If this were a complex object that was changing from one to another (like changing to a different set of book data in a library program), then that wouldn’t be as big of an issue - you just replace the reference with a new one.

I did one putting the logic in the JSX and one putting it in a helper function. As Dan mentions, you can put it in a reducer. You’ll still have to do the same complicated cloning. Again, my tendency would be to put it in a custom hook and just handle it there - getting all that messiness in another file. Would I use useState or useReducer inside the hook? It depends. If I new that it was only one thing I was going to have to manipulate then useState would be a tiny bit less boilerplate. If there is any chance that there will be more things that I have to do, then useReducer starts making more sense.

That’s just my $.02.

1 Like

You’re right, I should have clarified what exactly I meant: useState is fine for anything if you can just blow it away and completely replace it on a state change.

As soon as you have even one level of nesting, and you want to make granular changes, those changes tend to become onerously difficult to implement.

At which point [IME!] normally comes the realisation that I’m trying to do too much in a single object. So I extract the next level of nesting and work on that by itself, becomes a component. And is that is a nested object, extract the children and so on. Basically a process of flattening the structure until it’s actually feasible to work with

With this, for charts that OP seems to want to be [user] adjustable, where it’s

  • easy to describe them using objects,
  • but where those objects get pretty complex,
  • and where the properties can be changed on-the-fly

IMO I’d approach that by either

  1. Splitting the object down into a load of different components, describing it that way
  2. Use a state management solution like Valtio that lets me mutate the object at will, massively simplifying the updates
  3. Combination of those two

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.