0

I have two components, where selectBirthMonth depends on SelectBirthYear. Which is why I use useEffect, to watch the selectedYear change, so that I can change selectedMonth in case it is needed.

code sandbox

So in controller context my components look the following

<Controller
   control={control}
   name="selectedBirthYear"
   defaultValue={years[0]}
   render={({ field }) => (
     <SelectBirthYear
       field={field}
       years={years}
       value={selectedYear}
       defaultValue={selectedYear}
       onChange={useEffect(() => {setSelectedYear(field.value)})}
    />
   )}
 />
</div>

and ...

<Controller
       control={control}
       name="selectedBirthMonth"
       defaultValue={months[0]}
       render={({ field }) => (
         <SelectBirthMonth
           field={field}
           startYear={startYear}
           selectedYear={selectedYear}
           months={months}
           value={selectedMonth}
           defaultValue={selectedMonth}
           reducedMonths={reducedMonths}
           onChange={useEffect(() => setSelectedMonth(field.value))}
        />
       )}
     />

SelectBirthMonth totally ignored the following code though:

  const [selectedMonth, setSelectedMonth] = useState(months[0]);

  const watchYearChange = () => {
    if(Number(selectedYear.name) == startYear){
      setSelectedMonth(reducedMonths[reducedMonths.length - 1]);
    }
  };

  useEffect(() => watchYearChange(), [selectedYear]);

Meaning, no matter, which year is chosen, the month won't react. What do I miss?

Katharina Schreiber
  • 1,187
  • 2
  • 11
  • 38
  • Can you please update the CodeSandbox as it is not using RHF right now? At least there is no `` used. In short: there is a much simpler way of what you want to do then using useEffect (also you are using it wrong by passing it as a callback) as you can use RHF's `watch` here. – knoefel Oct 24 '21 at 11:02
  • Yes, it was working before the , with it broke, unfortunately, that is why I thought there is no point of providing broken sand box. What would be another way exactly, I am so stuck, since days. But, let me try and see, if I can showcase the broken code first – Katharina Schreiber Oct 24 '21 at 11:06
  • https://codesandbox.io/s/jolly-brahmagupta-fdfkb - please see the changed sandbox – Katharina Schreiber Oct 24 '21 at 11:17
  • I have been playing around a bit and the problem is def, that the Controller won't allow the value to-rerender. The year is being watched, either with useEffect or with watch, as I can log every change to the console. The problem ist, the setSelectedMonth won't fire depending on year / won't render – Katharina Schreiber Oct 24 '21 at 13:20

2 Answers2

1

I would recommend using a small date library like date-fns to handle date related things. It's a great package, here are the docs for it. Also it can handle i18n for you if this should be a requirement in the future.

I used it in the below CodeSandbox and also corrected a few things:

  • when you use RHF you don't need to setup extra state for your component as RHF will manage the form state for you.
  • it's much simpler if you just use one date for the whole birthdate - this way you will always have the current values for year, month and day and don't need to watch values or use extra defined state

Edit frosty-bash-vv1xf

