Home > database >  setstate did not update state for object contains array members in react js
setstate did not update state for object contains array members in react js

Time:09-30

I am new to Reactjs, I have done a test code to manage different kinds of popups by state, I have a array to hold all popups and stored in state object.

When a popup is displayed, the code will add the popup into the array, and assign an unique handle to it. But when a popup is closed via close button, the popup stored in the array will be removed.

The popup can be displayed via button click, and the array in state is updated correctly. However, when the close button inside the popup is pressed, handleClose callback is called, for some reason, the array was changed to empty which is the initial value.

Could someone kindly help please?

Here is the code:

import React, { useState } from 'react';
import './App.css';
import "bootstrap/dist/css/bootstrap.min.css";
import Notification from './Notifification';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';


interface componentType {
  item: React.ReactNode;
  handle: number;
}

interface stateType {
  index: number;
  unused?: number;
  array: componentType[];
}

const App: React.FC = () => {
  const [state, setstate] = useState<stateType>({
    index: 1,
    array: []
  })

  const handleClose = (ok: boolean, handle: number) => {
    const comp = state.array.find(c => c.handle === handle)
    if (comp === undefined) {
      //It will run here as the array is empty
      console.log("Error: not there, handle = "   handle, " array = "   state.array)
      return
    }

    const newArray = state.array.filter((value, index, arr) => { return value.handle !== handle; })
    console.log("new array in close popup = ", newArray)
    setstate({ ...state, array: newArray })
  }

  const showPopup = () => {
    console.log("show popup");
    const idx = state.index   1

    let comp: componentType = { item: <Notification handleClose={handleClose} title={'TEST'} body={'BODY'} handle={idx}></Notification>, handle: idx }
    const newArray = [...state.array, comp]
    console.log("new array in show popup = ", newArray)
    setstate({ ...state, index: idx, array: newArray })
  }

  return (
    <>
      <Form>
        <Button className="mr-sm-2" onClick={() => showPopup()}>Popup button</Button>
      </Form>
      {
        //after the Popup button is clicked, the array should be correct, as the popup can be seen
        state.array.map((component, index) => {
        return React.isValidElement(component.item) ? React.cloneElement(component.item, { key: index }) : component.item
      }
      )}
    </>
  );
}

export default App;

Notification.tsx

import React from "react";
import Modal from "react-bootstrap/Modal";
import Button from "react-bootstrap/Button";

export interface NotificationProps {
    handleClose: (ok: boolean, handle: number) => void;
    title: string;
    body: string;
    handle: number;
}

const Notification: React.FC<NotificationProps> = ({ handleClose, handle, title, body, ...props }) => {
    return (
        <Modal
            {...props}
            size="lg"
            aria-labelledby="contained-modal-title-vcenter"
            centered
            show={true}
        >
            <Modal.Header closeButton>
                <Modal.Title id="contained-modal-title-vcenter">
                    {title}
                </Modal.Title>
            </Modal.Header>
            <Modal.Body>
                {body}
            </Modal.Body>
            <Modal.Footer>
                <Button onClick={() => {
                    handleClose(true, handle)
                }} className="mr-sm-2">OK</Button>

                <Button variant="secondary" onClick={() => {
                    handleClose(false, handle)
                }}>Cancel</Button>
            </Modal.Footer>
        </Modal >
    );
}
export default Notification;

CodePudding user response:

The issue here is stale enclosures of state variable in the handleClose callback. Don't store instantiated React components in state, store just the data and render the JSX from state. You should also use functional state updates to ensure updating from the previous state and not the state closed over in callback scope.

const App: React.FC = () => {
  const [state, setState] = useState<stateType>({
    index: 1,
    array: []
  });

  const handleClose = (ok: boolean, handle: number) => {
    setState(state => {
      if (state.array.find(c => c.handle === handle)) {
        return {
          ...state,
          array: state.array.filter(c => c.handle !== handle),
        };
      }
      return state;
    });

  }

  const showPopup = () => {
    setState(state => ({
      ...state,
      index: state.index   1,
      array: [
        ...state.array,
        {
          item: Notification,
          title: 'TEST',
          body: 'BODY',
          handle: state.index   1,
        },
      ],
    }));
  }

  return (
    <>
      <Form>
        <Button className="mr-sm-2" onClick={showPopup}>
          Popup button
        </Button>
      </Form>
      {state.array.map(({ item, ...props }, index) => {
        const Component = item;
        return <Component key={props.handle} handleClose={handleClose} {...props} />;
      })}
    </>
  );
}

Update

However the compiler does not seem to be happy with const Component = item; return <Component handleClose={handleClose} {...props} />;, the error message is "TypeScript error in E:/Dev/myapp/src/App.tsx(59,17): JSX element type 'Component' does not have any construct or call signatures"

Component is already defined in Reactjs, so I changed to component as below: {state.array.map(({ item, ...props }, index) => { const component = item; return <component handleClose={handleClose} {...props} />; })} but it is having typescript error “Property 'component' does not exist on type 'JSX.IntrinsicElements'. TS2339”

Yeah, seems it's complaining about the type of Component missing, and then when you changed it to component it is now no longer a valid React component. Remember, React component names are PascalCased.

You can try giving it another name instead, like Item and provide the same type definition:

{state.array.map(({ item, ...props }, index) => {
  const Item: React.ReactNode = item;
  return <Item key={props.handle} handleClose={handleClose} {...props} />;
})}

Or just store it in state with the correct naming convention:

interface componentType {
  Item: React.ReactNode;
  handle: number;
}

const App: React.FC = () => {
  const [state, setState] = useState<stateType>({
    index: 1,
    array: []
  });

  ...

  const showPopup = () => {
    setState(state => ({
      ...state,
      index: state.index   1,
      array: [
        ...state.array,
        {
          Item: Notification,
          title: 'TEST',
          body: 'BODY',
          handle: state.index   1,
        },
      ],
    }));
  }

  ...

  return (
    <>
      <Form>
        <Button className="mr-sm-2" onClick={showPopup}>
          Popup button
        </Button>
      </Form>
      {state.array.map(({ Item, ...props }, index) => (
        <Item key={props.handle} handleClose={handleClose} {...props} />
      ))}
    </>
  );
}

* Don't forget to include a React key when mapping.

  • Related