There's a few red flags here, but the main one that stands out is how you're using watch(). useEffect and useState will (likely) be re-running on every render because watch() is probably returning a non-memoized object. The reason your code "works" using useState is because setting a state object to the same thing does nothing - if the username or password isn't changing, the state doesn't get updated, so your component doesn't re-render.
This isn't true with useReducer though, unless you explicitly cater for it in your reducer function - something like this will probably fix the bug:
const reducer = (state, action) => {
switch (action.type) {
case "username":
return state.username !== action.payload
? {...state, username: action.payload }
: state;
case "password":
return state.password !== action.payload
? {...state, password: action.payload }
: state;
default:
return state;
}
};
However, you should also try and make the dependencies for hooks as simple as possible. Is there any reason you couldn't call watch() first, and then pass the values to your effect?
const { username, password } = watch();
useEffect(() => {
dispatch({type : 'username', payload : username});
dispatch({type : 'password', payload : password});
}, [username, password]);
Or, you could go one better and separate the two into two different effects - there's no need to dispatch a username update if the password has changed:
const { username, password } = watch();
useEffect(() => {
dispatch({type : 'username', payload : username});
}, [username]);
useEffect(() => {
dispatch({type : 'password', payload : password});
}, [password]);
And, while we're in refactoring mode, why not create a custom hook to handle the logic for you? Something like:
const useLoginFormState = (formValues) => {
const { username, password } = formValues;
useEffect(() => {
dispatch({type : 'username', payload : username});
}, [username]);
useEffect(() => {
dispatch({type : 'password', payload : password});
}, [password]);
return formValues;
}
// Now in your component -
const MyComponent = () => {
const formValues = useLoginFormState(watch());
};