DEV Community

Discussion on: React - Closure that dependency!

Collapse
 
loucyx profile image
Lou Cyx

This is basically the same as providing stateful components, but with the difference that you're violating the one-way flow by setting parent state from the child. My recommendation instead of returning components from hooks is to make custom hooks that set some properties on stateless components. That way you can reuse the state provided by the custom hooks in other components that share the same properties, you can omit some properties if you want, or listen to changes and so something in the middle, and so on.

A better solution to your problem:

import React, { useCallback, useMemo, useState } from "react";

// You design `SelectComponent` as a stateless component
const SelectComponent = ({ options, optionProps, ...props }) => (
    <select {...props}>
        {options.map(({ value, label }) => (
            <option key={value} {...{ value, ...optionProps }}>
                {optionProps?.children ?? label}
            </option>
        ))}
    </select>
);

// You create a custom hook that returns some `SelectComponent` props
// (could be reused in other components with the same props).
const useInputState = initial => {
    const [value, setValue] = useState(initial);
    const onChange = useCallback(
        ({ currentTarget: { value } }) => setValue(value),
        [],
    );

    return { value, onChange };
};

// Finally you just use the `SelectComponent` combined with the hook...
const RefactoredWithClosureHookExample = () => {
    const selectProps = useInputState("two");

    return (
        <div>
            <SelectComponent
                options={[
                    { value: "one", label: "One" },
                    { value: "two", label: "Two" },
                    { value: "three", label: "Three" },
                ]}
                style={{ color: "red" }}
                optionProps={{ style: { color: "green" } }}
                {...selectProps}
            />
            <div>
                {selectProps.value
                    ? `Selected: ${selectProps.value}`
                    : "No selection"}
            </div>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

I wrote an article about this pattern if you want to take a look.

Cheers!

Collapse
 
noriller profile image
Bruno Noriller

The hook with logic is something I already saw to separate logic from JSX.
It makes the code cleaner and you have different things in different places and is certainly something that should be used more.

That said, I had times I stopped at the second refactor because the component was a "one of a kind", it was also inside something that didn't need to know about how the "select" worked internally. So this let me expose to the parent only what it needed to know while hiding the details it didn't need to know.

I wouldn't recommend it save a few occasions you find yourself having to have a lot of details from the child being controlled by the parent. (Also, it's a lot easier to do than using something like an imperative handle.)