Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pw:laboratoare:06 [2023/03/12 14:31]
ciprian.dobre [Redux Toolkit]
pw:laboratoare:06 [2023/05/08 09:55] (current)
ciprian.dobre [Scopul laboratorului]
Line 4: Line 4:
 ===== Scopul laboratorului ===== ===== Scopul laboratorului =====
  
-In acest laborator vom intra in detaliu legat de gestionarea starii aplicatiei folosind ​de **hook**-uri si **Redux Toolkit**. Motivul pentru care este nevoie de gestiunea starii aplicatiei este ca anumite componente in diferite locatii ale aplicatiei au nevoie sa imparta aceleasi date. O posibilitate pentru a propaga datele la mai multe componente este ca o compenta parinte sa trimita la descendentii sai datele prin proprietati din copil in copil, insa aceasta abordare poate aglomera componentele si duce la cod greu de gestionat. Alternativa cea mai buna este ca datele partajate de diferite componente sa fie puse la dispozitie printr-o stare globala accesibila prin functii speciale numite hooks. De asemnea, vom prezenta in acest laborator si modalitati de a crea formulare pentru a executa mutatii pe backend si cum puteti gestiona starea formularului cu validarea datelor introduse.+In acest laborator vom intra in detaliu legat de gestionarea starii aplicatiei folosind **hook**-uri si **Redux Toolkit**. Motivul pentru care este nevoie de gestiunea starii aplicatiei este ca anumite componente in diferite locatii ale aplicatiei au nevoie sa imparta aceleasi date. O posibilitate pentru a propaga datele la mai multe componente este ca o compenta parinte sa trimita la descendentii sai datele prin proprietati din copil in copil, insa aceasta abordare poate aglomera componentele si duce la cod greu de gestionat. Alternativa cea mai buna este ca datele partajate de diferite componente sa fie puse la dispozitie printr-o stare globala accesibila prin functii speciale numite hooks. De asemnea, vom prezenta in acest laborator si modalitati de a crea formulare pentru a executa mutatii pe backend si cum puteti gestiona starea formularului cu validarea datelor introduse.
  
 ===== Gestionarea starii ===== ===== Gestionarea starii =====
