Home > Enterprise >  ReactJS and creating ‘object of objects’ using state hooks?
ReactJS and creating ‘object of objects’ using state hooks?

Time:10-24

I'm trying to create an 'object of objects' in ReactJS using state hooks, but I'm unsure how to dynamically create it based on the data coming in.

The data arrives on a websocket, which I have placed in a Context and is being used by the component in question. The JSON data hits the onmessage, it invokes my useEffect state hook to then call a function to update the useState variable accordingly.

The inbound websocket data messages come in one at a time and look something like this (important keys listed, but there lots more props inside them) :

{
  "name": "PipelineA",
 "state": "succeeded",
  "group": "Group1"
}

{
  "name": "PipelineE",
  "state": "succeeded",
  "group": "Group1"
}

{
  "name": "PipelineZ",
  "state": "succeeded",
  "group": "Group4"
}

...where the name and group are the values I want to use to create an 'object of objects'. So the group will be used to create a group of pipelines that are all part of that same group, which within that object, each pipeline will have its name as the 'key' for its entire data. So, the end state of the ‘object of objects’ would look something like this:

{
  "Group1": {
    "PipelineA": {
      "name": "PipelineA",
      "state": "running",
      "group": "Group1"
    },
    "PipelineB": {
      "name": "PipelineB",
      "state": "running",
      "group": "Group1"
    }
  },
  "Group2": {
    "PipelineC": {
      "name": "PipelineC",
      "state": "running",
      "group": "Group2"
    },
    "PipelineD": {
      "name": "PipelineD",
      "state": "running",
      "group": "Group2"
    }
  },
  ...etc...
}

So the idea being, pipelines of Group1 will be added to the Group1 object, if PipelineA already exists, it just overwrites it, if it does not, it adds it. And so on and so on.

I'm (somewhat) fine with doing this outside of React in plain JS, but I cannot for the life of me figure out how to do it in ReactJS.

    const [groupedPipelineObjects, setGroupedPipelineObjects] = useState({});
    const [socketState, ready, message, send] = useContext(WebsocketContext);

    useEffect(() => {
        if (message) {
            updatePipelineTypeObjects(message)
        }
    }, [message]);

    const updatePipelineGroupObjects = (data) => {
        const pipelineName = data.name
        const pipelineGroup = data.group
        // let groupObj = {pipelineGroup: {}} // do I need to create it first?
        setGroupedPipelineObjects(prevState => ({
            ...prevState,
            [pipelineGroup]: {[pipelineName]: data} // <-- doesnt do what I need
        }))
    }

And help or suggestions would be appreciated. FYI the pipeline names are unique so no duplicates, hence using them as keys.

Also, why am I doing it this way? I already have it working with just an object of all the pipelines where the pipeline name is the key and its data is the value, which then renders a huge page or expandable table rows. But I need to condense it and have the Groups as the main rows for which I then expand them to reveal the pipelines within. I thought doing this would make it easier to render the components.

CodePudding user response:

It's just that you haven't gone quite far enough. What you have will replace the group entirely, rather than just adding or replacing the relevant pipeline within it. Instead, copy and update the existing group if there is one:

const updatePipelineGroupObjects = (data) => {
    const pipelineName = data.name;
    const pipelineGroup = data.group;
    // let groupObj = {pipelineGroup: {}} // do I need to create it first?
    setGroupedPipelineObjects((prevState) => {
        const groups = { ...prevState };
        if (groups[pipelineGroup]) {
            // Update the existing group with this pipeline,
            // adding or updating it
            groups[pipelineGroup] = {
                ...groups[pipelineGroup],
                [pipelineName]: data,
            };
        } else {
            // Add new group with this pipeline
            groups[pipelineGroup] = {
                [pipelineName]: data,
            };
        }
        return groups;
    });
};

Also, you're trying to use iterable destructuring ([]) here:

const [ socketState, ready, message, send ] = useContext(WebsocketContext);

but as I understand it, your context object is a plain object, not an iterable, so you'd want object destructuring ({}):

const { socketState, ready, message, send } = useContext(WebsocketContext);

Live Example:

const { useState, useEffect, useContext } = React;

const WebsocketContext = React.createContext({ message: null });

const Example = () => {
    const [groupedPipelineObjects, setGroupedPipelineObjects] = useState({});
    const { socketState, ready, message, send } = useContext(WebsocketContext);

    useEffect(() => {
        if (message) {
            updatePipelineGroupObjects(message);
        }
    }, [message]);

    const updatePipelineGroupObjects = (data) => {
        const pipelineName = data.name;
        const pipelineGroup = data.group;
        // let groupObj = {pipelineGroup: {}} // do I need to create it first?
        setGroupedPipelineObjects((prevState) => {
            const groups = { ...prevState };
            if (groups[pipelineGroup]) {
                // Update the existing group with this pipeline,
                // adding or updating it
                groups[pipelineGroup] = {
                    ...groups[pipelineGroup],
                    [pipelineName]: data,
                };
            } else {
                // Add new group with this pipeline
                groups[pipelineGroup] = {
                    [pipelineName]: data,
                };
            }
            return groups;
        });
    };

    return <pre>{JSON.stringify(groupedPipelineObjects, null, 4)}</pre>;
};

// Mocked messages from web socket
const messages = [
    {
        name: "PipelineA",
        state: "succeeded",
        group: "Group1",
    },
    {
        name: "PipelineB",
        state: "running",
        group: "Group1",
    },
    {
        name: "PipelineC",
        state: "running",
        group: "Group2",
    },
    {
        name: "PipelineD",
        state: "running",
        group: "Group2",
    },
    {
        name: "PipelineE",
        state: "succeeded",
        group: "Group1",
    },
    {
        name: "PipelineZ",
        state: "succeeded",
        group: "Group4",
    },
];

const App = () => {
    const [fakeSocketContext, setFakeSocketContext] = useState({ message: null });

    useEffect(() => {
        let timer = 0;
        let index = 0;
        tick();
        function tick() {
            const message = messages[index];
            if (message) {
                setFakeSocketContext({ message });
                  index;
                timer = setTimeout(tick, 800);
            }
        }
        return () => {
            clearTimeout(timer);
        };
    }, []);

    return (
        <WebsocketContext.Provider value={fakeSocketContext}>
            <Example />
        </WebsocketContext.Provider>
    );
};

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

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

  • Related