Carlos Escobar
Carlos Escobar

Reputation: 434

Using useRef in inside a function handler with hooks

I'm trying to submit a form that sends a http request onSubmit, but I'm getting undefined when setting the state on the values. I'm not sure why the values are not being passed upon click and being set with the set...() function.

Below is the code of that component. Upon first submit action I get an error because the "surveyUserAnswers" are undefined, but in the next submissions it works. Not sure why? Could someone advise.

I'm very new to typescript and react hooks, so excuse my code! thanks

import React, { useRef, useState } from "react";

import Loader from "../UI/loader/loader";

import axios from "axios";
import "./surveybox.css";

interface surveryAnswer {
  id: number;
  answers: string[];
}

const SurveyBox: React.FC = () => {
  const [surveyUserAnswers, setSurveyUserAnswers] = useState<surveryAnswer>();
  const [loading, setLoading] = useState(false);
  const programmingQRef = useRef<HTMLSelectElement>(null);
  const skillsQRef = useRef<HTMLSelectElement>(null);
  const stateManagementQRef = useRef<HTMLSelectElement>(null);
  const programmerTypeQRef = useRef<HTMLSelectElement>(null);

  const onSubmitSurvey = (e: React.FormEvent): void => {
    e.preventDefault();
    setLoading((prevLoading) => !prevLoading);
    setSurveyUserAnswers({
      id: Math.random(),
      answers: [
        programmerTypeQRef.current!.value,
        skillsQRef.current!.value,
        stateManagementQRef.current!.value,
        programmerTypeQRef.current!.value,
      ],
    });

    axios
      .post(`${DB_URL}/users-answers.json`, surveyUserAnswers)
      .then((res) => {
        setLoading((prevLoading) => !prevLoading);
      })
      .catch((error) => {
        console.log(error);
        setLoading((prevLoading) => !prevLoading);
      });
  };

  return (
    <div className="surveybox-container">
      {loading ? (
        <div className={"loader-holder"}>
          <Loader />
        </div>
      ) : (
        <React.Fragment>
          <h2>Quick survey!</h2>
          <form action="submit" onSubmit={onSubmitSurvey}>
            <label>favorite programming framework?</label>
            <select ref={programmingQRef} name="programming">
              <option value="React">React</option>
              <option value="Vue">Vue</option>
              <option value="Angular">Angular</option>
              <option value="None of the above">None of the above</option>
            </select>
            <br></br>
            <label>what a junior developer should have?</label>
            <select ref={skillsQRef} name="skills">
              <option value="Eagerness to lear">Eagerness to learn</option>
              <option value="CS Degree">CS Degree</option>
              <option value="Commercial experience">
                Commercial experience
              </option>
              <option value="Portfolio">Portfolio</option>
            </select>
            <br></br>
            <label>Redux or Context Api?</label>
            <select ref={stateManagementQRef} name="state-management">
              <option value="Redux">Redux</option>
              <option value="Context Api">Context Api</option>
            </select>
            <br></br>
            <label>Backend, Frontend, Mobile?</label>
            <select ref={programmerTypeQRef} name="profession">
              <option value="Back-end">back-end</option>
              <option value="Front-end">front-end</option>
              <option value="mobile">mobile</option>
            </select>
            <br></br>
            <button type="submit">submit</button>
          </form>
        </React.Fragment>
      )}
    </div>
  );
};

export default SurveyBox;

Upvotes: 0

Views: 93

Answers (1)

Ori Drori
Ori Drori

Reputation: 193250

Setting the state is an async action, and the updated state would only be available at the next render.

In your case, the default state is undefined, and this is what you send at the 1st submit. The state is now updated, and when you submit again, you send the previous answer, and so on...

To solve this, prepare a const (newAnswer), and set it to the state, and use it in the api call.

Note: in your case, you're not using the surveyUserAnswers at all, so you can remove this state entirely.

const onSubmitSurvey = (e: React.FormEvent): void => {
  e.preventDefault();
  setLoading((prevLoading) => !prevLoading);

  const newAnswer = {
    id: Math.random(),
    answers: [
      programmerTypeQRef.current!.value,
      skillsQRef.current!.value,
      stateManagementQRef.current!.value,
      programmerTypeQRef.current!.value,
    ],
  }

  setSurveyUserAnswers(newAnswer);

  axios
    .post(`${DB_URL}/users-answers.json`, newAnswer)
    .then((res) => {
      setLoading((prevLoading) => !prevLoading);
    })
    .catch((error) => {
      console.log(error);
      setLoading((prevLoading) => !prevLoading);
    });
};

Upvotes: 3

Related Questions