Issue with setting the state of form data in React Material UI Dialog when updating it

There seems to be a issue with the dialog form where new data selection results in displaying the original data incorrectly. Only by closing and reopening the dialog does it show the correct values. I suspect there might be some trickery going on with the useEffect and useState, but I'm unsure how to resolve it.

To test this, click on the Button Data 1 to confirm that the data is correct, and then click on Data 2. You will notice that it displays the data corresponding to Data 1 instead of Data 2, which is not the desired outcome.

In both the demo and the actual implementation, the state selectedId is necessary as it is referenced elsewhere.

View the code sandbox here

import React, { useState, useCallback, useEffect } from "react";
import SomeDialog from "./SomeDialog";
import { FormData } from "./FormData";
import { Button } from "@mui/material";

let initialFormData: FormData[] = [
  { name: "data1", id: 0, location: "here" },
  { name: "data2", id: 1, location: "there" }
];

function Demo(): any {
  const [selectedData, setSelectedData] = useState<FormData>(
    initialFormData[0]
  );

  const [selectedId, setSelectedId] = useState<number>(0);

  const [dialogOpen, setDialogOpen] = useState<boolean>(false);

  const handleDialogOpen = useCallback(() => setDialogOpen(true), []);
  const handleDialogClose = useCallback(() => setDialogOpen(false), []);

  const onClick = (index: number) => {
    setSelectedId(index);
    handleDialogOpen();
  };

  useEffect(() => {
    let data = initialFormData.find((data) => data.id === selectedId);

    setSelectedData(data);
  }, [selectedId]);

  return (
    <div style={{ height: "100vh", width: "100vw" }}>
      <Button
        onClick={() => {
          onClick(0);
        }}
      >
        Data 1
      </Button>

      <Button
        onClick={() => {
          onClick(1);
        }}
      >
        Data 2
      </Button>

      <SomeDialog
        open={dialogOpen}
        onClose={handleDialogClose}
        data={selectedData}
      />
    </div>
  );
}

export default Demo;

Dialog Form

import React, { useState, useEffect, ChangeEvent } from "react";
import TextField from "@mui/material/TextField";
import { Button, Dialog, Box } from "@mui/material";

import { FormData } from "./FormData";

interface SomeDialogProps {
  open: boolean;
  onClose: () => void;
  data: FormData;
}

function SomeDialog(props: SomeDialogProps) {
  const { open, onClose, data } = props;

  const [formData, setFormData] = useState<FormData>(data);

  useEffect(() => {
    setFormData(data);
  }, [data]);

  function handleDataInputChange(event: ChangeEvent<HTMLInputElement>) {
    const { id, value } = event.target;
    setFormData({ ...formData, [id]: value });
  }

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();

    console.info(formData);

    onClose();
  }

  return (
    <Dialog
      open={open}
      onClose={onClose}
      aria-labelledby="modal-modal-title"
      aria-describedby="modal-modal-description"
    >
      <Box
        component="form"
        sx={{
          "& .MuiTextField-root": { m: 1, width: "25ch" }
        }}
        noValidate
        autoComplete="off"
        onSubmit={handleSubmit}
      >
        <TextField
          id="id"
          label="Id"
          defaultValue={formData.id}
          onChange={handleDataInputChange}
        />

        <TextField
          id="name"
          label="Name"
          defaultValue={formData.name}
          onChange={handleDataInputChange}
        />

        <TextField
          id="location"
          label="Location"
          defaultValue={formData.location}
          onChange={handleDataInputChange}
        />

        <Button type="submit">Submit</Button>
      </Box>
    </Dialog>
  );
}

export default SomeDialog;

export interface FormData {
  name: string;
  id: number;
  location: string;
}

Answer №1

Upon analyzing the code, two issues have been identified:

Demo.tsx

The Demo.tsx file presents a problem where the setSelectedData(data) call within the useEffect function is asynchronous. Consequently, calling handleDialogOpen() immediately after setting the selectedId may result in the dialog displaying outdated data. A better approach would be to merge the logic for both selectedId and selectedData in a single function before invoking handleDialogOpen(), as demonstrated below:

const onClick = (index: number) => {
  let data = initialFormData.find((data) => data.id === index);
  setSelectedData(data);
  setSelectedId(index);
  handleDialogOpen();
};

This modification ensures that upon opening the dialog, the selectedData contains the most recent data.

SomeDialog.tsx

A similar issue exists in the SomeDialog.tsx file, where the formData state variable is initialized with the value of the data prop during the first render. Given the asynchronous nature of React state updates, any modifications to the data prop do not instantly reflect on the formData state variable.

To address this issue, it is recommended to directly utilize the data prop for initializing the form fields, eliminating the need for a local formData state:

import React, { useEffect, ChangeEvent } from "react";
import TextField from "@mui/material/TextField";
import { Button, Dialog, Box } from "@mui/material";

import { FormData } from "./FormData";

interface SomeDialogProps {
  open: boolean;
  onClose: () => void;
  data: FormData;
}

function SomeDialog(props: SomeDialogProps) {
  const { open, onClose, data } = props;

  function handleDataInputChange(event: ChangeEvent<HTMLInputElement>) {
    const { id, value } = event.target;
    // Utilize data prop directly without updating local state
    data[id] = value;
  }

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();

    console.info(data);

    onClose();
  }

  return (
    <Dialog
      open={open}
      onClose={onClose}
      aria-labelledby="modal-modal-title"
      aria-describedby="modal-modal-description"
    >
      <Box
        component="form"
        sx={{
          "& .MuiTextField-root": { m: 1, width: "25ch" }
        }}
        noValidate
        autoComplete="off"
        onSubmit={handleSubmit}
      >
        <TextField
          id="id"
          label="Id"
          defaultValue={data.id}
          onChange={handleDataInputChange}
        />

        <TextField
          id="name"
          label="Name"
          defaultValue={data.name}
          onChange={handleDataInputChange}
        />

        <TextField
          id="location"
          label="Location"
          defaultValue={data.location}
          onChange={handleDataInputChange}
        />

        <Button type="submit">Submit</Button>
      </Box>
    </Dialog>
  );
}

