Reputation: 857
I am new to React hooks and have been experimenting with them. I am trying to write a usePostForm hook, where I can copy the post-state and merge it with the new post field but for some reason, I keep getting an infinite loop. What am I missing?
const usePostForm = field => {
const [post, setPost] = useState({
title: '',
points: '',
course: '',
question: ''
});
const setFormPost = () => {
setPost({ ...post, ...field });
};
return [post, setFormPost];
};
Later in another function, I am calling it as
const [post,setPost]=usePostForm()
setPost({title:'hello'})
Upvotes: 0
Views: 1107
Reputation: 19762
How you're invoking the setPost
is key. In the example above, every time the component rerenders it invokes the setPost
over and over.
The easiest approach would be to set values from within a form
. If you're using form elements, you can use event.target
to update the values in a useCallback
function. For example, using the useCallback
function in combination with the prevState
callback:
const usePostForm = () => {
const [values, setValues] = React.useState({
title: '',
points: '',
course: '',
question: ''
});
const setFormPost = React.useCallback(
({ target: { name, value } }) =>
setValues(prevState => ({ ...prevState, [name]: value })),
[setValues]
);
return [values, setFormPost];
};
Where name
and value
are coming from event.target
:
<input type="text" name="title" value={values.title} onChange={setFormPost} />
I found this to be the easiest way to utilize a single object state with multiple properties. Then you can either add field
in handleSubmit
(if it doen't need to be controlled) or add field
to your initial useState
call (if it needs to be applied to a field as an initial value).
For example, this initializes values via the spread syntax
within the useState
function (click the run code snippet
button below):
const usePostForm = field => {
const [values, setValues] = React.useState({
title: '',
points: '',
course: '',
question: '',
...field
});
const setFormPost = React.useCallback(
({ target: { name, value } }) =>
setValues(prevState => ({ ...prevState, [name]: value })),
[setValues]
);
return [values, setFormPost];
};
const Form = () => {
const [values, setFormPost] = usePostForm({
title: "Initial Title Value",
points: 1
});
const handleSubmit = e => {
e.preventDefault();
alert(JSON.stringify(values, null, 4));
};
return(
<form className="form" onSubmit={handleSubmit}>
<div className="input-container">
<p className="label">Title:</p>
<input
className="input"
type="text"
name="title"
placeholder=""
value={values.title}
onChange={setFormPost}
/>
</div>
<div className="input-container">
<p className="label">Points:</p>
<input
className="input"
type="number"
name="points"
placeholder=""
value={values.points}
onChange={setFormPost}
/>
</div>
<div className="input-container">
<p className="label">Course:</p>
<input
className="input"
type="text"
name="course"
placeholder=""
value={values.course}
onChange={setFormPost}
/>
</div>
<div className="input-container">
<p className="label">Question:</p>
<input
className="input"
type="text"
name="question"
placeholder=""
value={values.question}
onChange={setFormPost}
/>
</div>
<div className="btn-container">
<button className="btn primary" type="submit">Submit</button>
</div>
</form>
);
}
ReactDOM.render(
<Form />,
document.getElementById('root')
);
html {
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
font-size: 16px;
font-weight: 400;
line-height: 1.5;
-webkit-text-size-adjust: 100%;
background: #fff;
color: #666;
}
.btn {
color: #fff;
border: 1px solid transparent;
margin: 0 10px;
cursor: pointer;
text-align: center;
box-sizing: border-box;
padding: 0 30px;
vertical-align: middle;
font-size: .875rem;
line-height: 38px;
text-align: center;
text-decoration: none;
text-transform: uppercase;
transition: .1s ease-in-out;
transition-property: color,background-color,border-color;
}
.btn:focus {
outline: 0;
}
.btn-container {
text-align: center;
margin-top: 10px;
}
.form {
width: 550px;
margin: 0 auto;
}
.danger {
background-color: #f0506e;
color: #fff;
border: 1px solid transparent;
}
.danger:hover {
background-color: #ee395b;
color: #fff;
}
.error {
margin: 0;
margin-top: -20px;
padding-left: 26%;
color: red;
text-align: left;
}
.input {
display: inline-block;
height: 40px;
font-size: 16px;
width: 70%;
padding: 0 10px;
background: #fff;
color: #666;
border: 1px solid #e5e5e5;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
transition: .2s ease-in-out;
transition-property: color,background-color,border;
}
.input:focus {
outline: 0;
border: 1px solid #1e87f0;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
.input-container {
width: 100%;
height: 60px;
margin-bottom: 20px;
display: inline-block;
}
.label {
width: 25%;
padding-top: 8px;
display: inline-block;
text-align: center;
text-transform: uppercase;
font-weight: bold;
height: 34px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
background: rgb(238, 238, 238);
}
.primary {
background-color: #1e87f0;
}
.primary:hover {
background-color: #0f7ae5;
color: #fff;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id='root'>
</div>
Upvotes: 1
Reputation: 53874
Take note that you are missing an argument at setFormPost
:
const setFormPost = field => {
setPost({ ...post, ...field });
};
The infinite loop depends on where/which context you calling setFormPost
(you should elaborate more), my guess it's in useEffect
which causes a render loop.
Upvotes: 0