const { data } = useQuery problem: I want data to be object initially - javascript

I'm trying to implement a login function to my react app.
import React, { useState, useEffect } from 'react'
import { useQuery, useLazyQuery, useMutation } from "#apollo/client"
import { useForm } from "react-hook-form"
import { LOGIN } from '../queries/queries'
const Login = () => {
const [formValue, setFormValue] = useState({})
const { loading, error, data } = useQuery(LOGIN, {
variables: {
email: formValue.email,
password: formValue.password
}
})
const { register, handleSubmit } = useForm()
const onSubmit = (value) => {
setFormValue(value)
}
if (loading) return <p>loading</p>
return(
<>
<form onSubmit={handleSubmit(onSubmit)} >
<input
type="text"
name="email"
placeholder="E-mail"
ref={register}
/>
<input
type="password"
name="password"
placeholder="Password"
ref={register}
/>
<button type="submit">
Login
</button>
</form>
</>
)
}
When I code console.log(data.user) for example, error happens because user is not undefined.
I know I can get object from data if I code variables directly, but I want to get it after handleSubmit.
I think if I can make data object initially, error would not happen.
Then is there any way to do that?

try "data?.user" instead of "data.user" when referring to that object attribute
the question mark should disable the error if the object doesnt exist
update:
you can also try declaring data as an empty objec literal:
{ loading, error, data = {} }

Related

Next.js trying to push input value into json, but it doesn't work

Right there I'm trying to get value from inout, and after that change it in json.
Json file is correct, and value from it is also correct, so when I'm trying to output PcosData....Title - everything is okay
the serverside has also no problems, as in terminal i get (get & POST), and data which is in POST is also returning, that means - a) server gets input value b) backend is correct
so the problem. is in frontend, I think I'm doing something wrongg when I'm trying to push data in HandleSubmit or smth like that
import PcosData from "/data/pcos.json";
import {useEffect} from "react";
import { useState } from "react";
import bodyParser from "body-parser";
import util from "util";
const getBody = util.promisify(bodyParser.urlencoded());
export async function getServerSideProps({req, res}) {
await getBody(req, res);
console.log(req.method, req.body);
return {
props: {
// status
}
}
}
export default function adminPcos() {
const [title, setTitle] = useState(PcosData["pcos-first"].title);
const handleSubmit = (e) => {
// e.preventDefault();
PcosData["pcos-first"].title = title;
console.log(handleSubmit)
}
return (
<div>
<h1>hello world</h1>
{PcosData["pcos-first"].map((pcosdata) => (
<form onSubmit={handleSubmit} key={ pcosdata.id } method="post">
<input type="text" className="name" name="title" defaultValue={pcosdata.title} onChange={(e) => setTitle({text: e.target.value})}/>
<button type="submit">Submit</button>
</form>
))}
</div>
)
}

Axios post method - not getting UseState data

Working on Full stack app, client side is giving problems mainly using axios module. The axios post method is not posting const data in the UseState from react. The server shows entry as primary ID is created but no other data is entered, it is showing as null.
This is the client side for Add Book Form:
import React from 'react'
import { useState } from 'react'
import axios from 'axios'
//import { useNavigate } from 'react-router-dom'
const Add = () => {
const [bookData, setBook] = useState({
title: '',
author: '',
details: '',
price: null,
cover: '',
userId: 1
})
const [error, setError] = useState(false)
//const navigate = useNavigate()
const dataEntry = (event) => {
setBook((previous) => ({...previous, [event.target.name]: event.target.value }))
}
const eventClick = async (event) => {
event.preventDefault()
try {
const result = await axios.post('/books', bookData) //error occurs here
console.log(result)
//navigate('/login')
} catch (error) {
setError(error.response.data)
}
}
return (
<div className='form'>
<h1>Add New Book</h1>
<form>
<input required type={'text'} placeholder={'title'} name={'title'} onChange={dataEntry} />
<input required type={'text'} placeholder={'author'} name={'author'} onChange={dataEntry} />
<input required type={'text'} placeholder={'details'} name={'details'} onChange={dataEntry} />
<input required type={'number'} placeholder={'price'} name={'price'} onChange={dataEntry} />
<input required type={'text'} placeholder={'cover'} name={'cover'} onChange={dataEntry} />
<button onClick={eventClick}>ADD</button>{error && "Something went wrong!"}
</form>
</div>
)
}
export default Add
I tried changing the Sequelize model structure and still getting no data entered. I've ensured my client side folder in the package.json has the 'proxy': 'http://localhost:5000/api/' route set up and still it's giving errors

How to use useForm to validate input while using localStorage

