React-hook-form: Edge case involving async form initialization with react-select

Created on 12 Jun 2020  ·  37Comments  ·  Source: react-hook-form/react-hook-form

Describe the bug
So I have this edge case when I first built form involving async load of data. Basically I need to load 2 this, the data form for edit, and the options of a select. However, in my data form I only have the id/value for initialize the select. In addiction, for better UX, I don't want to put conditions for load my component while those data are initializing. Instead of that, I prefer using isLoading prop of react-select.

I tried 3 approaches. My first thought was to filter the options and put the filtered value as defaultValue of the component. It doesn't worked. The second one, doing a useEffect + setValue. In this one I got the expected behavior. However the form got dirty, which is an inconvenient thing. At last, I tried using reset function from react-hook-form, but I got in infinite loops involving useEffect and getValues.

I built the codesandbox in a way to get real close to my case, including the libs I've used in my project.

Codesandbox link (Required)
https://codesandbox.io/s/react-hook-form-controller-template-zp6sc?file=/src/index.js:1677-1693

question

All 37 comments

At last, I tried using reset function from react-hook-form, but I got in infinite loops involving useEffect and getValues.

Can you share a codesandbox for this behavior? also why you need to run getValues()

I edited the codesandbox with the code I remembered to use. but I was not able to reproduce the behaviour. The good news is that it stopped the infinite loop. Any ideas?

also why you need to run getValues()

In my form I have other fields. Correct me if I am wrong, but I believe if I don't use getValues(), the other fields will be cleared.

also why you need to run getValues()

In my form I have other fields. Correct me if I am wrong, but I believe if I don't use getValues(), the other fields will be cleared.

getValues() is a "dumb" function to read what's in the form. watch() is the "smart" function to subscribe for the form changes. hope this clear thing up a bit.

getValues() is a "dumb" function to read what's in the form. watch() is the "smart" function to subscribe for the form changes. hope this clear thing up a bit.

Thanks for clarifying this to me. Sometimes is difficult to decide which one to use.

So, for this case in specific, the correct approach is to use useEffect + reset() + watch()?

for your use case should be just: useEffect + reset()

I would recommend you read the doc again, as there are examples which may help you understand better.

In case, I miss understood you this morning. Is this what you after? https://codesandbox.io/s/condescending-einstein-ns7y0?file=/src/App.tsx

I would recommend you read the doc again, as there are examples which may help you understand better.

As you suggested, after some reading and testing now I understand why I don't need getValues()/watch(). reset() works like I want it does, resetting just what I pass to it.

And I managed to make this work just using what you said. I edited the codesandbox with the solution. The only minor problem is that reset() runs twice in form initialization.

Thank you for the support @bluebill1049!

I would recommend you read the doc again, as there are examples which may help you understand better.

As you suggested, after some reading and testing now I understand why I don't need getValues()/watch(). reset() works like I want it does, resetting just what I pass to it.

And I managed to make this work just using what you said. I edited the codesandbox with the solution. The only minor problem is that reset() runs twice in form initialization.

Thank you for the support @bluebill1049!

aha great! I am glad it's working. Music to my ears. My pleasure too.

And I managed to make this work just using what you said. I edited the codesandbox with the solution. The only minor problem is that reset() runs twice in form initialization.

can you append some logic to prevent this from happening?

can you append some logic to prevent this from happening?

I tried using useRef as a flag, but had no success. I will have to spend more time on this. Any ideas?

