Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to define handlers in functional React components?

As far as I know, there are three ways to define functions in JavaScript.

1. Declaration

function handleEvent(e) {}

2. Assignment

var handleEvent = function(e) {}

3. Arrow

var handleEvent = (e) => {}

I have been searching for hours trying to find information on which of these options is the preferred way to declare handlers in functional React components. All the articles I found talk about class components, binding, etc. But with the new Hooks out, there must be standard for defining them inside the function as well. (After all, functional components have always existed anyway.)

Consider the following component, which declares three handlers, which are examples of different behaviour that you might need in a React component.

function NameForm(props) {
    const [inputName, setInputName] = useState("");

    useEffect(() => setInputName(props.initialValue), [props.initialValue]);

    const handleInputChange = function(event) {
        setInputName(event.target.value);
    };

    const resetForm = function() {
        setInputName(props.initialValue);
        props.onReset();
    };

    const handleFormSubmit = function(event) {
        event.preventDefault();
        props.onSubmit(inputName);
        resetForm();
    };

    /* React-Bootstrap simple form example using these handlers. */
    return (
        <Form onSubmit={handleFormSubmit}>
            <Form.Group controlId="NameForm">
                <Form.Control
                    type="text"
                    placeholder="Enter your name here"
                    value={inputName}
                    onChange={handleInputChange}
                />
                <Button type="submit">Submit</Button>
                <Button onClick={resetForm}>Reset</Button>
            </Form.Group>
        </Form>
    );
}

All of these handlers are directly passed as callbacks into other components. They might be called whenever, but at that exact moment, we need to have access to the current values of props and any state like inputName. Additionally, as you might have noticed, the handleFormSubmit handler also calls the resetForm handler.

What would be the recommended approach to defining the handlers from a performance perspective? Can it be avoidable that they are redefined on every render?

Does useCallback also fit in here somewhere?

like image 417
Snackoverflow Avatar asked Aug 03 '19 09:08

Snackoverflow


2 Answers

The current standard is to declare the handlers as constants for immutability and as arrow-functions for binding purposes.

function NameForm(props) {
    const [inputName, setInputName] = useState("");

    useEffect(() => setInputName(props.initialValue), [props.initialValue]);

    const handleInputChange = (event) => {
        setInputName(event.target.value);
    }

    const resetForm = () => {
        setInputName(props.initialValue);
        props.onReset();
    }

    const handleFormSubmit = (event) => {
        event.preventDefault();
        props.onSubmit(inputName);
        resetForm();
    }

    /* React-Bootstrap simple form example using these handlers. */
    return (
        <Form onSubmit={handleFormSubmit}>
            <Form.Group controlId="NameForm">
                <Form.Control
                    type="text"
                    placeholder="Enter your name here"
                    value={inputName}
                    onChange={handleInputChange}
                />
                <Button type="submit">Submit</Button>
                <Button onClick={resetForm}>Reset</Button>
            </Form.Group>
        </Form>
    );
}

All of these handlers are directly passed as callbacks into other components. They might be called whenever, but at that exact moment, we need to have access to the current values of props and any state like inputName

As currently constructed we meet the requirements for your description. Since props and state are defined as higher level data that the component has access to, all the event-handlers have access to them. And when used as a call-back in another component, they will remain bound to the initial component where they were defined in.

Which means if you have an event-handler like this:

const handleInputChange = (e) => {
    setValue(e.target.value)
}

And you pass it into a ChildComponent like this:

<Child handleInputChange={handleInputChange}/>

And the Child uses the prop/event-handler like this:

<input onChange={props.handleInputChange}/>

You would be getting the event from the Child input, but you would be updating the state of the Parent, the original component that defined the event-handler.

like image 94
Chris Ngo Avatar answered Sep 28 '22 02:09

Chris Ngo


There is virtually no difference between the "declaration", "assignment" and "arrow" approaches. The only thing that matters is that you don't always create new instances of the handler functions on each render. For this, use the useCallback hook to memoize the function references:

const handleInputChange = useCallback((event) => {
  setInputName(event.target.value);
}, []); // `setInputName` is guaranteed to be unique, from the React Hooks docs, so no need to pass it as a dependency

const resetForm = useCallback(() => {
  setInputName(props.initialValue);
  props.onReset();
}, [props.onReset, props.initialValue]; // these come from props, so we don't know if they're unique => need to be passed as dependencies

const handleFormSubmit = useCallback((event) => {
    event.preventDefault();
    props.onSubmit(inputName);
    resetForm();
}, [props.onSubmit, resetForm, inputName]); // `resetForm` and `inputName`, although defined locally, will change between renders, so we also need to pass them as dependencies

useCallback docs: https://reactjs.org/docs/hooks-reference.html#usecallback

like image 45
Andrei Duca Avatar answered Sep 28 '22 00:09

Andrei Duca