React alternative support drilling (reverse, child to parent) way to process forms
P粉419164700
P粉419164700 2023-09-01 19:45:14
0
2
452

I am new to React and learning it through some hands-on projects. I'm currently working on form processing and validation. I'm using React Router's Form component in a SPA, and inside the form I have a FormGroup element, which renders label inputs and error messages. I also use my own input component within the FormGroup component to separate the logic and state management of the inputs used in the form.

So I placed the Form component and the FormGroup component in the sample login page like this:

pages/Login.js

import { useState } from 'react'; import { Link, Form, useNavigate, useSubmit } from 'react-router-dom'; import FormGroup from '../components/UI/FormGroup'; import Button from '../components/UI/Button'; import Card from '../components/UI/Card'; import './Login.scss'; function LoginPage() { const navigate = useNavigate(); const submit = useSubmit(); const [isLoginValid, setIsLoginValid] = useState(false); const [isPasswordValid, setIsPasswordValid] = useState(false); var resetLoginInput = null; var resetPasswordInput = null; let isFormValid = false; if(isLoginValid && isPasswordValid) { isFormValid = true; } function formSubmitHandler(event) { event.preventDefault(); if(!isFormValid) { return; } resetLoginInput(); resetPasswordInput(); submit(event.currentTarget); } function loginValidityChangeHandler(isValid) { setIsLoginValid(isValid); } function passwordValidityChangeHandler(isValid) { setIsPasswordValid(isValid); } function resetLoginInputHandler(reset) { resetLoginInput = reset; } function resetPasswordInputHandler(reset) { resetPasswordInput = reset; } function switchToSignupHandler() { navigate('/signup'); } return ( 
Go Cup

Log in to your Go Cup account

{ value = value.trim(); if(!value) { return [false, 'Username or e-mail address is required.'] } else if(value.length < 3 || value.length > 30) { return [false, 'Username or e-mail address must have at least 3 and at maximum 30 characters']; } else { return [true, null]; } }, onValidityChange: loginValidityChangeHandler, onReset: resetLoginInputHandler }} /> Forgot password? } inputProps={{ type: "password", name: "password", validity: (value) => { value = value.trim(); if(!value) { return [false, 'Password is required.'] } else if(value.length < 4 || value.length > 1024) { return [false, 'Password must be at least 4 or at maximum 1024 characters long.']; } else { return [true, null]; } }, onValidityChange: passwordValidityChangeHandler, onReset: resetPasswordInputHandler }}/>
or
); } export default LoginPage;

As you can see in the code above, I use the FormGroup component and pass the onValidityChange and onReset properties to get isValid value. Changes and reset functions to reset input after form submission, etc. Use my custom hook useInput to create the isValid and reset functions in the input component. I am passing the isValid value when the value changes and passing the reset function from the input component using props defined in the FormGroup component. I'm also using isLoginValid and isPasswordValid states defiend in the login page to store the updated isValid state value passed from the child input component. So I have defined states in the input component and passed them to the parent component using props and stored their values in other states created in that parent component. The prop drilling that was going on made me feel a little uncomfortable.

State is managed inside the input component, I have these states:

  • Value: Enter the value of the element.
  • isInputTouched: Determines whether the user has touched/focused the input to determine whether to display a validation error message, if any.

I combine and apply some functions (such as the validation function passed to the input component) to these two states to create other variable values to collect information about the input and its validity, such as whether the value is valid (isValid ), whether there is message verification (message), if the input is valid (isInputValid = isValid || !isInputTouched) to decide to display the verification message.

These states and values are managed in a custom hook I created, useInput, like this:

hooks/use-state.js

import { useState, useCallback } from 'react'; function useInput(validityFn) { const [value, setValue] = useState(''); const [isInputTouched, setIsInputTouched] = useState(false); const [isValid, message] = typeof validityFn === 'function' ? validityFn(value) : [true, null]; const isInputValid = isValid || !isInputTouched; const inputChangeHandler = useCallback(event => { setValue(event.target.value); if(!isInputTouched) { setIsInputTouched(true); } }, [isInputTouched]); const inputBlurHandler = useCallback(() => { setIsInputTouched(true); }, []); const reset = useCallback(() => { setValue(''); setIsInputTouched(false); }, []); return { value, isValid, isInputValid, message, inputChangeHandler, inputBlurHandler, reset }; } export default useInput; 

I am currently using this custom hook in Input.js like this:

components/UI/Input.js

import { useEffect } from 'react'; import useInput from '../../hooks/use-input'; import './Input.scss'; function Input(props) { const { value, isValid, isInputValid, message, inputChangeHandler, inputBlurHandler, reset } = useInput(props.validity); const { onIsInputValidOrMessageChange, onValidityChange, onReset } = props; let className = 'form-control'; if(!isInputValid) { className = `${className} form-control--invalid`; } if(props.className) { className = `${className} ${props.className}`; } useEffect(() => { if(onIsInputValidOrMessageChange && typeof onIsInputValidOrMessageChange === 'function') { onIsInputValidOrMessageChange(isInputValid, message); } }, [onIsInputValidOrMessageChange, isInputValid, message]); useEffect(() => { if(onValidityChange && typeof onValidityChange === 'function') { onValidityChange(isValid); } }, [onValidityChange, isValid]); useEffect(() => { if(onReset && typeof onReset === 'function') { onReset(reset); } }, [onReset, reset]); return (  ); } export default Input; 

