react-hook-form: issue: Select value is not aligning with dynamic options based on other field values

Version Number

7.33.1

Codesandbox/Expo snack

https://codesandbox.io/s/react-hook-form-csb-m5t03g?file=/src/ReactHookForm.tsx

Steps to reproduce

I tried condensing this down as much as possible. The original functionality has buttons to increment or decrement single episodes, but I believe this is all under the same issue and can be demonstrated solely by the finish button.

  1. Select any season other than the last
  2. Click on ‘finish’

You will see that both the episode value and episode options are correct, but the select is not displaying the correct value. If you are already on the final season, then clicking ‘finish’ will immediately show the correct value. I know this is happening because we are transitioning from a season with less possible episodes than the one that is being set. If the final season has less than or equal to the number of episode of the current season, then the finish button works properly.

Expected behaviour

The <select> for progress_episode gets its possible values from watch('progress_season') to determine the episode count. When using setValue to update the inputs outside of the select element, watch seems to display the correct values, but the current selection on the input doesn’t update.

What browsers are you seeing the problem on?

No response

Relevant log output

No response

Code of Conduct

  • I agree to follow this project’s Code of Conduct

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (10 by maintainers)

Most upvoted comments

I didn’t know that either, that is really helpful. I use setValueAs: (v) => parseInt(v) elsewhere so am familiar with the callback version. I will switch to something like that. Thank you!

The concept of controlled vs uncontrolled inputs is outside RHF and is about where the input’s value is stored.

You can better learn about this concept and the RHF Controller API yourself, but the TLDR; is: controlled components are “controlled” with a value from outside, like React’s state, so, they’re being “fed” the value into their value prop or whatever. Uncontrolled components on the other hand, are the source of truth of their value, like a plain HTML input without JS; RHF listens for it’s onChange event and “makes note” of it’s value, but never “tells” the input what the value is.

This makes a lot more sense. Thank you for all your help!

I also want to say how much I love this library. It is so powerful, thank you!

If you’ve never used the Controller API before, make note of what the field object is and that it has the value prop which you can spread into an input along with the other props you can find returned from the register method call. But register never returns value, since it’s designed to handle the uncontrolled components

The concept of controlled vs uncontrolled inputs is outside RHF and is about where the input’s value is stored.

You can better learn about this concept and the RHF Controller API yourself, but the TLDR; is: controlled components are “controlled” with a value from outside, like React’s state, so, they’re being “fed” the value into their value prop or whatever. Uncontrolled components on the other hand, are the source of truth of their value, like a plain HTML input without JS; RHF listens for it’s onChange event and “makes note” of it’s value, but never “tells” the input what the value is.

@Moshyfawn this is super helpful, thank you! I have never worked with Controlled components with React hook Form before so this will be a first for me. I will have to read up on them to understand what is going on better. But your example totally works so that’s awesome!

I have to dynamically update the episode options based on which season is selected so I’m not sure how else to make sure that always renders before the value is set.

I’d say, the progress_episode input should be controlled instead, because it’s more of a React component life-cycle issue at this point.

Here’s a very crude example; you can optimize it however you want, but the idea is to “control” the input’s value outside of it, instead of using the uncontrolled API (register) where the input’s value source of truth is inside the input and “hoping” value’s gonna be updated in time after the “outside” update

<Controller
  control={control}
  name="progress_episode"
  render={({ field }) => (
    <select {...field}>
      {episode_options.map((option) => (
        <option key={option} value={option}>
          Episode {option}
        </option>
      ))}
    </select>
  )}
/>

Exactly. If the final season has an episode number that is equal to or less than the season you are currently in, then the button performs correctly.

I have to dynamically update the episode options based on which season is selected so I’m not sure how else to make sure that always renders before the value is set.

thanks, @Moshyfawn got it thanks. coupld issues:

  • valueAsNumber is not applicable for select
  • the select only has values up to 6 hence set select 13 is not going to work
Screen Shot 2022-07-24 at 10 32 30 am

Thanks for the detailed issue report and condensed csb (i can see the effort), would you mind sharing a video on the actual issue as well? It probably would be easier to spot the issues.

also curious about the following code maybe the root cause:

const episode_options = getEpisodeOptions(
  progress_options,
  watch("progress_season")
);

could you also double-check if the watch itself returns the expected results for you?