Превышение максимального обновления. Запуск в бесконечный цикл

#javascript #reactjs

#javascript #reactjs

Вопрос:

у меня есть несколько форм на вкладках, это одна из них, но когда я нажимаю, чтобы просмотреть эту конкретную форму, я получаю эту ошибку: ошибка Uncaught: превышена максимальная глубина обновления. Это может произойти, когда компонент повторно вызывает setState внутри componentWillUpdate или componentDidUpdate. React ограничивает количество вложенных обновлений, чтобы предотвратить бесконечные циклы. я сузил ошибку до этого

 
    useEffect(() => {
        if(mode === 'create') append();
      }, [append])

  

и это

 
 

     const watchKeyOfficers = useWatch({ name: 'keyOfficers' });
    
      // Disable selected option on next select
      useEffect(() => {
        if(watchKeyOfficers) {
          setUsers(prevUsers => {
            const newWatchKeyOfficers = watchKeyOfficers.map(ko => ko.staffName?.userid);
            const newUsers = prevUsers?.map(u => ({ ...u, isDisabled: newWatchKeyOfficers.includes(u.userid) }))
            return newUsers;
          })
        }
      }, [watchKeyOfficers])
    
      const watchBranchId = useWatch({ name: 'branchId' });
    
    
    
    
      useEffect(() => {
        const branchId = watchBranchId?.branchId;
        setUsers(usersJson.filter(pu => pu.branchId === branchId));
        if(create) {
          reset({
            ...getValues(),
            keyOfficers: [{}]
          }, {
            errors: true, // errors will not be reset 
            dirtyFields: true, // dirtyFields will not be reset
            isDirty: true, // dirty will not be reset
          })
        }
      }, [append, getValues, reset, watchBranchId])

  

введите код здесь

