React Hooks stale state inside function - reactjs

I have something like this
const [state, setState] = useState({
num: 0
});
const validateBiggerThan0 = () => {
return state.num > 0;
};
const [validation, setValidation] = useState({
val: validateBiggerThan0
});
The problem is when I call validation.validateBiggerThan0, this function does not have the current state.
In this simple example, I could pass the value sate.num as a parameter to the validateBiggerThan0, but in my real application this is not feasible because I have multiple validations and some can use more than one value from the state. Is there any alternative to this?
A sandbox with the problem:
https://codesandbox.io/s/heuristic-mountain-2poz7?fontsize=14&hidenavigation=1&theme=dark

The solution to your problem is to not use useState for the function object.
const validation = {
val: validateBiggerThan0
};
Or to update the object on every change of your state:
useEffect(() => {
setValidation({
val: validateBiggerThan0
});
}, [ state, setValidation, validateBiggerThan0 ]);
// Don't remove "state" from the dependency array.
Don't forget to wrap your validation functions in a useCallback hook in case you use the useEffect method.
const validateBiggerThan0 = useCallback(() => {
return state.num > 0;
}, [state]);

Related

Invalid custom hook call

I'm just a react beginner. I'm trying to create a custom hook, which will be triggered once an onClick event is triggered. By what I see, I need to use the useRef hook, to take into account if the component is rendered by first time, or if it's being re-rendered.
My code approach is the next:
const Clear = (value) => {
const useClearHook = () => {
const stateRef = useRef(value.value.state);
console.log(stateRef);
useEffect(() => {
console.log("useEffect: ");
stateRef.current = value.value.state;
stateRef.current.result = [""];
stateRef.current.secondNumber = [""];
stateRef.current.mathOp = "";
console.log(stateRef.current);
value.value.setState({
...stateRef.current,
result: value.value.state.result,
secondNumber: value.value.state.secondNumber,
mathOp: value.value.state.mathOp,
});
}, [stateRef.current]);
console.log(value.value.state);
};
return <button onClick={useClearHook}>Clear</button>;
};
Any suggestion? Maybe I might not call ...stateRef.current in setState. I'm not sure about my mistake.
Any help will be appreciated.
Thanks!
Your problem is useClearHook is not a component (the component always goes with the first capitalized letter like UseClearHook), so that's why when you call useRef in a non-component, it will throw that error. Similarly, for useEffect, you need to put it under a proper component.
The way you're using state is also not correct, you need to call useState instead
Here is a possible fix for you
const Clear = (value) => {
const [clearState, setClearState] = useState()
const useClearHook = () => {
setClearState((prevState) => ({
...prevState,
result: [""],
secondNumber: [""],
mathOp: "",
}));
};
return <button onClick={useClearHook}>Clear</button>;
};
If your states on the upper component (outside of Clear). You can try this way too
const Clear = ({value, setValue}) => {
const useClearHook = () => {
setValue((prevState) => ({
...prevState,
result: [""],
secondNumber: [""],
mathOp: "",
}));
};
return <button onClick={useClearHook}>Clear</button>;
};
Here is how we pass it
<Clear value={value} setValue={setValue} />
The declaration for setValue and value can be like this in the upper component
const [value, setValue] = useState()

How to correctly implement a props callback function in the useEffect hook

I want to use useEffect() to detect changes to a state value and pass that value to a parent component using a callback function received as a prop. I can't figure out a way to do this without disabling the eslint missing dependency warning. I have this problem on both the child component and that child's child.
Here is the parent implementation:
const updateValues = (newValues) => {
setValues({ ...values, ...newValues });
};
<GeneralUpdates onUpdate={updateValues} />
Here is the first child (GeneralUpdates):
const [values, setValues] = useState({
name: '',
description: '',
});
// This handles form input changes
const handleChange = (prop) => (event) => {
setValues({ ...values, [prop]: event.target.value });
};
useEffect(() => {
onUpdate(values);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [values]);
<FilesUpload handleChange={onUpdate}/>
And this is the child's child (FilesUpload):
const [featuredPhotos, setFeaturedPhotos] = useState([]);
useEffect(() => {
handleChange({ featuredPhotos });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [featuredPhotos]);
Adding handleChange as a dependency results in an infinite re-rendering loop. I've tried every solution I can find but must be missing something here.
A colleague helped me find a solution to this—passing a named function to useEffect rather than the anonymous one:
const updateCallback = () => {
onUpdate(values);
};
useEffect(updateCallback, [values]);
There isn't any problem with disabling eslint for this particular rule if you know 100% what you are doing.
However you can still get around this without disabling eslint by using useCallback hook for handleChange function being passed from parent if parent is a functional component
// in parent
const handleChange = useCallback(({featuredPhotos}) => {
// Do what you want to do here
}, []);
If parent is not a functional component, make sure you aren't using arrow function in render while passing it as props and you should be ok
handleChange = ({featuredPhotos}) => {
...
}
render() {
return (
<Child handleChange={this.handleChange} />
)
}
EDIT:
for your case, you can update the implementation by using useCallback for updateValue function and inside it use functional setState
const updateValues = useCallback((newValues) => {
setValues(prevValues => ({ ...prevValues, ...newValues }));
}, []);
I'm adding one more solution which keeps also eslint happy:
const callback = useRef();
callback.current = onUpdate;
useEffect(() => {
callback.current(values);
}, [values, callback]);

React: custom hook doesn't work with context

I created a custom hook to store Objects in a useState hook and allow changing properties without loosing the other entries.
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
setState({
...state,
...newState
});
}
];
};
This hook works in my component but doesn't when I assign it to my context.
Here is what I did:
I created a context:
export const navigation = createContext();
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/store.js:40-83
I created a useObject variable and assigned it as value to my Context Provider
<navigation.Provider value={useObject()}>
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/Layout.js:234-284
I load the context via useContext and change its value
const [navigationState, setNavigationState] = useContext(navigation);
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js:476-616
Result:
The context always stores the new entry and removes all existing entries.
Anyone knows why ?
Here is the Sandbox link. You can test it by clicking the filter button. I expected to see {search:true, icon: 'times'} as context value. Thx!
https://codesandbox.io/s/keen-glitter-3nob7?file=/src/App.js
There is one important things to note here. useEffect in App.js is run once and hence the onClick function set with setNavigationState will use the values from its closure at the point at which it is defined i.e initial render.
Due to this, when you call the function within Header.js from context's the value along with the localState are being reset to the initial value.
SOLUTION 1:
One solution here is to use callback approach to state update. For that you need to modify your implementation on useObject a bit to provide the use the capability to use the callback value from setState
const useObject = initialValue => {
const [state, setState] = useState(initialValue);
return [
state,
newState => {
if(typeof newState === 'function') {
setState((prev) => ({ ...prev, ...newState(prev)}));
} else {
setState({
...state,
...newState
});
}
}
];
};
and then use it in onContextClick function like
const onContextClick = () => {
setState(prevState => {
setNavigationState(prev => ({ icon: ICON[prevState.isOpen ? 0 : 1] }));
return { isOpen: !prevState.isOpen };
});
};
Working DEMO
SOLUTION 2:
The other simpler approach to solving the problem is to use useCallback for onContextClick and update the navigation state with useEffect, everytime the closure state is updated like
const onContextClick = React.useCallback(() => {
setNavigationState({ icon: ICON[state.isOpen ? 0 : 1] });
setState({ isOpen: !state.isOpen });
}, [state]);
useEffect(() => {
setNavigationState({
search: true,
icon: ICON[0],
onClick: onContextClick
});
}, [onContextClick]);
Working demo

Creating objects from states

I have two states:
const [firstState, setFirstState] = useState(6.5)
const [secondState, setSecondState] = useState(3.5)
I wish to combine these two states into an object, like this:
const [myObject, setMyObject] = useState({
first: {firstState},
second: {secondState}
});
//NOTE: This does not compile when i try to create state-object with state values.
I then send the object to a child-component as a prop which is later used for display.
<ChildComponent objectToChild={myObject}/>
What's the best pratice to combine states into an object?
Their are different option
1. Using the useState Hook
const [myObject, setMyObject] = useState({
first: firstState,
second: secondState
});
modify state
setMyObject({...myObject, firstState })
2. Using the useReducer
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
const initialState = {
first: 'name',
second: 'surname'
};
const reducer = (state, action) => {
switch (action) {
case 'first': return { ...state, first: state.first };;
case 'second': return { ...state, second: state.second };;
default: throw new Error('Unexpected action');
}
};
use it like
const [state, dispatch] = useReducer(reducer, initialState);
You can simply create an object as an argument in useState method, so It would be like this:
const [user, setUser] = useState({});
and call setUser method like this:
setUser(prevState => {
return {...prevState, [user.firstState]: 6.5}
}
Read more about useState hook here:
https://pl.reactjs.org/docs/hooks-reference.html#usestate

How to use static variables with react hooks

With const [open, setOpen] = useState(false) I can create a variable open which is persisted over calls of a functional component.
But which hook can I use if I do not want a rerender when setting a variable?
I have a custom hook draft:
const useVariable = (initialValue) => {
const ref = useRef();
return useMemo(() => {
ref.current = [initialValue, (newValue) => { ref.current[0] = newValue }]
}, [])
}
But according to https://reactjs.org/docs/hooks-reference.html#usememo I can not rely that useMemo is not called anytime again.
You can make use of useRef hook if you just want to store the some data in a variable and not re-render when the variable is set
const unsubCallback = useRef(null);
if(!unsubCallback) {
unsubCallback.current = subscribe(userId)
}
Thank to #shubham-khatri I found a solution to my question. Just use the initialValue of the useRef hook:
const useVariable = initialValue => {
const ref = useRef([
initialValue,
param => {
ref.current[0] = typeof param === "function"
? param(ref.current[0])
: param
}
]);
return ref.current;
};
https://codesandbox.io/s/v3zlk1m90
Edit: To account for Christopher Camp's comment I added that also a function can be passed like in useState. See usage in codesandbox

Resources