skip to Main Content

I have code below.

// this is a function which pass to onClick props of a button
const handleDialogClick = () => {
    setDialogOpen({
      title: "Please Choose Language You Want",
      content: (
        <TextField
          label="Text"
          select
          fullWidth
          InputLabelProps={{ shrink: true }}
          className="border-1 border-cadet-gray"
          onChange={({ target }) => {
            setPendingLanguage(target.value);
          }}
        >
          <MenuItem value="Chinese">Chinese</MenuItem>
          <MenuItem value="English">English</MenuItem>
          <MenuItem value="Japanese">Japanese</MenuItem>
        </TextField>
      ),
      onCancelClick: setDialogClose,
      onConfirmlClick: () => {
        // below will get old reference :(
        setLanguage(pendingLanguage);

        setDialogClose();
      }
    });
  };

  return (
    <div>
      <Button variant="contained" onClick={handleDialogClick}>
        click open dialog
      </Button>

      // some code here...
    </div>
  )

And feel confused about how to get new value of state, when the state be used by the props of functional component.

(I know pass a function to set function can get old value, but I’m not supposed to use old value to calculate new value.)

For compelete code, please read codesandbox

2

Answers


  1. The problem is caused by the functions that you store in the state, for example:

    onConfirmlClick: () => {
      setLanguage(pendingLanguage);
      setDialogClose();
    }
    

    When the function is stored, the current value of pendingLanguage is preserved in the closure. The entire handleDialogClick function is recreated on each render, with the current value, but since the modal is open, the functions that are saved in the atom do not.

    I’m not familiar enough with Jotai, but I don’t think that saving functions in the state is a good way to go. However, there’s a workaround, use a ref to store the current value of pendingLanguage. The ref is an object, and the stale function in the state would point to the same ref object, and you can get the current updated value.

    const ref = useRef(pendingLanguage);
    
    useEffect(() => {
      ref.current = pendingLanguage;
    });
    
    const handleDialogClick = () => {
      setDialogOpen({
        title: 'Please Choose Language You Want',
        content: (
          <TextField
            label="Text"
            select
            fullWidth
            InputLabelProps={{ shrink: true }}
            className="border-1 border-cadet-gray"
            onChange={({ target }) => {
              setPendingLanguage(target.value);
            }}
            defaultValue={pendingLanguage}
          >
            <MenuItem value="Chinese">Chinese</MenuItem>
            <MenuItem value="English">English</MenuItem>
            <MenuItem value="Japanese">Japanese</MenuItem>
          </TextField>
        ),
        onCancelClick: setDialogClose,
        onConfirmlClick: () => {
          setLanguage(ref.current);
    
          setDialogClose();
        }
      });
    };
    
    Login or Signup to reply.
  2. You can just assign the choosen value to a temporary variable declared at the beginning of the function.

    import { useAtom } from "jotai";
    import { useState } from "react";
    
    import Button from "@mui/material/Button";
    import TextField from "@mui/material/TextField";
    import Typography from "@mui/material/Typography";
    import MenuItem from "@mui/material/MenuItem";
    
    import { openDialogAtom, closeDialogAtom } from "../stores/dialogStateAtom";
    
    const Test = () => {
      const [language, setLanguage] = useState("none");
      const [pendingLanguage, setPendingLanguage] = useState("");
      const [, setDialogOpen] = useAtom(openDialogAtom);
      const [, setDialogClose] = useAtom(closeDialogAtom);
    
      const handleDialogClick = () => {
        let pending = language;
        setDialogOpen({
          title: "Please Choose Language You Want",
          content: (
            <TextField
              label="Text"
              select
              fullWidth
              InputLabelProps={{ shrink: true }}
              className="border-1 border-cadet-gray"
              onChange={({ target }) => {
                pending = target.value
                setPendingLanguage(target.value);
              }}
            >
              <MenuItem value="Chinese">Chinese</MenuItem>
              <MenuItem value="English">English</MenuItem>
              <MenuItem value="Japanese">Japanese</MenuItem>
            </TextField>
          ),
          onCancelClick: setDialogClose,
          onConfirmlClick: () => {
            // below will get old reference :(
            setLanguage(pending);
    
            setDialogClose();
          }
        });
      };
    
      return (
        <div>
          <Button variant="contained" onClick={handleDialogClick}>
            click open dialog
          </Button>
    
          <form className="border-1 p-5 my-4">
            <Typography
              style={{
                textAlign: "left",
                marginBottom: 16
              }}
            >
              Language: {language} / {pendingLanguage}
            </Typography>
            <TextField
              fullWidth
              label="vocabulary"
              InputLabelProps={{ shrink: true }}
            />
          </form>
        </div>
      );
    };
    
    export default Test;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search