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

[React] Use updater function in setData in useForm hook #1859

Merged
merged 1 commit into from
Jun 21, 2024

Conversation

rrmesquita
Copy link
Contributor

This is a proposal for changing the ergonomics of the key-value variant for the setData hook in React.

Current behaviour

Using this simple form as an example:

const form = useForm({
    name: 'Alice',
    lastname: 'Doe',
})

It is expected that sequential calls to setData will update the form data accordingly. However, subsequent calls overwrite the changes made by the previous calls.

form.setData('name', 'Bob') // { name: 'Bob', lastname: 'Doe'}
form.setData('lastname', 'Smith') // { name: 'Alice', lastname: 'Smith'}

Desired behaviour

Sequential setData calls use an updater function to batch the changes from multiple calls.

form.setData('name', 'Bob') // { name: 'Bob', lastname: 'Doe'}
form.setData('lastname', 'Smith') // { name: 'Bob', lastname: 'Smith'}

This can be achieved using updater functions for the set functions in React. From React documentation:

Is using an updater always preferred?

In most cases, there is no difference between these two approaches.

However, if you do multiple updates within the same event, updaters can be helpful.

If you prefer consistency over slightly more verbose syntax, it’s reasonable to always write an updater if the state you’re setting is calculated from the previous state.

It is worth noting that this only affects the key-value variant of the setData function, and if anyone is impacted by this change, they can switch to the object variant of the setData function.

@rrmesquita rrmesquita changed the title Use set function for key-value data setter in useForm react hook Use updater function for key-value data setter in useForm react hook Apr 18, 2024
@rrmesquita rrmesquita changed the title Use updater function for key-value data setter in useForm react hook [React] Use updater function for key-value data setter in useForm hook Apr 18, 2024
@rrmesquita
Copy link
Contributor Author

Update: I just noticed that the current behaviour is very unexpected because it overrides previous changes not only from key-value calls but all setData calls. Personally, I consider this a bug.

Copy link
Contributor

@derrickreimer derrickreimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one looks safe & good to me!

@reinink reinink changed the title [React] Use updater function for key-value data setter in useForm hook [React] Use updater function in useForm hook Jun 21, 2024
@reinink reinink changed the title [React] Use updater function in useForm hook [React] Use updater function in setData in useForm hook Jun 21, 2024
@reinink reinink merged commit bfdb56e into inertiajs:master Jun 21, 2024
4 checks passed
@reinink
Copy link
Member

reinink commented Jun 21, 2024

@rrmesquita Hey thanks for catching this! 🙏

And thanks for the review @derrickreimer and @thecrypticace 🤙

@FrozenGod
Copy link

FrozenGod commented Jul 4, 2024

Just upgraded to 1.2.0 with hope that it'll be fixed but it seems to still be bugged.

@derrickreimer
Copy link
Contributor

This patch is not released yet, but will go out in the next version.

@anoshiri
Copy link

Is there a smooth workaround for this?

@mezg0
Copy link

mezg0 commented Jul 13, 2024

Is there a smooth workaround for this?

You can use a updater functions instead for now:

setData(prev => ({
 ...prev,
 name: 'Bob',
 lastname: 'Smith'
}))

@rrmesquita
Copy link
Contributor Author

You can extend the hook locally:

// file: hooks/useForm.ts

import { useForm as useInertiaForm } from '@inertiajs/react'

export default function useForm<T extends Record<string, unknown>>(initialValues = {} as T) {
    const form = useInertiaForm(initialValues)

    return {
        ...form,
        setData(keyOrData: keyof T | ((data: T) => T) | T, value?: T[keyof T]) {
            if (typeof keyOrData === 'string') {
                form.setData((data) => ({ ...data, [keyOrData]: value }))
            } else if (typeof keyOrData === 'function') {
                form.setData((data) => keyOrData(data))
            } else {
                form.setData(keyOrData)
            }
        },
        // Other useful helpers
    }
}

@anoshiri
Copy link

Thanks for this. But I didn't want to extend in order not to cause a conflict with the patch when it is released. So what I have done is:

nst newData = data;
newData [field1] = value1;
newData [field2] = value2;

setData(data, newData );

@matteogilioli
Copy link

It doesn't seem to me that the problem is solved in 1.3.0, am I wrong?

@derrickreimer
Copy link
Contributor

@matteogilioli it should be! Might sharing some relevant code where you're seeing something not behaving as expected?

@matteogilioli
Copy link

@matteogilioli it should be! Might sharing some relevant code where you're seeing something not behaving as expected?

Sorry, my fault. Now it seems to have resolved, I have no idea what changed in the meantime, maybe some caching problem.

@IvanCaceres
Copy link

I can confirm this is fixed in 1.3.0-beta.2, I just tested it.
Consecutive setData calls will update the individual fields and retain the existing state 🙂. Thanks!

@vitalijalbu
Copy link

You can extend the hook locally:

// file: hooks/useForm.ts

import { useForm as useInertiaForm } from '@inertiajs/react'

export default function useForm<T extends Record<string, unknown>>(initialValues = {} as T) {
    const form = useInertiaForm(initialValues)

    return {
        ...form,
        setData(keyOrData: keyof T | ((data: T) => T) | T, value?: T[keyof T]) {
            if (typeof keyOrData === 'string') {
                form.setData((data) => ({ ...data, [keyOrData]: value }))
            } else if (typeof keyOrData === 'function') {
                form.setData((data) => keyOrData(data))
            } else {
                form.setData(keyOrData)
            }
        },
        // Other useful helpers
    }
}

hi, how can i use that? just import it from a hook file instead of inertia ?

@rrmesquita
Copy link
Contributor Author

hi, how can i use that? just import it from a hook file instead of inertia ?

@vitalijalbu yes, like so:

import useForm from '../resources/lib/hooks'

@vitali-bc
Copy link

I tried to upgrade to this version but cannot make it happen:
(provider is null when sending):

    const { page, processing } = props;
    const {
        post,
        setData,
        data,
    } = useForm({
        provider: null,
    });

    const handleDownload = (slug = null) => {
        if (!slug) {
            message.error("Provider slug is required.");
            return;
        }

        setData({ provider: slug });

        console.log("🚀 clicked-name:", data);

        post(`/products/download`, {
            onSuccess: (response) => {
                console.log("Response:", response);
                message.success(`Catalogo di ${slug} scaricato con successo!`);
                setModal(false); // Close the modal on success
            },
            onError: (error) => {
                console.error("Error:", error);
                message.error(
                    error?.message || "Errore durante il download del catalogo."
                );
            },
        });
    };

@rrmesquita
Copy link
Contributor Author

@vitali-bc that is a different problem. useForm hook uses useState internally and those updates are asynchronous, meaning that you can't call setData and immediately submit the form, because the data state was not yet updated. I still face that same problem though, and I still don't have a nice workaround for it.

@vitalijalbu
Copy link

@vitali-bc that is a different problem. useForm hook uses useState internally and those updates are asynchronous, meaning that you can't call setData and immediately submit the form, because the data state was not yet updated. I still face that same problem though, and I still don't have a nice workaround for it.

I dont' have a form but only a onClick button, in this situation how can i update form data?
Of course I can use axios, but want to see how inertia does.
Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.