export default SomeDialog;

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Arrange the row information in the MUI DataGrid in order to prepare it for exporting to CSV or Excel

Is there a way to organize row data for exporting to CSV or Excel with the MUI DataGrid? Take a look at my code snippet for the toolbar. slots={{ noRowsOverlay: NoDataComponent, noResultsOverlay: NoDataComponent, toolbar: ( ...

Encountering a React-timestamp problem with the error message "Unable to locate 'react' in the node modules directory."

My journey of creating an App using React was going smoothly until I decided to install react-timestamp via npm for converting unix time (https://www.npmjs.com/package/react-timestamp). However, now I'm encountering a compilation error that states: ...

Designing functional components in React with personalized properties utilizing TypeScript and Material-UI

Looking for help on composing MyCustomButton with Button in Material-ui import React from "react"; import { Button, ButtonProps } from "@material-ui/core"; interface MyButtonProps { 'aria-label': string, // Adding aria-label as a required pro ...

Issue with maintaining proper hydration persists despite implementing the useEffect function in Next JS 13

I encountered a hydration error when using Next 13 to create a navigation menu that displays the current date and time. Despite being a client component and utilizing useEffect, I'm still facing this issue. ### /src/components/navigation/index.tsx &a ...

React Native's setState function not causing a re-render

When using this component, the expected behavior is as follows: I press it, the selectedOpacity() function is called, the state is updated so it now renders with opacity=1. However, for some reason, after calling this.setState, it is not re-rendering. I h ...

Issue with useState in Next.js when fetching data from an API

When attempting to retrieve data from the API, I am receiving a response. However, when trying to set the data to useState using the setAccessData function, the data is not being accessed properly. Despite trying multiple methods, the data continues to sho ...

Adjusting tab index in Reactjs when a change in action occurs in material-ui

I am currently utilizing the Tabs component from material-ui. The swipe-able tab opens on onTouchTap, and everything is working as expected. However, I am facing an issue where the tab index does not reset to 0 after a new action. Instead, it retains the ...

Interacting with Material-UI GridTile: Understanding Touch Events

I am currently utilizing the library material-ui and have a GridList containing GridTiles that can respond to two distinct touch events. When a user touches the tile, they should navigate to another route, and when they touch the 'actionIcon', it ...

What could be causing my React components to not display my CSS styling properly?

If you're developing a React application and integrating CSS for components, ensure that you have included the style-loader and css-loader in your webpack configuration as shown below: module.exports = { mode: 'development', entry: &apo ...

Problems Arising with HTML Form Functionality

After creating an HTML form, I encountered an issue where upon submission, it prompts me to open G Mail or Outlook in order to send the email. Although the correct email address is populated, I wish for the email to be sent without having to open any ext ...

Tips for customizing styles when an element is being dragged over in Material-UI?

If I want to alter the backgroundColor when hovering, I can utilize sx={{"&:hover":{backgroundColor:"yellow"}} Is there a way to adjust the backgroundColor on dragover? It appears that sx={{"&:dragOver":{backgroundCol ...

Can you explain the purpose of the search_/> tag used in this HTML code?

I came across this code snippet while working on creating a customized new tab page for Firefox. <input class="searchBar search_google" type="text" name="q" placeholder="Google" search_/> However, I'm confused about the search_ ...

Problems arising in the arrangement of Material-UI Grid components

I've encountered a problem with the grid system not working as expected in my setup. In one file, I have configured it to display blog items like this: return ( <div> {blogs != null ? (<Grid container direction="r ...

Select a single option from the group to include in the array

I'm currently developing a new soccer betting application. My goal is to allow users to choose the result of a match - whether it's a win, loss, or draw - and then save that selection in a list of chosen bets. https://i.stack.imgur.com/hO3uV.png ...

How to change router.push in NextJS to navigate to non-shallow routes

On my search page, I encounter a problem when users make consecutive searches without refreshing the page. Even though the query in the URL updates, the products on the page remain unchanged. I attempted to solve this by using router.push("/search-pa ...

The challenge of integrating Strapi with React and Material UI data tables

Recently, I started working with React and set up a backend using Strapi. After testing the GraphQL query and confirming that it retrieves values for all nodes, I encountered an issue while populating a Material UI DataGrid component in React. Most of the ...

Workaround for syncing MobX observables when toggling a select/deselect all checkbox

In my application, there is a checkbox list with a 'Select All' checkbox at the top. The list is populated by an observable array of strings. When the user clicks on 'Select All', it should check all checkboxes; subsequent clicks should ...

How can I prevent anchors from performing any action when clicked in React?

My dilemma involves this HTML element: <a href onClick={() => fields.push()}>Add Email</a> The purpose of the href attribute is to apply Bootstrap styles for link styling (color, cursor). The issue arises when clicking on the element caus ...

Tips for quietly printing a PDF document in reactjs?

const pdfURL = "anotherurl.com/document.pdf"; const handleDirectPrint = (e: React.FormEvent) => { e.preventDefault(); const newWin: Window | null = window.open(pdfURL); if (newWin) { newWin.onload = () => ...

What is the method to initialize a Stripe promise without using a React component?

I have encountered an issue while implementing a Stripe promise in my React app. The documentation suggests loading the promise outside of the component to prevent unnecessary recreations of the `Stripe` object: import {Elements} from '@stripe/react-s ...