简体   繁体   中英

Should I use separate useState for each property

I have written following code:

function CreateModal({toggleCreatePorjectModal, fetchProjects}) {
    const [formObj, setFormObj] = useState({name: "", link: "", error: ""})


    function validateLinkAndCreateProject() {
        if(formObj.link.trim() === "" || formObj.name.trim() === "") {
            setFormObj(state => ({...state, error: "ALL fields are mandatory, please enter Project Name and Feed Link to create project."}))
            return
        }
        
        rssFeedParser(formObj.link.trim())
            .then((res) => {
                axios.post(`${ServerPath}/projects/create`, {
                    name: formObj.name, 
                    link: formObj.link
                })
                .then(response => {
                    if(response.data.error) {
                        setFormObj(state => ({...state, error: "Something went wrong. Please try again after some time."}))
                    } else {
                        fetchProjects()
                        toggleCreatePorjectModal(false)
                    }  
                })
                .catch((err) => {
                    setFormObj(state => ({...state, error: err.msg}))
                })
            })
            .catch((err) => {
                setFormObj(state => ({...state, error: err.msg})) 
            })
    }
    return  (
        <div >
            {/*Will Render Something based on above code*/}
        </div>
    )
}

I have used only one useState to store properties like "name", "link", "error" in one object. As I wanted to keep logic of FormObj and validateLinkAndCreateProject together. All three properties use only one useEffect. Hence, I thought that it is better to keep all three properties inside useState instead of creating 3 different usestate.

But my manager, and my technical architect telling me to create 3 useState, one useState for each property,that is, separate useState for name, link and error. According to them, never create object inside useState like useState({name: "", link: "", error: ""}). I should always create separate useState for each property.

As per my understanding, in older react we used to have only one state which use to have 30 to 40 properties in one object. With introduction of React hooks, as per "separation of concern", we are suppose to separate related logic from rest of the code. Hence, we will end up 5 to 6 states having objects with 4 to 5 properties.

Note: Here "separation of concerns" means breaking class code in such way that dependent state + useEffect + useRef etc kept together. below is quote from React documentation.

Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.

I wanted to Am I missing anything??, Is it okay to create useState({name: "", link: "", error: ""}). or should I create separate state for each one of them?

It's a judgement call, but in most cases, yes, use separate useState calls for each of those three pieces of state information (or use useReducer instead). That way, setting them is simple and straightfoward. Eg:

const [name, setName] = useState("");
const onNameChange = e => setName(e.target.value);
<input
    type="text"
    onChange={onNameChange}
>

Class-based components get a setState function that accepts partial state updates and merges them into the component's state, but that isn't how useState 's setter works; it completely replaces the state item.¹ That means if you're using a compound state item as in your question, you have to include all of its parts in every call to the setter — which sets you up to accidentally use stale copies of the parts you're not intentionally updating:

const [formObj, setFormObj] = useState({name: "", link: "", error: ""});
const onNameChange = ({target: {value}}) => setFormObj({...formObj, name: value}); // <== USUALLY WRONG

The problem there is that the data in formObj can be out of date. You'd have to use the callback form instead:

const [formObj, setFormObj] = useState({name: "", link: "", error: ""});
const onNameChange = ({target: {value}}) => setFormObj(formObj => ({...formObj, name: value}));

The other issue is that if you're using any of those pieces of state as dependencies in a useEffect / useMemo / useCallback /etc., it's easy to get that wrong too.

So yes, your manager and tech architect are likely correct, use individual useState calls (or useReducer ). But, again, it's a judgement call; you could just always use the callback form of useFormObj .


Another option:

You could create a hook for the form object with individual setters for the name, link, and error that accept either a string or an event, like this:

// A reusable utility function: if the argument is an object with a `target` property,
// return `x.target.value`; otherwise, just return `x`.
function unwrapValue(x) {
    if (typeof x === "object" && x.target) {
        x = x.target.value;
    }
    return x;
}

// Off-the-cuff, untested, just a sketch
function useFormObj(initialFormObj = {name: "", link: "", error: ""}) {
    const [formObj, setFormObj] = useState(initialFormObj);
    const setters = useRef(null);
    if (!setters.current) {
        // Only true on first call
        setters.current = {
            setName(name) {
                name = unwrapValue(name);
                setFormObj(formObj => ({...formObj, name}));
            },
            setLink(link) {
                link = unwrapValue(link);
                setFormObj(formObj => ({...formObj, link}));
            },
            setError(error) {
                error = unwrapValue(error);
                setFormObj(formObj => ({...formObj, error}));
            },
        };
    }
    return [formObj, setters.current];
}

And then:

const [formObj, {setName, setLink, setError}] = useFormObj();
<input
    type="text"
    onChange={setName}
>

That's handy when you need to use form objects in more than one component, or you just want to have smaller more easily-testable pieces.


¹ From the documentation (you have to scroll down slightly to the Note: ):

Note

Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:

 setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });

Another option is useReducer , which is more suited for managing state objects that contain multiple sub-values.

It is true that useState replaced the state completely, but you can create your own custom hooks which you can try to update the state using spread operator, Eg {...obj, ...values} So this way you don't have to update the whole object, spread operator will take care.

I think you can go for custom hooks and manage this.

I realize this is an old question, but if anyone else is trying to figure this out, I'd like to say I've built a whole production app this way. Every page (it was a next.js app) used one useState and in it was an objected with many properties. Error, form data, loading, etc.

If you read the react docs they recommend against it because of possible performance issues due to the fact that an object is re-evaluated every time. Even with that, the app runs quickly. I think we overcomplicate things way too often. A reducer would have been better, sure. But the speed you can develop with not going into abstract complexities by keeping things simple has a lot less bugs and performance issues than people seem to think. Remember, the React team are smart. They're not going to set you up for failure. If it really hindered performance in a great way and it was a basic and fundamental thing like useState is, they'd go out of their way to prevent you from doing such things.

By the way this production app services hundreds of active users every week for ordering food and we've had no complaints and heard only good things.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM