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 :
Here'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:
// Login.js
The 'Register'
<input>
in the<Login/>
component (which contains all the .jsx for the ui form seen on the screen) invokes theuserRegister(
) function via anonClick event
. It receives this function as a prop from my<App/>
(App.js) component, then the following happens...// App.js
1 -
userRegister()
is invoked2- 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' })
// 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 databaseconst { getData, getUser, addUser } = require('./db/postgres.js'); app.get('/users', (req, res) => { getUser(req.query) });
// postgres.js
1- the
getUser()
function sends the following query to the postgres databaseconst getUser = (user) => { return client.query(`select * from users where username='${user.username}'`) };
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
which should, in turn, update the
alreadyRegistered state
totrue
and invoke the alert 'user already exists', but, as demonstrated in the top screenshot,alreadyRegistered
doesn't update in time
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?
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
}
}
}