Line 224: Line 224:
 ===== Formulare ===== ===== Formulare =====
  
 +De multe ori o sa aveti nevoie de a crea formulare pentru a adauga/​schimba date pe backend. In HTML aveti multe elemente care functioneaza ca input-uri pentru formulare, ex. select, textbox, checkbox etc. Pentru React bibliotecile de UI cum este si Material UI expun aceste input-uri deja stilizate si cu atribute care va pot ajuta pentru a controla componentele respective.
 +
 +Problemele in definirea formularelor este atat controla componentelor din formular cat si validarea formularului,​ ne dorim ca formular sa valideze datele pana sa fie trimis catre backend cat si sa se schimbe la diferite actiuni ale utilizatorului cum ar fi pentru afisarea erorilor sau pentru ascunderea unur campuri. Pentru controlarea componentelor vom folosi **[[https://​react-hook-form.com/​|react-hook-form]]** iar pentru validarea formularului [[https://​www.npmjs.com/​package/​yup|Yup]]:​
 +
 +''​npm install react-hook-form yup @hookform/​resolvers''​
 +
 +Folosind **useForm** putem obtine starea si actiuni de modificare pe starea unui formular dandu-se o anumita schema pentru formular iar cu yup putem definii o schema de validare pentru starea formularului care printr-un **resolver** va impiedica formularul sa fie submis daca nu se respecta schema de validare si va returna mesaje de eroare. In exemplul de mai jos aveti definirea unui formular simplu unde se defineste schema de validare si campurile formularului intr-un controller.
 +
 +<​code>​
 +/**
 + * Use a function to return the default values of the form and the validation schema.
 + * You can add other values as the default, for example when populating the form with data to update an entity in the backend.
 + */
 +const getDefaultValues = (initialData?:​ { email: string }) => {
 +    const defaultValues = {
 +        email: "",​
 +        password: ""​
 +    };
 +
 +    if (!isUndefined(initialData)) {
 +        return {
 +            ...defaultValues,​
 +            ...initialData,​
 +        };
 +    }
 +
 +    return defaultValues;​
 +};
 +
 +/**
 + * Create a hook to get the validation schema.
 + */
 +const useInitLoginForm = () => {
 +    const { formatMessage } = useIntl();
 +    const defaultValues = getDefaultValues();​
 +
 +    const schema = yup.object().shape({ // Use yup to build the validation schema of the form.
 +        email: yup.string() // This field should be a string.
 +            .required(formatMessage( // Use formatMessage to get the translated error message.
 +                { id: "​globals.validations.requiredField"​ },
 +                {
 +                    fieldName: formatMessage({ // Format the message with other translated strings.
 +                        id: "​globals.email",​
 +                    }),
 +                })) // The field is required and needs a error message when it is empty.
 +            .email() // This requires the field to have a email format.
 +            .default(defaultValues.email),​ // Add a default value for the field.
 +        password: yup.string()
 +            .required(formatMessage(
 +                { id: "​globals.validations.requiredField"​ },
 +                {
 +                    fieldName: formatMessage({
 +                        id: "​globals.password",​
 +                    }),
 +                }))
 +            .default(defaultValues.password),​
 +    });
 +
 +    const resolver = yupResolver(schema);​ // Get the resolver.
 +
 +    return { defaultValues,​ resolver }; // Return the default values and the resolver.
 +}
 +
 +/**
 + * Create a controller hook for the form and return any data that is necessary for the form.
 + */
 +export const useLoginFormController = (): LoginFormController => {
 +    const { formatMessage } = useIntl();
 +    const { defaultValues,​ resolver } = useInitLoginForm();​
 +    const { redirectToHome } = useAppRouter();​
 +    const { loginMutation:​ { mutation, key: mutationKey } } = useLoginApi();​
 +    const { mutateAsync:​ login, status } = useMutation([mutationKey],​ mutation);
 +    const queryClient = useQueryClient();​
 +    const dispatch = useDispatch();​
 +    const submit = useCallback((data:​ LoginFormModel) => // Create a submit callback to send the form data to the backend.
 +        login(data).then((result) => {
 +            dispatch(setToken(result.response?​.token ?? ''​));​
 +            toast(formatMessage({ id: "​notifications.messages.authenticationSuccess"​ }));
 +            redirectToHome();​
 +        }), [login, queryClient,​ redirectToHome,​ dispatch]);
 +
 +    const {
 +        register,
 +        handleSubmit,​
 +        formState: { errors }
 +    } = useForm<​LoginFormModel>​({ // Use the useForm hook to get callbacks and variables to work with the form.
 +        defaultValues,​ // Initialize the form with the default values.
 +        resolver // Add the validation resolver.
 +    });
 +
 +    return {
 +        actions: { // Return any callbacks needed to interact with the form.
 +            handleSubmit,​ // Add the form submit handle.
 +            submit, // Add the submit handle that needs to be passed to the submit handle.
 +            register // Add the variable register to bind the form fields in the UI with the form variables.
 +        },
 +        computed: {
 +            defaultValues,​
 +            isSubmitting:​ status === "​loading"​ // Return if the form is still submitting or nit.
 +        },
 +        state: {
 +            errors // Return what errors have occurred when validating the form input.
 +        }
 +    }
 +}
 +</​code>​
 +
 +Pentru componenta de UI trebuie folosit formularul in interiorul unui element de tip **form** unde se specifica ca atribut functia de submitere. In interiorul formularului se leaga variabilele la input-urile de form si ca sa se submita trebuie sa existe un buton de tip "​submit"​ care va apela functia specificata in form cu datele din formular asa cum aveti ca exemplu mai jos.
 +
 +<​code>​
 +export const LoginForm = () => {
 +    const { formatMessage } = useIntl();
 +    const { state, actions, computed } = useLoginFormController();​ // Use the controller.
 +
 +    return <form onSubmit={actions.handleSubmit(actions.submit)}>​ {/* Wrap your form into a form tag and use the handle submit callback to validate the form and call the data submission. */}
 +        <Stack spacing={4} style={{ width: "​100%"​ }}>
 +            <​ContentCard title={formatMessage({ id: "​globals.login"​ })}>
 +                <Grid container item direction="​row"​ xs={12} columnSpacing={4}>​
 +                    <Grid container item direction="​column"​ xs={12} md={12}>
 +                        <​FormControl ​
 +                            fullWidth
 +                            error={!isUndefined(state.errors.email)}
 +                        > {/* Wrap the input into a form control and use the errors to show the input invalid if needed. */}
 +                            <​FormLabel required>​
 +                                <​FormattedMessage id="​globals.email"​ />
 +                            </​FormLabel>​ {/* Add a form label to indicate what the input means. */}
 +                            <​OutlinedInput
 +                                {...actions.register("​email"​)} // Bind the form variable to the UI input.
 +                                placeholder={formatMessage(
 +                                    { id: "​globals.placeholders.textInput"​ },
 +                                    {
 +                                        fieldName: formatMessage({
 +                                            id: "​globals.email",​
 +                                        }),
 +                                    })}
 +                                autoComplete="​username"​
 +                            /> {/* Add a input like a textbox shown here. */}
 +                            <​FormHelperText
 +                                hidden={isUndefined(state.errors.email)}
 +                            >
 +                                {state.errors.email?​.message}
 +                            </​FormHelperText>​ {/* Add a helper text that is shown then the input has a invalid value. */}
 +                        </​FormControl>​
 +                    </​Grid>​
 +                    <Grid container item direction="​column"​ xs={12} md={12}>
 +                        <​FormControl
 +                            fullWidth
 +                            error={!isUndefined(state.errors.password)}
 +                        >
 +                            <​FormLabel required>​
 +                                <​FormattedMessage id="​globals.password"​ />
 +                            </​FormLabel>​
 +                            <​OutlinedInput
 +                                type="​password"​
 +                                {...actions.register("​password"​)}
 +                                placeholder={formatMessage(
 +                                    { id: "​globals.placeholders.textInput"​ },
 +                                    {
 +                                        fieldName: formatMessage({
 +                                            id: "​globals.password",​
 +                                        }),
 +                                    })}
 +                                autoComplete="​current-password"​
 +                            />
 +                            <​FormHelperText
 +                                hidden={isUndefined(state.errors.password)}
 +                            >
 +                                {state.errors.password?​.message}
 +                            </​FormHelperText>​
 +                        </​FormControl>​
 +                    </​Grid>​
 +                </​Grid>​
 +            </​ContentCard>​
 +            <Grid container item direction="​row"​ xs={12} className="​padding-top-sm">​
 +                <Grid container item direction="​column"​ xs={12} md={7}></​Grid>​
 +                <Grid container item direction="​column"​ xs={5}>
 +                    <Button type="​submit"​ disabled={!isEmpty(state.errors) || computed.isSubmitting}>​ {/* Add a button with type submit to call the submission callback if the button is a descended of the form element. */}
 +                        {!computed.isSubmitting && <​FormattedMessage id="​globals.submit"​ />}
 +                        {computed.isSubmitting && <​CircularProgress />}
 +                    </​Button>​
 +                </​Grid>​
 +            </​Grid>​
 +        </​Stack>​
 +    </​form>​
 +};
 +</​code>​
 +
 +===== Resurse utile =====
 +
 +  * Aplicatia [[https://​gitlab.com/​mobylabwebprogramming/​reactfrontend|demo]] a noastra
 +  * [[https://​redux-toolkit.js.org/​|Redux Toolkit]]
 +  * [[https://​www.npmjs.com/​package/​yup|Yup]]
 +  * [[https://​react-hook-form.com/​|react-use-form]]
pw/laboratoare/06.1678624272.txt.gz ยท Last modified: 2023/03/12 14:31 by ciprian.dobre
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0