react-final-form: Warning: Cannot update a component from inside the function body of a different component.

Are you submitting a bug report or a feature request?

bug report

What is the current behavior?

After npm upgrading to the latest React, I am getting the following warning in my console from the React Developer tools Chrome extension: “Warning: Cannot update a component from inside the function body of a different component.”

The component trace just points to a line with a boring/standard use of Field in JSX. Quickly going through the app I’m working on, this warning happens everywhere, on every use of Field in the app. For example, the warnings point to lines like the 2nd to last line (RadioField = ...) of this file (please excuse the coding style here). Again, there is nothing special about this code; it’s happening on every Form that has components that use Field.

import React from "react";
import { Field } from "react-final-form";

const Radio = ({label,input,children,className}) => {
 const { checked } = input;
  return (
    <label className={"input-radio "+ (checked? "is-checked ": "")+
     className}>
      {children && children}
      <input
        {...input}
        className="form-radio"
      />
      <var title={label}>{label}</var>
    </label>
  );
};

Radio.defaultProps = { label:"", meta:{}, input:{}, children:[] };

const RadioField = props => <Field {...props} component={Radio} type="radio"/>;

export default RadioField;

What is the expected behavior?

No warning

Sandbox Link

I’ll make one if you need it, but I think the link below in Other Information succinctly explains what is happening and a hooks based workaround that react-final-form can implement.

What’s your environment?

ChromeOS 79 React 16.13.0 released Feb 26th, 2020 Final Form 4.18.7 React Final Form 6.3.5

Other information

See here: https://reactjs.org/blog/2020/02/26/react-v16.13.0.html#warnings-for-some-updates-during-render

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 48
  • Comments: 49 (14 by maintainers)

Most upvoted comments

I’m also seeing this warning after updating to React 16.13.0. For me, it only seems to occur when a field is conditionally rendered.

Sandbox link: https://codesandbox.io/s/empty-forest-6m0ri?fontsize=14&hidenavigation=1&theme=dark

Hi guys. Sorry to be so late on this. I got laid off at work and have had two children locked in the house with me for weeks.

Upside of this idea: very easy to implement (just a couple of lines have to be changed), almost no side-effects.

A lot have work has been done to ensure that things look right “on first render”, which is why things are the way they are. This is going to be a major breaking change. I just tried it and 75% of the unit tests failed.

Downside: it changes the useField() API, which is a public one, so this would affect projects using it directly instead of the Field component.

Yep.

This needs more investigation.

Published fix in v6.4.0 and final-form@4.19.0.

Summary: useField() was doing a quick subscribe-and-unsubscribe of the field on first render to get the initial state. React v16.13.x doesn’t like this, as it was calling setState() in the containing <Form/> component. So I’ve added a silent flag to form.registerField() which allows this register-unregister trick to happen without any ripples going out to other subscribers. This solves the problem.

would you mind explaining why notifyFieldListeners() has an optional name passed into it and the usage there?

That means “only notify listeners to this one field”. It doesn’t seem to have any other usages, so it’s vestigial from an earlier time, but it’s just what was needed in this case.

It is called quite a bit and seems to only get name passed in when silent is true now?

Yes. The rest of the times, it really does need to notify all the other fields, as they can depend on each other.

Not sure what safeFields is.

A copy of the fields structure must be made to iterate through, since some of the things that get called can result in fields being unregistered.

A good example of this happening is the Wizard Form Example. Any time the Previous or Next buttons are pressed, just about every Field from all pages throws this warning.

I have some good news for you guys…

Published fix in final-form@4.19.1. It fixes that sandbox, @yann-combarnous.

I did some analysis on this issue and found the source of the problem, at least for conditionally rendered fields, but this might also be the cause in other cases:

https://github.com/final-form/react-final-form/blob/f3c1aea93b520c0620f1b8090f5966e0da94a798/src/useField.js#L90-L105

In here, the field is synchronously registered, which causes a synchronous update to the form state, which in turn synchronously triggers setState() on the Form:

https://github.com/final-form/react-final-form/blob/f3c1aea93b520c0620f1b8090f5966e0da94a798/src/ReactFinalForm.js#L97

So what it boils down to is that the mounting of a Field directly calls setState() on the Form. This is apparently not a problem in some cases, but in others. I’ll have to do some additional analysis to figure how how to rewrite that code to use useEffect() instead. If anyone else already has a good idea, just reply, I’ll keep a close eye on this thread.