knoefel
  • 5,991
  • 3
  • 29
  • 43
  • Thank you so much for your effort, unfortunatelly it won't re-render the month accordingly to the chosen year. What I have bult was kinda age validation, basically, I can't choose any date, which sets my age less than 18. So, if I go an choose 2002, December, and then switch to 2003, December needs to be re-rendered to October (cause we have octber now). Choosing any avaliable date won't do the validation. Any ideas on that? – Katharina Schreiber Oct 24 '21 at 13:44
  • I updated my CodeSandbox and got rid of the watched values as they are not needed. And from an UX perspective it's much cleaner to give the user the feedback that the date must be older then at least 18 years on submit and then show an validation error message, so that the user can correct it. I implemented that behaviour In the CodeSandbox. – knoefel Oct 24 '21 at 18:01
  • Thanks for your feedback. The value still won't update. I figured, it was a matter of setValue, as somehow the value in Controller gets cached and won't get re-rendered, from what I understood with my limited IT knowledge. Please see the solution below. – Katharina Schreiber Oct 24 '21 at 18:07
  • Which value won‘t update? The days change according to the selected year (leap year) or month. The validation is triggered onSubmit, so it will show an error message when the user set the birthdate to a date not older then 18 years. – knoefel Oct 24 '21 at 18:26
  • Your usage of `useEffect` is wrong as it should not be used as callback for your `onChange` handler. Furthermore you're working against RHF, as you are saving state by using `useState` although RHF has all the relevant information saved in it's own form state. – knoefel Oct 24 '21 at 18:34
  • You can test in my CodeSandbox the behaviour i described by: 1. After Load hit Submit (it will print out the selected birthdate in the console) 2. Change the month to Nov or Dec or the days to a value greater then today 3. An error message will appear The same error message will appear if you change the month or days to a non valid value after page load and then hit Submit. – knoefel Oct 24 '21 at 18:43
  • Ok, one last suggestion. ;) i made another [CodeSandbox](https://codesandbox.io/s/stoic-matan-mzgcb?file=/components/SelectBirthDay.js), where you can only select valid dates. So if you for example select the year 2003 the options for the months November and December are disabled and can't be selected. The same logic applies to the days select. So the user can't select a birthdate younger then 18 years old. I hope this is what you want. Anyway i would strongly advice not to use your solution. – knoefel Oct 24 '21 at 19:04
  • Thanks you! That is what I had im my original sandbox. Still, a user can trick it out by first choosing 2002 > December and then changing the year to 2003. Console loggs then birthdate: Tue Nov 25 2003, no error accures. useEffect in onChange is a hack, I am aware, cause otherwise I get console errors, exactly what you say - I can't basically set state there. Maybe, choosing RHF in the first place for my purpouses was a wrong decision, cause you are right, I totally had to hack around, which is a bad style. – Katharina Schreiber Oct 25 '21 at 06:04
  • 1
    I think RHF is totally a good choice for your use case, it's more not so good UI/UX design to magically change field values as this could confuse the user. A simple error message is definitely more user friendly as it is clear to understand what's wrong with current selected birthdate. You are totally right with your last comment and the use case you described. I somehow removed the validation rule for the ``. I added it again, so now it will show the error message, when the user should do the trick you described above. – knoefel Oct 25 '21 at 10:26
  • 1
    Here is the updated [CodeSandbox](https://codesandbox.io/s/stoic-matan-mzgcb?file=/components/Form.js) I think now it should not be possible to submit a wrong birthdate. If you really need to get it done your way by automagically change the other fields - let me know and i will alter the CodeSandbox to this requirement. – knoefel Oct 25 '21 at 10:28
  • Now validation works, but ye, unfortunatelly I'd need to re-render, that's the project requirement. I already integrated a text warning, but still need to re-set tha date if someone tries to trickthe from .... – Katharina Schreiber Oct 25 '21 at 13:45
  • 1
    Ok, last try. i changed it to your requirements - so now it will jump back to the youngest possible date (today -18 years) if the user tries to trick the form. I have left the form validation so now it should very safe. Here is the new [CodeSandbox](https://codesandbox.io/s/frosty-bash-vv1xf?file=/components/Form.js) – knoefel Oct 25 '21 at 14:25
  • Apologies, I am in the hospital, so didn't come to this just yet. Could you please link the latest sandbox to your solution, so I can accept the answer and upvote the rest? – Katharina Schreiber Oct 26 '21 at 11:56
  • Oh no, i'm really sorry to hear that. I updated the answer with the latest sandbox. But what's far more important: i hope you get well soon!!! – knoefel Oct 26 '21 at 12:16
  • dear Knoefel, maybe you have an idea on this too? https://stackoverflow.com/questions/69752917/localize-date-fns – Katharina Schreiber Oct 28 '21 at 12:42
  • Dear Knoefel, I am facing another challenge. aybe you have an idea? :) https://stackoverflow.com/questions/70565916/react-hook-form-wont-show-error-messages – Katharina Schreiber Jan 03 '22 at 12:41
0

The answer is way too easy, to be working, but it does. Several times I've been reading this post here How to change React-Hook-Form defaultValue with useEffect()?, but could't really understand, where and how do I use setValue. As I assumed, the value of my select just wasn't changing, even though I was watching the sibling state change.

So I put the setValue into the useEffect hook and the rest stayed the same:

  const monthSelect = (data) => {
    setSelectedMonth(months[data.id - 1]);
  };
  
  const watchYearChange = () => {
    if(Number(selectedYear.name) == startYear){
      setSelectedMonth(lastAvaliableMonth)
    }
  };

  useEffect(() => setValue('selectedBirthMonth', lastAvaliableMonth), [selectedYear]);

Here are two siblings just in case:

<Controller
  control={control}
  name="selectedBirthYear"
  defaultValue={years[0]}
  render={({ field }) => (
  <SelectBirthYear
     field={field}
     years={years}
     value={selectedYear}
     defaultValue={selectedYear}
     onChange={useEffect(() => {setSelectedYear(field.value)})}
   />
 )}
/>

... and

    <Controller
      control={control}
      name="selectedBirthMonth"
      defaultValue={selectedMonth}
      render={({ field }) => (
      < SelectBirthMonth 
          field={field}
          startYear={startYear}
          selectedYear={selectedYear}
          months={months}
          value={selectedMonth}
          reducedMonths={reducedMonths}
          onChange={useEffect(() => monthSelect(field.value)), [selectedMonth]}/>
      )}
/>

If this solution somehow is not good, please let me know in the comments. I am a total beginner.

Katharina Schreiber
  • 1,187
  • 2
  • 11
  • 38