🗂

Cannot update a component while rendering ~.というwarningの原因と対処方法について

2023/03/12に公開

背景

reactとredux-formを用いて、隠しフィールドからサーバ側へ値を送信するフォームを作成していたら、「Warning: Cannot update a component (Connect(Form(Change Form))) while rendering a different component (Change Form). ( 警告: 別のコンポーネント (Change Form) のレンダリング中にコンポーネント (Connect(Form(Change Form))) を更新することはできません。) 」というwarningが発生し、原因と解決方法を見つけたので解説いたします。

warningの原因:compoentのレンダリング中に別のcomponentの状態を更新しようとしていたため

解決方法:useEffectを使用し、レンダリング後にstate更新用関数を呼び出しstateを更新させる

今回warningが発生したコードと解説

import React, { useState } from 'react'
import { useDispatch } from 'react-redux';
import { reduxForm, Field, change } from 'redux-form'
import renderField from '../renderField'
import { Button } from '@material-ui/core';
import { validate } from '../../func/validate';
import ModalForm from '../modal/ModalForm';

const Changeform = ({ handleSubmit, pristine, submitting, reset }) => {

  // Modalフォームを表示・非表示にするためのフラグ
  const [flag, setFlag] = useState(false)
  const flagChange = () => {
    setFlag(prev => !prev)
  }

  const dispatch = useDispatch();

  // 隠しFieldに値を設定
  dispatch(change('changeform', 'hiddenvalue', '隠しFieldの値だよ〜。'));

  // チェックが変更されたら、'checkboxValue'フィールドの値を動的に変更する
  const onCheckboxClick = (e) => {
    const newValue = e.target.checked ? '今日も良い天気ですね!' : '';
    dispatch(change('changeform', 'checkboxValue', newValue));
  };

  return (
    <>
      <form onSubmit={handleSubmit}>
        <Field name="username" component={renderField} label="名前" />
        <br></br>
        <div>
        <label>チェックボックス(チェックすると設定された値が表示されます。)</label>
        <div>
          <input type="checkbox" onChange={onCheckboxClick} />
          <Field name="checkboxValue" component={renderField} type="text" />
        </div>
        <br></br>
        <Field name="hiddenvalue" component={renderField} type="hidden" />
        </div>
        <br></br>
        <Button onClick={flagChange}>Modal入力フォームの表示</Button>
        <br></br>
        { flag && <ModalForm />}
        <br></br>
        <Button color="secondary" variant="contained" type="submit">送信</Button>
        <Button color="secondary" variant="contained" disabled={pristine || submitting} onClick={reset}>
          クリア
        </Button>
      </form>
    </>
  )
}

export default reduxForm({
  form: 'changeform',
  validate
})(Changeform)

上記のwarningは、公式によると、compoentのレンダリング中に副作用である別のcompoentの状態を更新しようとした時に発生するようです。

つまり、raectではcompoentのレンダリング中に別のcomponentの状態を更新してはいけないようです。

今回の実装の場合、Changeform componentの中でstateを用いてボタンクリックによるModalFormというcomponentの表示・非表示の切り替えを行なっていたので、その時に使用したstate更新用関数 setFlag が原因なのではないかと考え、コメントアウトしたり、useEffect内でstate更新用関数 setFlagを実行してみましたが、上記のwarningは消えませんでした。

他の箇所でstateを更新している箇所がないか調べていると、下記の部分が原因でした。

下記の部分で Changeform componentのレンダリング中にdispatch とredux-formのchangeメソッドによるField component の 値(隠しFieldの値だよ〜。のこと)を更新していたために今回のwarningが発生していたと考えられます。

// 隠しフィールドに値を設定
dispatch(change('changeform', 'hiddenvalue', '隠しFieldの値だよ〜。'));

--- 省略 ---

<Field name="hiddenvalue" component={renderField} type="hidden" />

warning解決のため、公式を参考にuseEffectを用いてdispatchによるField component の 値の更新を Changeform componentのレンダリング後に行うようにしてみました。

すると、「Cannot update a component ~」という warnig は削除されました。

// 隠しフィールドに値を設定
useEffect(() => {
  dispatch(change('changeform', 'hiddenvalue', '隠しFieldの値だよ〜。'));
}, [dispatch]);

まとめ

今回のwarningにより、reactではcompoentのレンダリング中に別のcomponentの状態を更新してはいけないということを初めて知りました。

普段どのタイミングでFieldに値を保持させるべきかあまり意識したことがなかったので、良い経験になりました。

今後はcomponentのレンダリング中に別のcomponentの状態を更新しようとした時は、useEffectを適切に使用していき対処していきたいです。

Discussion