Search code examples
javascriptreactjsreduxreact-reduxjoi

What is wrong in my redux store and action where I tried to implement validation of user?


I have ButtonRegistration component where I implemented a work after pressing on the button Registration. When the user presses on the button the modal with input fields appear and the user can types his data(name, email, password, confirmPassword)

const ButtonRegistration = () => {
  const dispatch = useDispatch();
  const user = useSelector((state) => state.user);
  const theme = useTheme();
  const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
  const [open, setOpen] = useState(false);
  const [error, setError] = useState(user.errorMessage);

  /*Handler for Sign UP section*/
  
  const handleSignup = () => {
    dispatch(valideteNewUser(user));
  };

  /*Handlers for Button-Dialog section*/

  const handleClickOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
  };

  /*Handler for Modal section*/

  const handleCloseErrorMessage = () => {
    setError(null);
  };

  return (
    <div style={{ marginLeft: "1rem" }}>
      {/*section Button-Dialog*/}

      <Button
        onClick={handleClickOpen}
        variant="outlined"
        size="small"
        sx={{ background: "rgb(177, 209, 227);" }}
      >
        Registration
      </Button>
      <Dialog
        fullScreen={fullScreen}
        open={open}
        onClose={handleClose}
        PaperProps={{
          component: "form",
          onSubmit: (event) => {
            event.preventDefault();
            const formData = new FormData(event.currentTarget);
            const formJson = Object.fromEntries(formData.entries());
            const email = formJson.email;
            console.log(email);
            handleClose();
          },
        }}
      ></Dialog>

      {/*section Modal*/}

      <Modal
        aria-labelledby="modal-title"
        aria-describedby="modal-desc"
        open={open}
        onClose={() => setOpen(false)}
        sx={{
          display: "flex",
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Sheet
          variant="outlined"
          sx={{
            maxWidth: 500,
            borderRadius: "md",
            p: 3,
            boxShadow: "lg",
          }}
        >
          <ModalClose
            variant="plain"
            sx={{ m: 1 }}
            onClick={handleCloseErrorMessage}
          />
          <Typography
            component="h2"
            id="modal-title"
            level="h4"
            textColor="inherit"
            fontWeight="lg"
            mb={1}
          >
            Registration
          </Typography>

          {/*section Sign UP*/}

          <Box
          >
            <Input
              size="lg"
              value={user.name}
              onChange={(e) => dispatch(setName(e.target.value))}
              type="text"
              placeholder="Enter your name..."
            ></Input>
            <Input
              size="lg"
              value={user.email}
              onChange={(e) => dispatch(setEmail(e.target.value))}
              type="text"
              placeholder="Enter email..."
            ></Input>
            <Input
              size="lg"
              value={user.password}
              onChange={(e) => dispatch(setPassword(e.target.value))}
              type="password"
              placeholder="Enter password..."
            ></Input>
            <Input
              size="lg"
              value={user.confirmdPassword}
              onChange={(e) => dispatch(setConfirmPassword(e.target.value))}
              type="password"
              placeholder="Confirm password..."
            ></Input>
            <Button size="md" onClick={handleSignup}>
              Register
            </Button>
            {error && (
              <Typography variant="body2" color="error">
                {error}
              </Typography>
            )}
          </Box>
        </Sheet>
      </Modal>
    </div>
  );
};

export default ButtonRegistration;

When the user typed incorrect data which I check by using Joi:

const Joi = require("joi");

const validator = (scheme) => (payload) =>
  scheme.validate(payload, { abortEarly: false });

const signupSchema = Joi.object({
  name: Joi.string().required(),
  email: Joi.string().email({ tlds: { allow: false } }).required(),
  password: Joi.string().min(3).max(10).required(),
  confirmPassword: Joi.ref("password"),
});

exports.validateRegister = validator(signupSchema);

I tried to render the message about incorrectly entered data by usin variable error in ButtonRegistartion component. I have the following action and reducer:

Action

export const valideteNewUser = (name, email, password, confirmPassword) => ({
  type: VALIDATE_USER,
  payload: {
    name,
    email,
    password,
    confirmPassword,
  },
});

export const setName = (name) => ({
  type: SET_NAME,
  payload: name,
});

export const setEmail = (email) => ({
  type: SET_EMAIL,
  payload: email,
});

export const setPassword = (password) => ({
  type: SET_PASSWORD,
  payload: password,
});

export const setConfirmPassword = (confirmPassword) => ({
  type: SET_CONFIRM_PASSWORD,
  payload: confirmPassword,
});

export const setErrorMessage = (errorMessage) => ({
  type: ERROR_MESSAGE,
  payload: errorMessage,
});

Reducer:

export const initialState = {
  user: {
    name: "",
    email: "",
    password: "",
    confirmdPassword: "",
    errorMessage: null,
  },
};

export function userReducer(state = initialState, action) {
  switch (action.type) {
    case VALIDATE_USER:
      const { error } = validateRegister({
        name: action.payload.name,
        email: action.payload.email,
        password: action.payload.password,
        confirmPassword: action.payload.confirmPassword,
      });
      if (error) {
        return {
          ...state,
          errorMessage: error.details.map((d) => d.message).join(", "),
        };
      } else {
        return {
          ...state,
          errorMessage: null,
        };
      }

    case SET_NAME:
      return {
        ...state,
        user: {
          ...state.user,
          name: action.payload,
        },
      };

    case SET_EMAIL:
      return {
        ...state,
        user: {
          ...state.user,
          email: action.payload,
        },
      };

    case SET_PASSWORD:
      return {
        ...state,
        user: {
          ...state.user,
          password: action.payload,
        },
      };

    case SET_CONFIRM_PASSWORD:
      return {
        ...state,
        user: {
          ...state.user,
          confirmPassword: action.payload,
        },
      };

    case ERROR_MESSAGE:
      return {
        ...state,
        user: {
          ...state.user,
          errorMessage: action.payload,
        },
      };

    default:
      return state;
  }
}

When the user types incorrect data then nothing appears but in the same moment I see in my Redux devTools that my errrorMessage changes.

How can implement the render of incorrectly inputs. Thanks in advance?


Solution

  • The ButtonRegistration incorrectly duplicates the selected user.errorMessage state into local state. This is a React anti-pattern. The basic gist is that the local error state doesn't update when the user.errorMessage value from the store updates.

    Update the ButtonRegistration component to reference and update the user.errorMessage state directly.

    const ButtonRegistration = () => {
      const dispatch = useDispatch();
    
      const user = useSelector((state) => state.user); // <-- includes errorMessage
    
      const theme = useTheme();
      const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
    
      const [open, setOpen] = useState(false);
    
      /*Handler for Sign UP section*/
      const handleSignup = () => {
        dispatch(validateNewUser(user)); // <-- updates user.errorMessage
      };
    
      ...
    
      /*Handler for Modal section*/
      const handleCloseErrorMessage = () => {
        dispatch(setErrorMessage(null)); // <-- clear user.errorMessage state
      };
    
      return (
        <div ... >
          ...
    
          <Modal ... >
            <Sheet ... >
              ...
    
              <Box>
                ...
    
                {user.errorMessage && (
                  <Typography variant="body2" color="error">
                    {user.errorMessage}
                  </Typography>
                )}
              </Box>
            </Sheet>
          </Modal>
        </div>
      );
    };
    
    export default ButtonRegistration;