Контролируемая форма React с дочерним / родительским компонентами

Я создаю управляемую форму с динамическими полями. Родительский компонент получает данные из хранилища redux, а затем устанавливает состояние со значениями. Я не хочу делать это с слишком большим количеством строк кода, поэтому я превращаю динамические поля в компонент. Состояния остаются в родительском компоненте, и я использую реквизиты для передачи функции handlechange.

Родитель:

function EditAbout(props) {
  const [img, setImg] = useState("");
  const [body, setBody] = useState(props.about.body);
  const [instagram, setInstagram] = useState(props.about.links.instagram);
  const [linkedin, setLinkedIn] = useState(props.about.links.linkedin);
  const [press, setPress] = useState(props.about.press)

  const handleSubmit = (e) => {
   // Submit the change to redux
  };

// set states with redux store
  useEffect(() => {
    setBody(props.about.body);
    setInstagram(props.about.links.instagram);
    setLinkedIn(props.about.links.linkedin);
    setPress(props.about.press);
  }, []);

  const handleChangeChild = (e, index) =>  {
    e.preventDefault();
    let articles = press
    const {value, name } = e.target
    if (name === "title") {
      articles[index].title = value;
    } else {
      articles[index].link = value;
    }
    setPress(articles)
    console.log(articles[index])
  }

  return (
    <Box>
      <h1>CHANGE ABOUT ME</h1>
      <Input
        label="Image"
        name="img"
        type="file"
        variant="outlined"
        margin="normal"
        onChange={(e) => setImg(e.target.files)}
      />
      <Input
        label="body"
        value={body}
        name="body"
        onChange={(e) => setBody(e.target.value)}
        variant="outlined"
        multiline
        rowsMax={12}
        margin="normal"
      />
      <Input
        label="instagram"
        value={instagram}
        name="instagram"
        variant="outlined"
        margin="normal"
        onChange={(e) => setInstagram(e.target.value)}
      />
      <Input
        label="Linkedin"
        value={linkedin}
        name="linkedin"
        variant="outlined"
        margin="normal"
        onChange={(e) => setLinkedIn(e.target.value)}
      />
      <Child press={press} onChange={handleChangeChild} />
      {props.loading ? (
        <CircularProgress color="black" />
      ) : (
        <Button onClick={handleSubmit} variant="contained">
          Send
        </Button>
      )}
    </Box>
  );
}

Ребенок :

function Child(props) {
  const { press, onChange } = props;

  const inputsMarkup = () =>
    press.map((article, index) => (
      <div key={`press${index}`} style={{ display: "flex" }}>
        <input
          name="title"
          value={press[index].title}
          onChange={(e) => onChange(e, index)}
        />
        <input
          name="link"
          value={press[index].link}
          onChange={(e) => onChange(e, index)}
        />
        <button>Delete</button>
      </div>
    ));

  return (
    <div>
      <h1>Press :</h1>
      {inputsMarkup()}
    </div>
  );
}

Когда я набираю родительские поля, все в порядке. Но когда я использую обновление состояния дочерних полей для одного символа, но сразу после этого возвращаюсь в свое предыдущее состояние. Он также не отображает изменение персонажа. Я вижу это только в консоли. Заранее спасибо за вашу помощь


person Bibi    schedule 09.12.2020    source источник


Ответы (1)


Проблема в том, что вы напрямую мутируете состояние. Когда вы создаете переменную articles (let articles = press), вы фактически не создаете копию, а articles фактически не содержит значения. Это всего лишь ссылка на это значение, указывающее на расположение объекта в памяти.

Поэтому, когда вы обновляете articles[index].title в своей handleChangeChild функции, вы фактически меняете и состояние press. Вы можете подумать, что это нормально, но без вызова setPress() React не узнает об изменении. Итак, хотя значение состояния изменено, вы его не увидите, потому что React не будет его повторно отображать.

Вам нужно создать копию массива press с помощью .map() и создать копию обновленного элемента массива. Вы можете найти обновленный handleChangeChild() ниже:

const handleChangeChild = (e, index) => {
  e.preventDefault();

  const { value, name } = e.target;

  setPress(
    // .map() returns a new array
    press.map((item, i) => {
      // if the current item is not the one we need to update, just return it
      if (i !== index) {
        return item;
      }

      // create a new object by copying the item
      const updatedItem = {
        ...item,
      };

      // we can safely update the properties now it won't affect the state
      if (name === 'title') {
        updatedItem.title = value;
      } else {
        updatedItem.link = value;
      }

      return updatedItem;
    }),
  );
};

person Zsolt Meszaros    schedule 09.12.2020
comment
Большое спасибо за ваш ответ. Это сработало, и теперь я понимаю все, что я сделал не так. - person Bibi; 10.12.2020