Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The new React lifecycle methods - breaking change #3953

Open
1 task done
magaton opened this issue Nov 13, 2023 · 6 comments
Open
1 task done

The new React lifecycle methods - breaking change #3953

magaton opened this issue Nov 13, 2023 · 6 comments
Labels
awaiting response feature Is a feature request

Comments

@magaton
Copy link

magaton commented Nov 13, 2023

Prerequisites

What theme are you using?

mui

Is your feature request related to a problem? Please describe.

Up until version 5.13.2 our app, based on rjsf (mui) was working well. Exceptions are the issues I have already filed:

But with version 5.13.3 in Form.tsx UNSAFE_componentWillReceiveProps was replaced with: componentDidUpdate and getSnapshotBeforeUpdate.

Here is the gist of the problem. We have 3 screen admin UI, each with its own schema and UI schema. We also have a couple of custom select widgets that take a value from formData, do some calculations, and update the state (formData) with the result array.
The issue happens on the initial render of, e.g. 2nd screen, where based on the formData from the 1st screen we need to set defaults (update formData). With UNSAFE_componentWillReceiveProps, it was working well, namely when our custom selects fired, the change was detected in UNSAFE_componentWillReceiveProps and the screen was re-rendered, but now, componentDidUpdate is not called on the initial render (as described in jsdocs and react docs).

I understand that the main use case supported by the current design is to change state based on onChange property in the <Form>, but in our case (initial render), there is no onChange event. We read passed formData in the custom widget, do the calculation, and update the state that is then used for conditional schemas on the 2nd screen.

What would be the recommended way to force re-render in our custom select widget, now that there is no UNSAFE_componentWillReceiveProps?

We feel stuck :(
I filed this issue as a feature since it is related to our multi-form scenario with custom widgets, so not really a common use case. Sorry, if not appropriate.

Thanks very much in advance!

Describe the solution you'd like

Any idea/workaround/feature-switch that would allow re-rendering from our custom widgets.

Describe alternatives you've considered

We are not a react shop, so most of our investigation was chasing our own tail. We have tried:

  • adding static getDerivedState, no luck
  • force props.onChange in our custom select which seems to help, but this results with a React warning and smells badly
  • detect the initial render with useRef - it does help when our custom select works with the previous formData, but we also have a custom select that needs both previous formData and specific fields on the 2nd form filled (so it is not just initial render, that we need to handle).
@magaton magaton added feature Is a feature request needs triage Initial label given, to be assigned correct labels and assigned labels Nov 13, 2023
@heath-freenome
Copy link
Member

@magaton Odd. I would have expected that the constructor for Form would have determined that the formData updated on the initial state setup and called onChange. Can you debug and let us know whether this is the case? And if so, then that means we likely have a bug in our constructors.

@heath-freenome heath-freenome added awaiting response and removed needs triage Initial label given, to be assigned correct labels and assigned labels Nov 17, 2023
@magaton
Copy link
Author

magaton commented Nov 18, 2023

No, the form does not see the change if the state is changed in the custom widget during the initial render.
I am not good with React so I can't really tell what the root cause is, but I created a project that demonstrates the problem.

The app is using the latest 5.14.2 and is not working properly. The same is with all versions since 5.13.3.

There is also a README summary about my observations and the screenshot of how it looks with version 5.13.2 (before lifecycle methods change). If you change @rjsf version to 5.13.2 you will be able to verify this

Thanks again for looking into this.

@lrozewicz
Copy link
Contributor

@magaton in both widget components, instead of modifying formData directly and then doing a shallow copy ({ ...formData }), it is recommended to create a completely new state object. This ensures that React detects the state change and re-renders the component.

In all places code snippet:

let newFormData = formContext.formData;

replace with code:

let newFormData = { ...formContext.formData };

It worked for me.

@magaton
Copy link
Author

magaton commented Nov 24, 2023

@lrozewicz Thanks very much for the reply. It took me basically a whole week to test it.
I have applied the change you advised; it does solve one problem but creates others.

There is definitely a different behavior noticed in both 5.13.2 and 5.14.2 versions of @rjsf with the:

let newFormData = { ...formContext.formData };

instead of:

let newFormData = formContext.formData;

TBH, although I understand what you are saying, the last step I am doing is that I call

formContext.setFormData({...newFormData});

which should recreate the object and make the change noticed in @rjsf library.

Some of the behaviours are quite odd, like that:

  • it works for one field, but not the other, if two fields are together in the schema.
  • the formData console log seems accurate but the condition is not reevaluated for the dependant schema when field that drives the condition gets updated. Then if I only touch the schema and app is reloaded, everything is ok...

I feel I cannot find my way through since there is not a single:

  • way how the form updates are handled in my custom widget
  • @rjsf version
    that is fully working with my schema.

Then there is a question of defaults, how they are initially populated in formData depending on:

  • version (5.13.2 vs 5.14.2)
  • the existence of my custom widgets (the order how they are invoked with regards to initial render (with the defaults)
  • the way how form is updated in my custom widgets

@heath-freenome I am now questioning whether there is something in my custom widgets that is causing this malfunction.
And I am slowly getting crazy about the whole gig since I am running in circles without proper understanding

Is there an example (sample project) that would demonstrate the following concepts I am employing in my project, probably in the wrong way

  • MyFormComonent
    where I load initially (passed) formData, register my custom widgets in the registry and implement onChange handler

  • Custom Select Widget that reads formData field(s), caclulates a field (array), populates options in the select widget, and sets the array field in the formData. In other words: 1 or more useEffects vs useMemo.

Thanks,
Milan

@heath-freenome
Copy link
Member

@magaton WOW, you are really bending the heck out of the rjsf code base. I'm curious why you aren't doing most of this work inside of onChange() handlers and passing up the local sub-formData objects to the parent so that the default processing logic of RJSF works for you rather than changing the whole of the form-data out of phase in useEffects()?

@magaton
Copy link
Author

magaton commented Dec 1, 2023

I wish I knew myself :) but, this is the 1st React code I have seen in my life :) I was always on the other side :)
Sorry, but I do not understand what you are suggesting. Would you mind explaining it a bit?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting response feature Is a feature request
Projects
None yet
Development

No branches or pull requests

3 participants