I’m using a custom React hook for forms. The high level overview:
const component = () => {
// load subscription
// set state variable once data has loaded from server-publish
// pass this init state into a custom hook:
const {inputs, handleInputChange, handleSubmit} = useForm(signup, initialState);
// return form
return (<form><inputs .../></form>)
}
The issue I’m having is, the useState gets initialized with {} because the server hasn’t returned data by the time this hook is called, and therefore the form always starts out with no data even in the case where their’s data for the user.
It does in fact add the state from the server to the form as desired, but constantly reruns, even when it seems initalState is has not changed.
How can we init values to a hook when we don’t have the data from the server?
or more generically…
How does one set the initial state of a useStatehook only the first time said initial state is not null (in my case, when the Meteor Publication has time to send the data to the client)?
Also, React.memo() is for turning any component into the equivalent of React.PureComponent, whether it’s a class component or a function component - only re-render if props have changed meaningfully.
If you wrap it in React.memo() then it will only render when the props change (after you call setInputs in this case).
function useForm(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
Basically, this function is shouldComponentUpdate, except you return true if you want it to not render.
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(useForm, areEqual);
useEffect only runs when the component is fully rendered.
Again:
Using setState inside useEffect will create an infinite loop that most likely you don’t want to cause.
useEffect is called after each render and when setState is used inside of it, it will cause the component to re-render which will call useEffect and so on and so on.
The only case that using useState inside of useEffect will not cause an infinite loop is when you pass an empty array as a second argument to useEffect like useEffect(() => {....}, []) which means that the effect function should be called once: after the first mount/render only.
But that’s the thing, I passed in initialState (which is the state of the form, that is sent to the client via a Publication), and even when it was the same useEffect still reran:
// the publication returns and populates the intialState values
This structure didn’t work. But also the sturucture you laid out doesn’t work because I need to return values from the useForm and your examples don’t provide for that. Also, OK React.memo keeps the function running once and does a difference check, but useEffect is suppose to do that as well – and doesn’t for some reason based on the initalState.
For the comparison of props you can use a deep compare like this: https://github.com/FormidableLabs/react-fast-compare.
" How does one set the initial state of a useStatehook only the first time" . Hooks are stateless. So I think the question is more like what should you present when you have no data (yet)? Input placeholders? A spinner? A loading bar. However, you have props so maybe you can initialize with a props object (from Redux if you use or from a parent component) and then switch props.
// ...
let initialState = {};
let stateSet = false; // <= set the state to false, the useEffect will run as many times as needed
if (loading) {
console.log(`inside Signup loading...`)
} else {
const person = Person.findOne({ userId: userId }) || '';
if (person) {
initialState.firstName = person.firstName || '';
initialState.lastName = person.lastName || '';
initialState.email = person.email || '';
initialState.password = person.password || '';
stateSet = true; // <= important, after the subscription is filled, I set the state
}
}
// ...
// pass in the new boolean here
const {inputs, handleInputChange, handleSubmit} = useForm(signup, initialState, stateSet);
// I now pass in the setState like so
const useForm = (callback, initialState, stateSet, validate) => {
const [inputs, setInputs] = useState(initialState);
// ...
// turn "OFF" useEffect with the boolean
useEffect(() => {
if (initialState) {
setInputs(() => ({...initialState}));
}
}, [stateSet]);
// ...
Again, I don’t like this, @paulishca I’ll look at deep compare of initalState instead of the boolean approach now. Could it be because initialState was not stored somewhere in the function?