Home > other >  React state does not update on time, asynchronous issue
React state does not update on time, asynchronous issue

Time:11-14

I've build a web application using React that has user registration. The user data:

{ 
  username: 'string', 
  password: 'string'
}

exists in a postgresql database and as state. I'm trying to handle a case where a user is trying to register with a previously used username.

Here's a screenshot of my ui component and react dev tools :

enter image description hereHere's a summary of how I'm handling the case where a username (i'll use the username 'bob' as seen of the screenshot) has already been used:

  1. // Login.js

    The 'Register' <input> in the <Login/> component (which contains all the .jsx for the ui form seen on the screen) invokes the userRegister() function via an onClick event. It receives this function as a prop from my <App/> (App.js) component, then the following happens...

  2. // App.js

    1 - userRegister() is invoked

    2- it invokes 2 functions that check whether the password and username are valid

    3- if they are, it invokes a function to check if the username has already been used by checking if it exists as a record in the postgresql database

    4- it does this by sending an axios request with the current username password object as params to the '/users' endpoint, written out in my server.js file

      axios({
          method: 'get',
          url: '/users',
          params: { username: 'string', password: 'string' 
        })
    
  3. // server.js

    1- the request is received by an express route handler at the '/users' endpoint

    2- inside the route handler, it invokes an imported getUser() function from postgres.js that communicates directly with the database

    const { getData, getUser, addUser } = require('./db/postgres.js');                           
    app.get('/users', (req, res) => {
      getUser(req.query)
    });
    
  4. // postgres.js

    1- the getUser() function sends the following query to the postgres database

    const getUser = (user) => {
      return client.query(`select * from users where username='${user.username}'`)
    };
    
    
  5. after sending that query

    this object should reach the client

    response.rows: [
      { id: null, username: 'bob', password: 'Pi2@' },
      { id: null, username: 'bob', password: '8*lA' },
      { id: null, username: 'bob', password: '8*lA' },
      { id: null, username: 'bob', password: '7&lL' },
      { id: null, username: 'bob', password: '*lL0' },
      { id: null, username: 'bob', password: 'mM0)' },
      { id: null, username: 'bob', password: '8*fF' },
      { id: null, username: 'bob', password: '7&yY' },
      { id: null, username: 'bob', password: '55$rR' },
      { id: null, username: 'bob', password: '7&yY' },
      { id: null, username: 'bob', password: '8*LAAa' },
      { id: null, username: 'bob', password: '8*lAa' },
      { id: null, username: 'bob', password: '8*lA' }
    ]
    

    which proves that the username does, in fact already exist in the database

    enter image description here

  6. which should, in turn, update the alreadyRegistered state to true and invoke the alert 'user already exists', but, as demonstrated in the top screenshot, alreadyRegistered doesn't update in time

    it DOES show the update in Rect devtools a few ms laterenter image description here

I'm fairly certain that the issue is due to state hooks and lifecycle methods being asynchronous, but with that being said how do I update the alreadyRegisted state to true before the code on line 63 executes? enter image description here

Here are snippets of the code files working together. Let me know if more information/context is needed

  • Login.js
import React from 'react';
import { Link } from 'react-router-dom';
import { NavBar, Footer } from'../Imports.js';


const Login = ({ userAuth, getUserAuth, userLogin, authorized, userRegister }) => {
  console.log('userAuth:', userAuth);
  return (
    <div id='login'>
      <NavBar userAuth={userAuth} getUserAuth={getUserAuth} authorized={authorized} />
      <section id='login-main'>
        <form>
          <input type='text' placeholder='username' onChange={() => {getUserAuth({...userAuth, username: event.target.value})}}/>
          <input type='text' placeholder='password'onChange={() => {getUserAuth({...userAuth, password: event.target.value})}}/>
          <input type='submit' value='Login' onClick={() => {event.preventDefault(); userLogin()}}/>
          <input type='submit' value='Register' onClick={() => {event.preventDefault(); userRegister();}}/>
        </form>
      </section>
      <Footer/>
    </div>
  )
};

export default Login;
  • App.js
import React, { useState, useEffect } from 'react';
import Router from './Router.js';
const axios = require('axios');

const App = () => {

  // Login/user state
  const [userAuth, getUserAuth] = useState({username: '', password: ''});
  const [authorized, authorizeUser] = useState(false);
  const [alreadyRegistered, userExists] = useState(false);

  // new user register
  const userRegister = () => { // <- 1)
    if (validUsername() && validPassword()) {
      checkIfExists();
      console.log('alreadyRegistered:', alreadyRegistered);
      if (alreadyRegistered) {
        alert('user already exists');
      } else {
        console.log('ERROR: should update alreadyRegistered state to TRUE and invoke ALERT')
      }
    }
  }

// check if username is already registered
  const checkIfExists = () => { // <- 3)
    axios({
      method: 'get',
      url: '/users',
      params: userAuth
    })
    .then((res) => {
      console.log(res.data.length)
      if (res.data.length) {
        console.log(res.data.length, 'INNER')
        userExists(true);
      }
    })
    .catch((err) => {
      throw err;
    })
  }
}; 
  • server.js
// ----- external modules -----
require('dotenv').config();
const express = require('express');
const path = require('path');
const { getData, getUser, addUser } = require('./db/postgres.js');

// check if user exists
app.get('/users', (req, res) => {
  console.log('req.body:', req.query)
  getUser(req.query)
  .then((response) => {
    console.log('response.rows:', response.rows);
    res.send(response.rows);
  })
  .catch((err) => {
    if (err) {
      throw err;
    }
  });
});
  • postgres.js
const { Client } = require('pg');

const getUser = (user) => {
  return client.query(`select * from users where username='${user.username}'`)
};

CodePudding user response:

Before answering the state question, it's important to know your application as written is vulnerable to SQL injection. Instead of a template string in your postgres.js file, you should use parameterized queries.

As for the value of alreadyRegistered, you're correct that the issue lies in the asynchronous nature of state hooks.

You can use the useEffect function to handle updates to alreadyRegistered like so:

useEffect(() => {
    if (alreadyRegistered) {
        alert('user already exists');
    } else {
        // create user...
    }
}, [alreadyRegistered]);

You could also avoid tracking alreadyRegistered in state and just call checkIfExists as needed:

// first, remove alreadyRegistered state
const [userAuth, getUserAuth] = useState({username: '', password: ''});
const [authorized, authorizeUser] = useState(false);

// then redefine checkIfExists to return Promise<boolean>
const checkIfExists = async () => {
    const res = await axios({
      method: 'get',
      url: '/users',
      params: userAuth
    })

    return !!res.data.length; // convert fetch result to boolean
}

// then call checkIfExists in userRegister
const userRegister = async () => {
    if (validUsername() && validPassword()) {
      const alreadyRegistered = await checkIfExists();
      if (alreadyRegistered) {
        alert('user already exists');
      } else {
        // handle user creation
      }
    }
  }
  • Related