i only see one console log in your codesandbox. (so it's working?)

i only see one console log in your codesandbox. (so it's working?)

For me when I first load the page of codesandbox it outputs twice. But when I edit something and save (hot reload), it outputs once.

I applied the solution on my project and the reset only occours once. So I think it is some issue with codesandbox.

I am closing the issue.

After write a bit more complex case, I am reopening the issue. Some things sounds weird to me.

  1. Even with this complex case, using pure check (without lodash), things worked fine.
  2. However, after put lodash at work, I could reproduce the issue. With two combo boxes using the useEffect + reset() logic, one of them didn't reset with the value.
  3. At this point, if I comment one of the combo boxes, the one that was not working starts working.

One of my thoughts is that the return of the lodash's functions (get(), find()) are making reset() get lost.

After write a bit more complex case, I am reopening the issue. Some things sounds weird to me.

  1. Even with this complex case, using pure check (without lodash), things worked fine.
  2. However, after put lodash at work, I could reproduce the issue. With two combo boxes using the useEffect + reset() logic, one of them didn't reset with the value.
  3. At this point, if I comment one of the combo boxes, the one that was not working starts working.

One of my thoughts is that the return of the lodash's functions (get(), find()) are making reset() get lost.

Do you have an isolated/simple CSB for this?

Do you have an isolated/simple CSB for this?

I edited the original one.

https://codesandbox.io/s/react-hook-form-controller-template-zp6sc?file=/src/index.js:1677-1693

could you please create a separate codesandbox just for this issue? easier for me.

Do you have an isolated/simple CSB for this?

could you please create a separate codesandbox just for this issue? easier for me.

I created a simpler separated codesandbox, without lodash. I don't know If this is what you need. Sorry if I misunderstood what you mean.

https://codesandbox.io/s/react-hook-form-controller-template-j3mu6?file=/src/index.js

Even with this complex case, using pure check (without lodash), things worked fine.

By the way, I could not reproduce this case with things working without lodash. I am very confused right now.

I would recommend producing a codesandobx isolate the problem without any other dependency. without using real query just a simple dumb component with setTimeout. see if you can reproduce the problem.

I would recommend producing a codesandobx isolate the problem without any other dependency. without using real query just a simple dumb component with setTimeout. see if you can reproduce the problem.

I think I have a more tangible problem. I created a CSB using only react-hook-form, making the requests with pure promises and setTimeout(). I could find the problem when I set the exactly same value to milliseconds of the user's request.

https://codesandbox.io/s/react-hook-form-controller-template-sgiwj?file=/src/index.js

I inserted comments at the lines where it sounded weird to me. Even lagging the user's request, if I comment the second combobox (Type), user's select loads.

i would like to help but your codesandbox is 186 lines... can you please create a small CSB to isolate the problem?

i would like to help but your codesandbox is 186 lines... can you please create a small CSB to isolate the problem?

I reduced it to 106 lines:
https://codesandbox.io/s/react-hook-form-controller-template-sgiwj?file=/src/index.js

i will take a look tmr. just to be clear, to reproduce the issue just change the timer to a greater number?

React.useEffect(() => {
    (async () => {
      // Changing the milliseconds value of setTimeout to a high value, it stops working
      const _user = await new Promise(resolve =>
        setTimeout(() => resolve(mockUser), 2000)
      );
      setUser(_user);
    })();
  }, []);

i will take a look tmr. just to be clear, to reproduce the issue just change the timer to a greater number?

React.useEffect(() => {
    (async () => {
      // Changing the milliseconds value of setTimeout to a high value, it stops working
      const _user = await new Promise(resolve =>
        setTimeout(() => resolve(mockUser), 2000)
      );
      setUser(_user);
    })();
  }, []);

For me, with this value of 2000 milliseconds the first select didn't load. If the second select is commented, the first one always load.

  1. That's a race condition issue, also why reset is at the component level instead of root level?
 React.useEffect(() => {
    if (loadFromValue) reset({ [name]: loadFromValue });
  }, [name, loadFromValue, reset]);

when you reset, you probably need to get the current form value and merge with your data.

eg: getValues()

  1. That's a race condition issue, also why reset is at the component level instead of root level?

I kept it at component level just for better reuse of the code across my app.

I tried using getValues() and it didn't work to me. At the end the solution I tried to achieve seems not to be the usual way to use the library. I think I will try a different approach.

Thank you for your support and patience @bluebill1049!

no worries, I think the set up is wrong. the moment you set state both input will get re-render and trigger reset, you should isolate and only re-render one at time depend on your selection not always run them together.

I think in my real project I do this way. Only my individual select components loads it's own data. I am right now doing a refactor in my code. When I do a console.log at theuseEffect code, only one execution is made. As you said before, due to the fact getValues ​​() be "dumb", it doesn't get the last value updated by reset () in other places. With this, if I have defaultValue defined in a input for example,reset ()is" clearing "it's value.

Follow below the output:

FormControlSelectStatus:

{
  "name": "status",
  "loadFromValue": {
    "label": "Finished",
    "value": "FINISHED"
  },
  "getValuesOutput": {
    "client": "undefined",
    "return_date": "",
    "reservation_date": "",
    "withdrawal_date": "",
    "id": "",
    "status": "undefined",
    "seller": "undefined"
  }
}

FormControlSelectSeller:

{
  "name": "seller",
  "loadFromValue": {
    "label": "Seller 1",
    "value": 1
  },
  "getValuesOutput": {
    "client": "undefined",
    "seller": "undefined"
  }
}

FormControlSelectClient:

{
  "name": "client",
  "loadFromValue": {
    "label": "Client 20",
    "value": 20
  },
  "getValuesOutput": {
    "client": "undefined",
    "return_date": "",
    "reservation_date": "",
    "withdrawal_date": "",
    "id": "",
    "status": "undefined"
  }
}

hmmm getValues() should return what's been registerd value tho. If i got time, i will try to do a demo tmr.

your problem in native select:
https://codesandbox.io/s/recursing-fast-on3wm?file=/src/App.tsx:490-500

here is a fix (hack): the problem because your data set is in object/array which useEffect will have outdated value (when not included in the dep) and Object.is at useEffect is simply not going to work for the data compare part.

https://codesandbox.io/s/recursing-fast-on3wm?file=/src/App.tsx

you can look around for deep compare useEffect which may work out for you :)

your problem in native select:
https://codesandbox.io/s/recursing-fast-on3wm?file=/src/App.tsx:490-500

here is a fix (hack): the problem because your data set is in object/array which useEffect will have outdated value (when not included in the dep) and Object.is at useEffect is simply not going to work for the data compare part.

https://codesandbox.io/s/recursing-fast-on3wm?file=/src/App.tsx

For me the two links open the same code. Besides, I don't see how equivalent this example is with my problem. In my case I have to initialize the form for edition, using async data coming from the back-end. So, basicly in my case I have 1 (data of form) + N (options of selects) async requests that I have to put together once everything is ready.

Now I see the difference =)

I am trying to get your mindset on this. I don't realize how to put this solution to work in my problem. RHF do anything similar to this example behind the scenes when using getValues​​()?

As I said, I think my problem is a little more complex, because it involves one more "variable" to the load of the form.

The fundamentals remain the same. Try to read my comments again, it's a React related question, instead of RHF (not try to push around responsibility). Try to compare the two CSB and see the diff.

Was this page helpful?
0 / 5 - 0 ratings