1

When I enter a key in my textFields they lose focus. I have found out that it's because of the onChange but I don't see anything wrong with it

onChange(e.target.value)

I have made a sample in codesandbox.io https://codesandbox.io/s/gallant-dust-3wdzhh?file=/src/App.js

Here's the code (from that sandbox) for the text fields:

import * as React from "react";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import {
  TextField,
  Input,
  inputBaseClasses,
  FormControl,
  InputLabel
} from "@mui/material";
import { BiShow, BiHide } from "react-icons/bi";
import { useState } from "react";

const ValidationTextField = styled(TextField)({
  "& input:valid + fieldset": {
    borderColor: "green",
    borderWidth: 2
  },
  "& input:invalid + fieldset": {
    borderColor: "red",
    borderWidth: 2
  },
  "&:hover:valid + fieldset": {
    borderColor: "yellow"
  },
  "& input:valid:focus + fieldset": {
    borderLeftWidth: 6,
    borderColor: "purple",
    color: "pink",
    padding: "4px !important"
  },
  "& .MuiOutlinedInput-root": {
    "&:hover fieldset": {
      borderColor: "yellow",
      color: "orange"
    }
  }
});

const StyledInput = styled(Input)({
  borderRadius: 4,
  border: "2px solid blue",
  padding: 4,
  [`&.${inputBaseClasses.multiline}`]: {
    height: "auto",
    border: "2px solid red"
  }
});

export default function Text({
  errors,
  label,
  rows,
  type,
  defaultValue,
  required,
  onChange,
  setValue,
  name
}) {
  const [showPass, setShowPass] = useState(false);

  /*const handleChange = (e) => {
    const eTarget = e.target.value;
    const eName = e.target.name;
    setValue(eName, eTarget);
  };*/

  const PassWordIcon = () =>
    showPass ? (
      <BiHide size={40} onClick={() => setShowPass(false)} />
    ) : (
      <BiShow size={40} onClick={() => setShowPass(true)} />
    );

  const Multiline = () => {
    return (
      <FormControl variant="outlined">
        <InputLabel>{label}</InputLabel>
        <StyledInput
          sx={{
            "&:hover": {
              border: "2px solid yellow"
            },
            "&.Mui-focused": {
              borderColor: "purple",
              color: "pink",
              padding: "4px !important"
            }
          }}
          onChange={(e) => onChange(e.target.value)}
          disableUnderline
          multiline
          rows={rows}
        />
      </FormControl>
    );
  };

  const Password = () => {
    return (
      <Box>
        <ValidationTextField
          label={label}
          required={required}
          error={errors}
          onChange={(e) => onChange(e.target.value)}
          type={showPass ? "text" : "password"}
          sx={{
            input: {
              color: "red",
              "&::placeholder": {
                color: "darkgreen",
                "&:hover fieldset": {
                  color: "orange"
                }
              }
            },
            label: {
              color: "pink"
            }
          }}
          variant="outlined"
          defaultValue={defaultValue}
        />
        <PassWordIcon />
      </Box>
    );
  };

  const TextInput = () => {
    return (
      <ValidationTextField
        label={label}
        required={required}
        type={type}
        onChange={(e) => onChange(e.target.value)}
        sx={{
          input: {
            color: "red",
            "&::placeholder": {
              color: "darkgreen",
              "&:hover fieldset": {
                color: "orange"
              }
            }
          },
          label: {
            color: "pink"
          }
        }}
        variant="outlined"
        defaultValue={defaultValue}
      />
    );
  };

  const inputType = (type) => {
    switch (type) {
      case "multiline":
        return <Multiline />;
      case "password":
        return <Password />;
      default:
        return <TextInput />;
    }
  };

  return <Box>{inputType(type)}</Box>;
}
Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198
TWOcvfan
  • 388
  • 1
  • 3
  • 19
  • Same cause as this recent question: https://stackoverflow.com/questions/75998687/theme-not-being-applied-to-styled-component/76001251#76001251. You are defining components (e.g. `Multiline`, `Password`, and `TextInput`) inside of another component (`Text`). All components should be defined at the top level (i.e. not inside another function/component). You can **use** them within `Text`, but you shouldn't **define** them there. You can still define them in the same file -- just don't nest the definitions inside `Text`. – Ryan Cogswell Apr 17 '23 at 15:14
  • Oh I didn't know that, thanks it works now. Can you make it as an answer, then I'll set it as the right answer – TWOcvfan Apr 18 '23 at 13:09

1 Answers1

0