Мне интересно, есть ли лучший способ использовать useEffect, чем то, что я сделал

 
    import React, { Fragment, useEffect, useState, useCallback } from 'react';
    import { useFormContext, Controller, useFieldArray, useWatch } from "react-hook-form";
    import {
      Form,
      Row,
      Col,
      Card,
      Button
    } from 'react-bootstrap';
    import moment from 'moment';
    import Select from "../../Shared/Select";
    import Datepicker from "../../Shared/Datepicker";
    import KeyOfficer from "../../Tooltips/KeyOfficer";
    import "../../Shared/style.css";
    import {Panel} from 'primereact/panel';
    
    // Data from JSON file
    import usersJson from '../../Dummy/ic4pro_users.json';
    import designatesJson from '../../Dummy/ic4pro_designates.json';
    import gradeJson from '../../Dummy/ic4pro_grades.json';
    
    const years = new Array(25   1).fill().map((e,i) => {
      return {label: i, value: i}
    });
    
    const months = new Array(10   1).fill().map((e,i) => {
      return {label: i, value: i}
    });
    
    const StepTwo = () => {
      const { register, errors, control, getValues, reset, selectedData, mode, setValue } = useFormContext();
    
      const { fields, append, remove } = useFieldArray({
        control,
        name: "keyOfficers"
      });
    
      const [ users, setUsers ] = useState([...usersJson])
    
      
    
      useEffect(() => {
        if(mode === 'create') append();
      }, [append])
    
      useEffect(() => {
        if(selectedData amp;amp; (mode !== 'create' || mode === null)) {
          reset({
            ...getValues(),
            keyOfficers: selectedData.keyofficers.map(sd => ({
              staffName: users.find(uj => uj.userid === sd.staffName),
              datejoin: moment(sd.datejoin, 'YYYYMMDD').toDate(),
              jobStayYear: years.find(y => y.value === parseInt(sd.jobStayYear)),
              jobStayMonth: months.find(m => m.value === parseInt(sd.jobStayMonth))
            }))
          })
        }
        else {
          reset({
            keyOfficers: []
          })
        }
      }, [selectedData])
    
      const watchKeyOfficers = useWatch({ name: 'keyOfficers' });
    
      // Disable selected option on next select
      useEffect(() => {
        if(watchKeyOfficers) {
          setUsers(prevUsers => {
            const newWatchKeyOfficers = watchKeyOfficers.map(ko => ko.staffName?.userid);
            const newUsers = prevUsers?.map(u => ({ ...u, isDisabled: newWatchKeyOfficers.includes(u.userid) }))
            return newUsers;
          })
        }
      }, [watchKeyOfficers])
    
      const watchBranchId = useWatch({ name: 'branchId' });
      
      
    
    
      useEffect(() => {
        const branchId = watchBranchId?.branchId;
        setUsers(usersJson.filter(pu => pu.branchId === branchId));
        if(create) {
          reset({
            ...getValues(),
            keyOfficers: [{}]
          }, {
            errors: true, // errors will not be reset 
            dirtyFields: true, // dirtyFields will not be reset
            isDirty: true, // dirty will not be reset
          })
        }
      }, [append, getValues, reset, watchBranchId])
    
      const getDesignate = useCallback((designate) => {
        const designateFind = designatesJson.find(de => de.designate_id === designate)
        return designateFind?.designate_name;
      }, []);
    
      const getGrade = useCallback((gradelevel) => {
        const gradeFind = gradeJson.find(gr => gr.gradeID === gradelevel)
        return gradeFind?.gradeType;
      }, []);
    
      const header1 = (
        <>
        <div >  
          <div>Key Officer <KeyOfficer/></div>
        </div>
      </>
      )
    
      return (
        <Fragment>
          <Panel header={header1} className="p-col-12 mb-3">
                    
                    <Form.Group as={Row} >
                    <Form.Label column sm={3} style={{paddingLeft:"2rem"}}>
                      Staff Name
                    </Form.Label>
                    
                   
                    <Form.Label column sm={2}>
                      Grade Level
                    </Form.Label>
                    
                    
                    <Form.Label column sm={1}>
                      Function
                    </Form.Label>
                    
                    
                    <Form.Label column sm={2} className="text-center">
                      Length Of Stay
                    </Form.Label>
                    
                    <Form.Label column xs={2} >
                      Job Stay Year
                    </Form.Label>
                    
                    <Form.Label column sm={2}  >
                      Job Month
                    </Form.Label>
                    </Form.Group>
                    
                    
              {fields.map((item, index) => (
                <>
                <Row key={item.id}>
                  <Form.Group as={Col} sm="3" controlId={`keyOfficers[${index}].staffName`}>
                    
                    <Controller
                      // id="keyofficer"
                      name={`keyOfficers[${index}].staffName`}
                      as={Select}
                      options={users}
                      // hideSelectedOptions={false}
                      control={control}
                      getOptionValue={option => option.userid}
                      getOptionLabel={option => `${option.title}. ${option.firstName} ${option.lastName}`}
                      rules={{ required: 'Staff Name is required!' }}
                      isInvalid={errors.keyOfficers?.[index]?.staffName}
                      disabled={mode === 'view' || mode === 'delete'}
                      defaultValue={item.staffName || ""}
                    />
                  </Form.Group>
                  <Form.Group as={Col} controlId={`keyOfficers[${index}].gradeLevel`}>
                    
                    <Form.Control 
                    id="gradelevel"
                    name={`keyOfficers[${index}].gradeLevel`} 
                    ref={register} 
                    style ={{height:'1.8rem', marginTop:0, marginBottom:0}} 
                    readOnly 
                    defaultValue={getGrade(watchKeyOfficers?.[index]?.staffName?.gradelevel)}/>
                  </Form.Group>
                  <Form.Group as={Col} controlId={`keyOfficers[${index}].designate`}>
                    
                    <Form.Control
                      name={`keyOfficers[${index}].designate`}
                      ref={register}
                      style ={{height:'1.8rem', marginTop:0, marginBottom:0}} 
                      readOnly
                      defaultValue={getDesignate(watchKeyOfficers?.[index]?.staffName?.designate)}
                    />
                  </Form.Group>
                  <Form.Group as={Col} controlId={`keyOfficers[${index}].datejoin`}>
                    
                    <Controller
                      control={control}
                      name={`keyOfficers[${index}].datejoin`}
                      rules={{ required: 'Length of Stay is required!' }}
                      defaultValue={item.datejoin || ""}
                      render={({ onChange, onBlur, value }) => (
                        <Fragment >
                          <Datepicker
                            onChange={onChange}
                            onBlur={onBlur}
                            selected={value}
                            isInvalid={errors.keyOfficers?.[index]?.datejoin}
                            className="form-control is-invalid"
                            placeholderText="Length of Stay..."
                            disabled={mode === 'view' || mode === 'delete'}
                          />
                        </Fragment>
                      )}
                    />
                  </Form.Group>
                  <Form.Group as={Col} controlId={`keyOfficers[${index}].jobStayYear`}>
                    
                    <Controller
                      name={`keyOfficers[${index}].jobStayYear`}
                      as={Select}
                      options={years}
                      control={control}
                      rules={{ required: 'Job Stay Year is required!' }}
                      isInvalid={errors.keyOfficers?.[index]?.jobStayYear}
                      disabled={mode === 'view' || mode === 'delete'}
                      defaultValue={item.jobStayYear || ""}
                    />
                  </Form.Group>
                  <Form.Group as={Col} controlId={`keyOfficers[${index}].jobStayMonth`}>
                    
                    <Controller
                      name={`keyOfficers[${index}].jobStayMonth`}
                      as={Select}
                      options={months}
                      control={control}
                      rules={{ required: 'Job Stay Month is required!' }}
                      isInvalid={errors.keyOfficers?.[index]?.jobStayMonth}
                      disabled={mode === 'view' || mode === 'delete'}
                      defaultValue={item.jobStayMonth || ""}
                    />
                  </Form.Group>
                  {(mode === 'create' || mode === 'edit') amp;amp; (
                    <Form.Group as={Col} style={{marginTop:0, maxHeight:'1.8rem'}} controlId={`keyOfficers[${index}].delete`}
                      className="d-flex align-items-center justify-content-center" xs="auto"
                    >
                      <Button variant="danger" style={{marginTop:0, maxHeight:'1.8rem'}} size="sm" onClick={() => remove(index)}>Delete</Button>
                    </Form.Group>
                  )}
                </Row>
                
                </>
              ))}
              {(mode === 'create' || mode === 'edit') amp;amp; (
              <Form.Group>
                <Button variant="primary" size="sm" style={{maxWidth:'7rem', maxHeight:'1.8rem'}} type="button" onClick={append}>Add Staff</Button>
              </Form.Group>
              )}
            </Panel>
        </Fragment>
      )
    }
    
    function compare(prevProps, nextProps) {
      return JSON.stringify(prevProps) === JSON.stringify(nextProps)
    }
    
    export default React.memo(StepTwo, compare);

  

Комментарии:

1. Это будет обновляться всякий раз, когда append также получает обновление, поскольку оно имеет useEffect зависимость. Вопрос в том, хотите ли вы, чтобы useEffect всегда вызывался при append обновлении?

2. пожалуйста, создайте уменьшенное воспроизведение вашей проблемы, это трудно определить, посмотрев на компонент, который вы сумбитовали. для лучшего понимания useEffect вашего лучшего учебного ресурса overreacted.io/a-complete-guide-to-useeffect

Ответ №1:

 useEffect(() => {
    if(mode === 'create') append();
  }, [append]) //// Only re-run the effect if append changes
  

append() выполняется в эффекте, поэтому каждый раз, когда вызывается эффект, он выполняет append() , который снова вызывает эффект, который выполняет append() … бесконечно.

Из документации reactjs