Same here, i’ve upgraded to the newest versions of final-form and the issue still remains.

I’m still having issues with the new version, also using fields with validation.

@erikras I noticed that the silent trick does not work for fields with validators since during the unsubscribe, line 902 still runs https://github.com/final-form/final-form/blob/64225dd3589020297c5e23209630ac31afd46c5c/src/FinalForm.js#L902

Don’t understand how an elite developer like you would be laid off, even now… Also, try Upwork. With your resume, you should be making a lot of money immediately working remotely.

On Mon, Mar 30, 2020, 6:46 AM Erik Rasmussen notifications@github.com wrote:

Hi guys. Sorry to be so late on this. I got laid off at work and have had two children locked in the house with me for weeks.

Upside of this idea: very easy to implement (just a couple of lines have to be changed), almost no side-effects.

A lot have work has been done to ensure that things look right “on first render”, which is why things are the way they are. This is going to be a major breaking change. I just tried it and 75% of the unit tests failed.

Downside: it changes the useField() API, which is a public one, so this would affect projects using it directly instead of the Field component.

Yep.

This needs more investigation.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/final-form/react-final-form/issues/751#issuecomment-605951654, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC2O764S54EQ7INNMQ5UGLTRKCBBFANCNFSM4K7IAAGQ .

No worries, everything will be fine @erikras . 🍻

Yeah, I’m gonna get a job quickly; have several offers already, but it still adds additional stress to the already elevated global level.

Companies restructure and my position disappeared out from under me. Wasn’t to do with my performance or skill level.

A lot of popular libraries are affected by this warning : https://github.com/facebook/react/issues/18178 It’s a warning party in my console right now as I also use urql which is affected as well. 😂

I’m also seeing this warning after updating to React 16.13.0. For me, it only seems to occur when a field is conditionally rendered.

Sandbox link: https://codesandbox.io/s/empty-forest-6m0ri?fontsize=14&hidenavigation=1&theme=dark

Yes, I got this error for a select box that is dynamically rendered.

I have created a generic dropdown menu component using (Material-Ui Autocomplete), so I can use where I need. But I am facing a warning,

“Warning: Cannot update a component (ProductSubCategory) while rendering a different component (ForwardRef(Autocomplete)). To locate the bad setState() call inside ForwardRef(Autocomplete)

Note

I followed different links but couldn’t get the proper answer I have a slightly different scenario. I have created a generic dropdown menu component and use inside other components where needed.

Here is code of dropdown menu component `

const AsyncDropDownMenu: React.FC<IAsyncDropDownMenu> = memo(
 ({
title,
id = "asynchronous-dropdown-menu",
loadData,
onSelect,
fieldError,
}) => {
 const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState<IOptionTypes[]>([]);
const loading = open && options.length === 0; 
const dispatch = useDispatch();   

useEffect(() => {
  let active = true;
  if (!loading) {
    return undefined;
  }
  (async () => {
    const data = await dispatch(loadData());
    if (active) {
      setOptions(data);
    }
  })();    

  return () => {
    active = false;
  };
}, [loading]);    

useEffect(() => {
  if (!open) {
    setOptions([]);
  }
}, [open]);    

return (
  <Autocomplete
    id={id}
    open={open}
    onOpen={() => {
      setOpen(true);
    }}
    onClose={() => {
      setOpen(false);
    }}
    onChange={(event, value) => seletOption(value)}
    getOptionSelected={(option, value) => {
      onSelect(value);              // When I call onSelect then I got warning, which I mentioned above.
      return option.name === value.name;
    }}
    getOptionLabel={(option) => option.name}
    options={options}
    loading={loading}
    renderInput={(params) => (
      <TextField
        {...params}
        label={title}
        helperText={fieldError.helperText}
        error={fieldError.error}
        InputProps={{
          ...params.InputProps,
          endAdornment: (
            <>
              {loading ? (
                <CircularProgress color="inherit" size={20} />
              ) : null}
              {params.InputProps.endAdornment}
            </>
          ),
        }}
      />
    )}
  />
);
}
);
export { AsyncDropDownMenu };`

Here is the code of Component in which I am using dropdown menu component

