Eli
Eli

Reputation: 857

Infinite loop when using react hooks

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

Answers (2)

Matt Carlotta
Matt Carlotta

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

Dennis Vash
Dennis Vash

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

Related Questions