In the input component, I directly use the isInputValid state to add the invalid CSS class to the input. But I also pass the isInputValid, message, isValid status and reset functions to the parent component to used in it. To pass these states and functions, I use the onIsInputValidOrMessageChange, onValidityChange, onReset functions defined in props (props drilldown but direction Instead, from child to parent).

This is the definition of the FormGroup component and how I use the input state inside the FormGroup to display the validation message (if any):

components/UI/FormGroup.js

import { useState } from 'react'; import Input from './Input'; import './FormGroup.scss'; function FormGroup(props) { const [message, setMessage] = useState(null); const [isInputValid, setIsInputValid] = useState(false); let className = 'form-group'; if(props.className) { className = `form-group ${props.className}`; } let labelCmp = (  ); if(props.sideLabelElement) { labelCmp = ( 
{labelCmp} {props.sideLabelElement}
); } function isInputValidOrMessageChangeHandler(changedIsInputValid, changedMessage) { setIsInputValid(changedIsInputValid); setMessage(changedMessage); } return (
{labelCmp} {!isInputValid &&

{message}

}
); } export default FormGroup;

As you can see from the above code, I defined the message and isInputValid states to store the updated message and isInputValid code> The state passed from the input component. I have defined 2 states in the input component to hold these values, but I need to define another 2 states in this component to store the updated and passed values in the input component. This is a bit weird and doesn't seem like the best way to me.

The question is: I think I can use React Context (useContext) or React Redux to solve the prop drilling problem here. But I'm not sure if my current state management is bad and can be improved using React Context or React Redux. Because from what I understand, React Context can be terrible in situations where the state changes frequently, but if the Context is used application-wide, then this works. Here I can create a context to store and update the entire form, allowing for form-wide expansion. React Redux, on the other hand, might not be the best fit for the silo, and might be a bit overkill. What do you think? What might be a better alternative for this particular situation?

Note: Since I am new to React, I am open to all your suggestions on all my coding, from simple mistakes to general mistakes. Thanks!

P粉419164700
P粉419164700

reply all (2)
P粉627136450

There are two main schools of thought about React state management: controlled and uncontrolled. Controlled forms may be controlled using a React context where values can be accessed from anywhere to provide reactivity. However, controlled input can cause performance issues, especially when updating the entire form on each input. This is where uncontrolled forms come in. With this paradigm, all state management must leverage the browser's native capabilities for displaying state. The main problem with this approach is that you lose the React aspect of the form, you need to manually collect the form data on submission, and maintaining multiple references for this can be tedious.

Controlled input looks like this:

const [name, setName] = useState(""); return  setName(e.currentTarget.value)} />

EDIT: As @Arkellys pointed out, you don't necessarily need references to collect form data,Here's an example usingFormData

And out of control:

const name = useRef(null); const onSubmit = () => { const nameContent = name.current.value; } return 

It's obvious from these two examples that maintaining multi-component forms using either method is tedious, so libraries are often used to help you manage your forms. I personally recommendReact Hook Formas a battle-tested, well-maintained, and easy-to-use forms library. It takes an uncontrolled form for optimal performance while still allowing you to watch a single input for reactive rendering.

Regarding whether to use Redux, React context, or any other state management system, there is generally no difference in terms of performance, assuming you implement it correctly. If you likeflux architecturethen by all means use Redux, but in most cases the React context is both performant and sufficient.

YouruseInputcustom hook looks like a valiant but misguided attempt to solve problemsreact-hook-formandreact-final-formCode > Already solved. You are creating unnecessary complexity and unpredictableside effectswith this abstraction. Additionally, youmirror props a> This is often an anti-pattern in React.

If you really want to implement your own form logic (which I recommend not to do unless it's for educational purposes), you can follow these guidelines:

  1. Keep a source of truth at the highest common ancestor
  2. Avoid mirroring and copying states
  3. UseuseMemoanduseRefto re-render as little as possible
    P粉596191963

    This is a straightforward aspect that I use to decide between a publish-subscribe library like Redux and propagating state through the component tree.

    If two components have a parent-child relationship and are at most two edges away from each other, propagate the child state to the parent

    Parent -> child1-level1 -> child1-level2 ------ OK

    Parent -> child1-level1 ------ OK

    Parent -> child1-level1 -> child1-level2 -> child1-level3 --> Too many trips to change status from child1-level3 to parent

    • If the distance between interactive components is more than 2 edges, use redux
    • Use redux for sibling components, i.e. subcomponents that share a parent component and need to communicate with each other (select a tree item in the side panel and display the details of the selected item in the main component)

    Since implementation

    • I find useInput to be an over-refactoring, your input component should be enough to manage input related operations, better abstract validation etc.
    • You can trigger all validations on form submission, in which case you don't need controlled input (append validation on the form's onSubmit event)
    • However, if your form contains too many fields (say >5) and you want to validate the field before submitting, you can use the onBlur event of the input field, or use onInput with a debounce action (such as debounce from lodash operation) or implement it like this

    function debounce(func, timeout = 300){ let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, timeout); }; }
      Latest Downloads
      More>
      Web Effects
      Website Source Code
      Website Materials
      Front End Template
      About us Disclaimer Sitemap
      php.cn:Public welfare online PHP training,Help PHP learners grow quickly!