You are defining the PasswordIcon, Multiline, Password, and TextInput components inside of the Text component. Though it is fine to use components inside other components, you should never define components inside other components. Doing so causes the component to be redefined on each render of the containing component which then prevents React from recognizing it as the same type of component. This in turn causes React to remount the element (i.e. completely remove it from the DOM and then add a new element back into the DOM in its place) rather than just re-render it.

Instead, you should define all components at the top level (i.e. not nested within another component or function). Any variables from the containing component that the nested components were dependent on can be passed as props.

Here's what this would look like in your case:

import * as React from "react";
import { styled } from "@mui/material/styles";
import Box from "@mui/material/Box";
import {
  TextField,
  Input,
  inputBaseClasses,
  FormControl,
  InputLabel
} from "@mui/material";
import { BiShow, BiHide } from "react-icons/bi";
import { useState } from "react";

const ValidationTextField = styled(TextField)({
  "& input:valid + fieldset": {
    borderColor: "green",
    borderWidth: 2
  },
  "& input:invalid + fieldset": {
    borderColor: "red",
    borderWidth: 2
  },
  "&:hover:valid + fieldset": {
    borderColor: "yellow"
  },
  "& input:valid:focus + fieldset": {
    borderLeftWidth: 6,
    borderColor: "purple",
    color: "pink",
    padding: "4px !important"
  },
  "& .MuiOutlinedInput-root": {
    "&:hover fieldset": {
      borderColor: "yellow",
      color: "orange"
    }
  }
});

const StyledInput = styled(Input)({
  borderRadius: 4,
  border: "2px solid blue",
  padding: 4,
  [`&.${inputBaseClasses.multiline}`]: {
    height: "auto",
    border: "2px solid red"
  }
});

const PassWordIcon = ({ showPass, setShowPass }) =>
  showPass ? (
    <BiHide size={40} onClick={() => setShowPass(false)} />
  ) : (
    <BiShow size={40} onClick={() => setShowPass(true)} />
  );
const Multiline = ({ label, onChange, rows }) => {
  return (
    <FormControl variant="outlined">
      <InputLabel>{label}</InputLabel>
      <StyledInput
        sx={{
          "&:hover": {
            border: "2px solid yellow"
          },
          "&.Mui-focused": {
            borderColor: "purple",
            color: "pink",
            padding: "4px !important"
          }
        }}
        onChange={(e) => onChange(e.target.value)}
        disableUnderline
        multiline
        rows={rows}
      />
    </FormControl>
  );
};
const Password = ({ label, required, errors, onChange, defaultValue }) => {
  const [showPass, setShowPass] = useState(false);
  return (
    <Box>
      <ValidationTextField
        label={label}
        required={required}
        error={errors}
        onChange={(e) => onChange(e.target.value)}
        type={showPass ? "text" : "password"}
        sx={{
          input: {
            color: "red",
            "&::placeholder": {
              color: "darkgreen",
              "&:hover fieldset": {
                color: "orange"
              }
            }
          },
          label: {
            color: "pink"
          }
        }}
        variant="outlined"
        defaultValue={defaultValue}
      />
      <PassWordIcon showPass={showPass} setShowPass={setShowPass} />
    </Box>
  );
};
const TextInput = ({ label, required, type, onChange, defaultValue }) => {
  return (
    <ValidationTextField
      label={label}
      required={required}
      type={type}
      onChange={(e) => onChange(e.target.value)}
      sx={{
        input: {
          color: "red",
          "&::placeholder": {
            color: "darkgreen",
            "&:hover fieldset": {
              color: "orange"
            }
          }
        },
        label: {
          color: "pink"
        }
      }}
      variant="outlined"
      defaultValue={defaultValue}
    />
  );
};

export default function Text({
  errors,
  label,
  rows,
  type,
  defaultValue,
  required,
  onChange,
  setValue,
  name
}) {
  const inputType = (type) => {
    switch (type) {
      case "multiline":
        return <Multiline label={label} onChange={onChange} rows={rows} />;
      case "password":
        return (
          <Password
            label={label}
            required={required}
            errors={errors}
            onChange={onChange}
            defaultValue={defaultValue}
          />
        );
      default:
        return (
          <TextInput
            label={label}
            required={required}
            type={type}
            onChange={onChange}
            defaultValue={defaultValue}
          />
        );
    }
  };

  return <Box>{inputType(type)}</Box>;
}

Edit top-level component definitions

Related answers (same root cause):

Related documentation: https://react.dev/learn/your-first-component#nesting-and-organizing-components

Excerpt:

Components can render other components, but you must never nest their definitions. Instead, define every component at the top level.

Ryan Cogswell
  • 75,046
  • 9
  • 218
  • 198