1
votes

I am refactoring a MERN app to use hooks and context, so I'm changing class components to functions. One component has a form that changes state based on the keystrokes in an input field, like:

    class CreateAccountComp extends Component {
      constructor(props) {
      super(props);

      this.state = {
        firstName: null,
        lastName: null,
        email: null,
        password: null,
        githubID: null,
    };
  }

  handleSubmit = (e) => {
    e.preventDefault();
    this.props.handleInputChange();
    API.getsync(this.state.githubID, this.state.firstName, this.state.lastName, this.state.email);
    ...
    })
  };

  handleChange = (e) => {
    e.preventDefault();
    const { name, value } = e.target;
    ...
    this.setState({ [name]: value }, () => null);
  };
  ....

This works after the callback was added to the 'this.setState' statement.

The new component looks like this:

const CreateAccountComp = (props) => {

  const [state, setState] = useState({
    firstName: null,
    lastName: null,
    email: null,
    password: null,
    githubID: null,
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log("HMMMM leaving CreateAccountcomp");
    props.handleInputChange();
    API.getsync(state.githubID, state.firstName, state.lastName, state.email);
    ...
  };

  const handleChange = (e) => {
    e.preventDefault();
    const { name, value } = e.target;
    ...
    setState({ [name]: value });
  };

Now, I can't use a callback to deal with the asynchronous setState statement, and the state properties being passed to the API.getsync() function are undefined.

My research comes up with examples of using async/await or useEffect, but they all deal with single calls to an external api. Here, I'm running handleChange to render the keystrokes into the input fields in a form, then running handleSubmit via onSubmit to execute a function to update the database based on the state values. Is there a way to update state properly in this function component?

2

2 Answers

2
votes

You can use useEffect hook and add state as a dependency to the useEffect hook so that it runs every time the state object changes

useEffect(() => {
    // code to run every time `state` object changes
    API.getsync(state.githubID, state.firstName, state.lastName, state.email);
}, [state]);

You are also not updating the state correctly. Unlike this.setState, which merges the previous state with the new one, function returned by useState hook will overwrite the previous state with the new state.

To avoid overwriting the state, make sure you merge the previous state with the new one manually

const handleChange = (e) => {
    e.preventDefault();
    const { name, value } = e.target;

    setState({ ...state, [name]: value });
};

Keep in mind that useEffect hook will also run when the component renders for the first time. After that, it will run only when the state object is changed.

Edit:

If you don' want to make the HTTP request when useEffect runs on initial render of the component, add an if statement inside the useEffect hook's callback that will make sure that HTTP request is only made once you have required data in the state object to make the HTTP request

useEffect(() => {

    if (state.githubID) {
        API.getsync(state.githubID, state.firstName, state.lastName, state.email);
    }

}, [state]);
0
votes

Yousaf got me on the right track, however, the code needed to be revised to work. This issue was that by executing 'API.getsync(state.githubID, state.firstName, state.lastName, state.email);' on initial load, the database was receiving data, which told the app that user information from github was there and the form to input the user name was never rendered. Apparently, the github api will respond with valid, but incorrect, information if the user name is undefined. The api is hit via:

axios
.get(
  "https://api.github.com/users/" + developerLoginName + "/repos?type=all"

This resulted in information on 4 repositories for user 'null' being added to the database!

This was resolved by removing the line calling the getsync function from useEffect.

However, I found that useEffect wasn't needed at all. Just adding the spread operator to the 'setState' made it work.