skip to Main Content

How to create multiple useState based on a array and keep track of changes? Or just pass a single state object to multiple components?

At the bottom of the component I’m trying to create customFilter in a loop based on the options array.

import * as React from 'react';

import TransferList from './TransferList';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
import Typography from '@mui/material/Typography';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';

export default function CustomFormatBox() {
  const options = [
    {
      name: 'sample_type_key',
      label: 'Sample Type',
      defaultOperator: 'in',
      valueEditorType: 'multiselect',
      values: [
        { name: '0', label: 'OPTICAL_BLANK_0' },
        { name: '1', label: 'OPTICAL_BLANK_1' },
      ],
    },
    {
      name: 'cuvette_number',
      label: 'Cuvette Number',
      defaultOperator: 'in',
      valueEditorType: 'multiselect',
      values: [
        { name: '0', label: '0' },
        { name: '1', label: '1' },
        { name: '2', label: '2' },
      ],
    },
    {
      name: 'wavelength_key',
      label: 'Wavelength',
      values: [
        { name: '0', label: '340nm' },
        { name: '8', label: '850nm' },
      ],
    },
  ];

  const [filters, setFilters] = React.useState([]);
  const [filters2, setFilters2] = React.useState([]);

  // How to merge multiple state into single object?
  const [allFilters, setAllFilters] = React.useState({
    sample_type_key: [],
    cuvette_number: [],
  });

  const customFilter = (option, right, setRight) => (
    <Accordion>
      <AccordionSummary
        expandIcon={<ArrowDownwardIcon />}
        aria-controls={`panel-${option.label}-content`}
        id={`panel-${option.label}-header`}
      >
        <Typography>{option.label}</Typography>
      </AccordionSummary>
      <AccordionDetails>
        <TransferList option={option} name right={right} setRight={setRight} />
        <table>
          <tbody>
            {right.map((item) => (
              <tr key={item.name}>
                <td>{item.label}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </AccordionDetails>
    </Accordion>
  );

  return (
<>
      {/* Eventually would like to create these filters in a loop based on options array. */}
      {customFilter(options[0], filters, setFilters)}
      {customFilter(options[1], filters2, setFilters2)}

      {/* How to tell setAllFilters to set only in cuvette_number?  */}
      {customFilter(options[1], allFilters['cuvette_number'], () => {
        setAllFilters((state) => ({ ...state, cuvette_number: state }));
      })}
      {/* Do not know how to do the update state part */}
    </>
  );
}

Working Stackblitz: https://stackblitz.com/edit/vite-react-state?file=src%2Fcomponents%2FCustomFormatBox.jsx

3

Answers


  1. Chosen as BEST ANSWER

    Per @evolutionxbox comment, version with useReducer

    import * as React from 'react';
    
    import TransferList from "./TransferList"
    import Accordion from '@mui/material/Accordion';
    import AccordionSummary from '@mui/material/AccordionSummary';
    import AccordionDetails from '@mui/material/AccordionDetails';
    import Typography from '@mui/material/Typography';
    import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
    
    export function createInitialState(options) {
        const emptyListsFromOptionsName = options.map((o) => [o.name, []])
    
        const objWithLists = Object.fromEntries(emptyListsFromOptionsName);
    
        return objWithLists;
    }
    
    function reducer(state, action) {
        const result = {
            ...state
        };
        result[action.type] = action.right;
        return result;
    }
    
    export default function CustomFormatBox() {
    
        const options = [{
            name: "sample_type_key",
            label: "Sample Type",
            defaultOperator: "in",
            valueEditorType: "multiselect",
            values: [
                { name: "0", label: "OPTICAL_BLANK_0" },
                { name: "1", label: "OPTICAL_BLANK_1" },
                { name: "2", label: "OPTICAL_BLANK_2" },
                { name: "8", label: "OPTICAL_BLANK_8" },
                { name: "10", label: "SAMPLE_BLANK_0" },
                { name: "11", label: "SAMPLE_BLANK_1" },
                { name: "20", label: "SYS_CUV_6" },
                { name: "21", label: "SYS_CUV_11" },
                { name: "22", label: "SYS_CUV_28" },
                { name: "253", label: "OPTICAL_DACS" }
            ]
        },
        {
            name: "cuvette_number",
            label: "Cuvette Number",
            defaultOperator: "in",
            valueEditorType: "multiselect",
            values: [
                { name: "0", label: "0" },
                { name: "1", label: "1" },
                { name: "2", label: "2" }
            ]
        },
        {
            name: "wavelength_key",
            label: "Wavelength",
            values: [
                { name: "0", label: "340nm" },
                { name: "8", label: "850nm" }
            ]
        }
        ];
    
        const [filtersAll, dispatch] = React.useReducer(reducer, createInitialState(options));
    
        const customFilter = (option, right, setRight) => (
            <Accordion key={option.name}>
                <AccordionSummary
                    expandIcon={<ArrowDownwardIcon />}
                    aria-controls={`panel-${option.label}-content`}
                    id={`panel-${option.label}-header`}
                >
                    <Typography>{option.label}</Typography>
                </AccordionSummary>
                <AccordionDetails>
                    <TransferList option={option} name right={right} setRight={setRight} />
                    <table>
                        <tbody>
                            {right.map(item => (
                                <tr key={item.name}>
                                    <td>{item.label}</td>
                                </tr>
                            ))}
                        </tbody>
                    </table>
                </AccordionDetails>
            </Accordion>
        );
    
        return (
            <div>
                {options.map((option) => {
                    return customFilter(option, filtersAll[option.name], (right) => dispatch({ type: option.name, right: right }))
                })}
            </div>
        )
    }
    

  2. Generally speaking it’s not recommended to generate states based on an array, which would make states unmanageable in the end. Maybe consider using a single state object in this case. I made some changes based on your existing code, hope it’s helpful!

    https://stackblitz.com/edit/vite-react-state-vwd49u?file=src%2Fcomponents%2FCustomFormatBox.jsx

    Login or Signup to reply.
  3. You can use an Object that includes the options as the initial-state and then map over the options and can show the list of options.

    import * as React from 'react';
    //Other imports
    
    const options = [] // your options
    // remove the other states, use this common state for all filters
    
    const [allFilters, setAllFilters] = React.useState(() => {
        const initialState = {};
        options.forEach((option) => {
          initialState[option.name] = []
        })
        return initialState;
      });
    
    const customFilter = (option, right, setRight) => (
           // Your component
    );
    
    return (
        <>
          //map over the options to display it as a list
          {options.map((option, index) => {
            return customFilter(option, allFilters[option.name], setAllFilters)
          })}
        </>
      );
    }
    

    You need to change the state updating logic in the TransferList Component. This will ensure the dynamic rendering of the component and the updating of the state correctly.

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