I'm trying to validate a simple form with a single input just for practice. I also don't want the value that the user types in the input to disappear after page refresh, for that reason, I did a little bit of searching and found out about saving that data using localStorage. After trying to implement that for a while, I managed to do that, when I refresh the page, the value is still there. However, now, when I'm trying to validate the form using useForm from react-hook-form, It just doesn't work for some reason, when I try to use that same useForm logic with an input without using localStorage, It works just fine, but while trying to add localStorage functionality, then it doesn't. I hope I'm describing my problem at least okey, here's the code :
import React, {useEffect, useState } from "react";
import "./App.css"
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if(!storedValues) return {
name: "",
age: ""
}
return JSON.parse(storedValues);
}
function Home() {
const [values, setValues] = useState(getForm)
const {register, handleSubmit, watch} = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value,
}))
}
const onSubmit = async data => { console.log(data); };
useEffect(()=>{
localStorage.setItem("form", JSON.stringify(values))
}, [values])
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input value={values.name} onChange={handleChange} name="name" placeholder="name" />
<input value={values.age} onChange={handleChange} name="age" placeholder="age"/>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default Home;
This code works fine since I'm not adding useForm register to the input, but if I do that, then It gets buggy, like this :
<input value={values.name} onChange={handleChange} name="name" placeholder="name" {...register("name")} />
The latest code only works If I remove the value atrribute from the input, but I can't do that, If I do, I can't use localStorage anymore.
Looking at the documentation, you had the syntax a little off with your register function. That function takes a second argument, which is an object of props, and that is where you want to define value, name and onChange.
Like this:
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
Here is the full code I have working on a codesandbox. That's really all I changed, expect removing the watch import.
import React, { useEffect, useState } from "react";
import "./styles.css";
import { useForm } from "react-hook-form";
const getForm = () => {
const storedValues = localStorage.getItem("form");
if (!storedValues)
return {
name: "",
age: ""
};
return JSON.parse(storedValues);
};
function Home() {
const [values, setValues] = useState(getForm);
const { register, handleSubmit } = useForm();
const handleChange = (e) => {
setValues((previousValues) => ({
...previousValues,
[e.target.name]: e.target.value
}));
};
const onSubmit = async (data) => {
console.log(data);
};
useEffect(() => {
localStorage.setItem("form", JSON.stringify(values));
}, [values]);
return (
<div className="container">
<form onSubmit={handleSubmit(onSubmit)}>
<input
placeholder="name"
{...register("name", {
onChange: handleChange,
name: "name",
value: values.name
})}
/>
<input
value={values.age}
onChange={handleChange}
name="age"
placeholder="age"
/>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default Home;

Why can't I have updated states in function component (using hook)?

I have login function component with two inputs. It's controlled component so email and password are bound to state(user). Input is a component I use instead of input tag(refactoring input). I can change state user(email and password) with input values using handleInputChange event handler and I also can submit form using handleSubmit handler.
Everything was good until I tried to validate form using yup and catching errors. I declared errors state to save errors I got. I want to catch errors and show in "div className="alert"" and I want to post user to server when no error exists. I see the errors related to yup in validate() function, but when I change errors state(setErrors([...er.errors])) I find errors state empty (console.log(errors.length)).
Here is login component:
import axios from "axios";
import queryString from "query-string"
import { useEffect, useRef,useState } from "react";
import React from "react"
import {useLocation, useRouteMatch,useParams} from "react-router-dom"
import Input from "./input";
import * as yup from 'yup';
const Login = () => {
useEffect(async()=>{
console.log(errors)
},[errors])
var [user,setUser]=useState({email:'',password:''});
var [errors,setErrors]=useState([])
let schema=yup.object().shape({
email:yup.string().email("ایمیل نامعتبر است").required("فیلد ایمیل الزامیست"),
password:yup.string().min(8,"رمز عبور باید حداقل 8 رقم باشد")
})
const validate=async()=>{
try {
const resultValidate=await schema.validate(user, { abortEarly: false })
}
catch(er){
console.log(er.errors)
setErrors([...er.errors])
}
}
const handleSubmit= async(e)=>{
e.preventDefault();
await validate();
console.log(errors.length)
if(errors.length===0){
alert("X")
const response= await axios.post("https://reqres.in/api/login",user)
console.log(response)
}
}
const handleInputChange=async(e)=>{
setUser({...user,[e.currentTarget.name]:e.currentTarget.value})
}
return (
<>
<div id="login-box" className="col-md-12">
{errors.length!==0 && (<div className="alert">
<ul>
{errors.map((element,item)=>{
return(
<>
<li key={item}>
{element}
</li>
</>
)
})}
</ul>
</div>) }
<form onSubmit={handleSubmit} id="login-form" className="form" action="" method="post">
<h3 className="text-center text-info">Login</h3>
<Input onChange={handleInputChange} name="email" id="email" label="نام کاربری" value={user.email}/>
<Input name="password" onChange={handleInputChange} id="password" value={user.password} label="رمز عبور"/>
{/* <div id="register-link" className="text-right">
Register here
</div> */}
<input type="submit" className="btn btn-primary" value="ثبت"/>
</form>
</div>
</>
);
}
export default Login;
and here is Input component:
import {Component} from "react"
class Input extends Component {
render() {
return <>
<div className="form-group">
<label htmlFor="username" className="text-info">{this.props.label}</label><br/>
<input type="text" onChange={this.props.onChange} name={this.props.name} id={this.props.id} className="form-control" value={this.props.value} />
</div>
</>;
}
}
export default Input;
I understood that setStates(in my component setErrors) are asynchronous and it's delayed. I tried using simple array variable (named errors) instead of state and hook, but guess what, it didn't rerender page when I changed the errors variable! Of course I can't see errors in page using this way.
I tried to resolve this using useEffect() and I decided to check validation errors and post in useEffect instead of handleSubmit handler:
useEffect(async()=>{
if(errors.length===0){
const response= await axios.post("https://reqres.in/api/login",user)
console.log(response)
}
console.log(errors)
}, [errors])
Now I see errors when inputs are invalid. When I type valid values, there are still same errors!
It looks like I can't have updated errors state and I just get previous errors even after I enter valid values! I try to not use class based component as I can. What shall I do?
You can return true if the input values are validated and false if not, from the validate function like this:
const validate = async () => {
try {
const resultValidate = await schema.validate(user, { abortEarly: false });
return true;
} catch (er) {
console.log(er.errors);
setErrors([...er.errors]);
return false;
}
};
And now in the handleSubmit function you have to modify a bit:
const handleSubmit = async (e) => {
e.preventDefault();
const isValid = await validate();
console.log(errors.length);
if (isValid) {
alert("X");
const response= await axios.post("https://reqres.in/api/login",user)
console.log(response)
setErrors([]); //so that the previous errors are removed
}
};
Issue
The issue you face is that React state updates are asynchronously processed. This doesn't mean that the state update is async and can be waited for. The errors state you enqueue won't be available until the next render cycle.
const validate = async () => {
try {
const resultValidate = await schema.validate(user, { abortEarly: false });
} catch(er) {
console.log(er.errors);
setErrors([...er.errors]); // (2) <-- state update enqueued
}
}
const handleSubmit = async (e) => {
e.preventDefault();
await validate(); // (1) <-- validate called and awaited
console.log(errors.length); // <-- (3) errors state from current render cycle
if (errors.length === 0) {
alert("X");
const response = await axios.post("https://reqres.in/api/login", user);
console.log(response);
}
}
Solution
I suggest returning an "errors" object from validate instead, you can enqueue any state updates later if you like.
const validate = async () => {
const errors = [];
try {
await schema.validate(user, { abortEarly: false });
} catch(er) {
console.log(er.errors);
errors.push(...er.errors);
}
return errors;
}
const handleSubmit = async (e) => {
e.preventDefault();
const errors = await validate();
console.log(errors.length);
if (!errors.length) {
alert("X");
const response = await axios.post("https://reqres.in/api/login", user);
console.log(response);
} else {
setErrors(prevErrors => [...prevErrors, ...errors]);
}
}

The state is not updated via the useState hook the first time

The state is updated only on the next keystroke but with the previous state. Screen 1
When you click on updateForm (), it is also empty, only after the second click, the state is updated. Screen 2
I understand that this is due to asynchrony, but in this case I do not know how to use it.
Home.jsx
import React, { useState } from 'react';
import { Form } from '../components/Form/Form';
const Home = () => {
const [dateForm, setDataForm] = useState({});
const updateForm = eachEnry => {
setDataForm(eachEnry);
console.log(dateForm);
};
return (
<div>
<Form updateForm={updateForm} />
</div>
);
};
export default Home;
Form.jsx
import React, { useState } from 'react';
import './Form.scss';
export const Form = ({ updateForm }) => {
const initInputState = {
name: '',
password: ''
};
const [dataForm, setDataForm] = useState(initInputState);
const { name, password } = dataForm;
const onChange = e => {
setDataForm({
...dataForm,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
updateForm(dataForm);
};
return (
<div>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Name"
value={name}
onChange={onChange}
name="name"
/>
<input
placeholder="Password"
onChange={onChange}
value={password}
type="text"
name="password"
/>
<button type="submit">Login</button>
</form>
</div>
);
};
Your code is working fine. You just doing console.log before the state is updated. State updates happen not when you using an update state function. It's happening when all component action and nested components actions are done.
Check your code with console log on another place click to check
As you can see I placed a console log on every Home component rerender. You can check that all works fine.
P.S. I did some improvements to your code. Check if u like it. And add a comment to updateForm function. Check this one too, please.
You evidently are not setting your state properly, here
setDataForm({
...dataForm,
[e.target.name]: e.target.value
});
should be
setDataForm(c => ({
...c,
[e.target.name]: e.target.value
}));

Categories

Resources