`

const ProductSubCategory: React.FC = () => {
const classes = CommonStyle();
const subCatList=[];
const dispatch = useDispatch();

//#region error states and function
 const [selectedOption, setSelectedOption] = React.useState({
      id: 0,
      name: "",
  });

 const [categoryError, setCategoryError] = React.useState({
    error: false,
    label: "",
    helperText: "",
    validateInput: false,
  });
 const onError = (action: Function, message: string = "Required field") => {
   action({
         error: true,
        label: "required",
        helperText: message,
        validateInput: true,
   });
 };

 //#endregion

 const onselect = (value: any) => {
 if (categoryError.validateInput) {
  setCategoryError({
     error: false,
     label: "",
     helperText: "",
     validateInput: false,
    });
  }
  setSelectedOption(value);
};

//#region grid columns
  const columns = [
  {
    title: "Category",
    render: (rowData: ITemp) => {
     return rowData.categories ? rowData.categories.name : "";
   },
 field: "category",
  lookup: {},
   editComponent: (props: any) => (
     <AsyncDropDownMenu
       title="Category"
       loadData={loadCategories}
       onSelect={onselect}
       fieldError={categoryError}
     ></AsyncDropDownMenu>
   ),
 },   
];

//#endregion

//#region validate input data
const onValidate = (
  data: any,
  reject: Function,
  resolve: Function,
  action: Function,
 isUpdate: boolean,
 oldData: any
) => {
  if (Object.keys(data).length === 0 && selectedOption.id === 0) {
    onError(setNameError);
    onError(setCategoryError);
    reject();
  } else if (selectedOption.id === 0) {
    onError(setCategoryError);
    reject();
  } else {

    if (isUpdate) {
      if (data !== oldData) dispatch(action(data, reject, resolve));
      else reject();
    } else {
      dispatch(action(data, reject, resolve));
    }
  }
};
//#endregion
 return (
        <div>
                   <CustomMatrialTable
                      title="Product Sub Categories"
                     isLoading={isLoading}
                     col={columns}
                     data={subCatList}
                   ></CustomMatrialTable>
             </div>
           );
          };
      export { ProductSubCategory };

`

Not a fix. But i realized that when i added the subscription prop to the form: ( subscription={{ submitting: true, pristine: true }} in my case) The errors disappeared.

I have the same issue with ff 4.19.1 and rff 6.5.0. I have not found a way to read the form state from outside my component safely. Is there a workaround?

I’m seeing this when using useField in conjunction with a select field built with downshift. Using the latest version of both packages.

I’ve solved it temporarily by wrapping the offending function in setTimeout:

setTimeout(() => input.onChange(state.selectedItem.value))

Larger section of code for context:

import { useSelect } from 'downshift'

const onStateChange = state => {
  if (state.selectedItem) {
    // setTimeout quiets react warning
    setTimeout(() => input.onChange(state.selectedItem.value))
  }
}

const {
  isOpen,
  selectedItem,
  getToggleButtonProps,
  getLabelProps,
  getMenuProps,
  highlightedIndex,
  getItemProps
} = useSelect({
  items,
  itemToString: item => (item ? item.label : ''),
  onStateChange,
  selectedItem: items.find(item => item.value === input.value)
})

@atuttle yep I have the very same error with checkbox adding a new Field. 😃

I have seen a reduction in these errors in my app, but they are not completely eliminated.

I have a form that shows some assumed values and gives the user the option to edit them by clicking on a link to make the form fields appear. Previously, that click would throw a couple of these errors (I think 2 or 3? odd since there are 5 fields). Those errors no longer appear. However, that form also includes a checkbox that, when toggled a certain way, adds an additional field. Adding that additional field is still throwing the error.

I’m not a frequent user of CodeSandbox, but hopefully I’ve done this right. The sandbox from @jsalis demonstrated my issue (checkbox to make field appear) so I forked it and updated the final-form version to 4.19.1, and the error still happens there: https://codesandbox.io/s/react-16130-final-form-warning-tnid2

Published fix in final-form@4.19.1. It fixes that sandbox, @yann-combarnous.

Thanks @erikras . Still getting one scenario where this error shows up, it seems. Seems to happen on submit when form.registerField is used. Replacing it with OnChange from react-final-form-listeners also seems to not trigger the error.

Will try and narrow down the specific pattern, have made a short try today at a CodeSandbox, with no luck so far. Stack trace:

Screenshot 2020-04-02 at 11 05 30