behavior not expected but working, I need some explanation please - reactjs

I am on the CRUD step with hooks and everything is working but I don't understand why the Axios.post doesn't need .then in this case.
If I send only customer instead customer[0] nothing happens, then the .then(response => console.log(response)) returns nothing. I guess that the customer[0] has already the right format: [{}].
import React, { useState, useEffect } from 'react';
import Axios from 'axios';
import { Form, Container, Col, Row, Button } from 'react-bootstrap';
// import data
import fields from './customerFields'; // <= array of object
function AddCustomers() {
const [customer, setCustomer] = useState([{}]);
const [inputValue, setInputValue] = useState('');
useEffect(() => {
setCustomer([inputValue]);
}, [inputValue]);
const handleSubmit = (event) => {
event.preventDefault();
const newCustomer = [...customer, inputValue];
setCustomer(newCustomer);
const url = 'http://localhost:5000/api/clients';
Axios.post(url, customer[0])
};
const handleChange = (event) => {
event.preventDefault();
const { value } = event.target;
const { name } = event.target;
const newValue = { ...inputValue, [name]: value };
setInputValue(newValue);
};
// return
return (
<Container>
<Row>
<Col className="col-form-label-sm">
<h3 id="">Identité du client</h3>
<Form
action=""
className="form-group"
onSubmit={(event) => handleSubmit(event)}
>
<Form.Group>
<Form.Label>{fields[0].label}</Form.Label>
<Form.Control
name={fields[0].name}
type="text"
value={inputValue.name}
onChange={(event) => handleChange(event)}
/>
</Form.Group>
<Form.Group>
<Form.Label>{fields[1].label}</Form.Label>
<Form.Control
name={fields[1].name}
type="text"
value={inputValue.name}
onChange={(event) => handleChange(event)}
/>
</Form.Group>
<Form.Group>
<Form.Label>{fields[2].label}</Form.Label>
<Form.Control
name={fields[2].name}
type="text"
value={inputValue.name}
onChange={(event) => handleChange(event)}
/>
</Form.Group>
<Button type="submit" variant="warning">
Ajouter un client
</Button>
</Form>
</Col>
</Row>
</Container>
);
}
export default AddCustomers;

You're making an asynchronous operation (Axios.post(url, customer[0])) and not waiting for it to resolve, so you're just leaving the promise floating there. If you don't care if the operation succeed or failed that is fine, but in most cases you want to either handle the error received or do something with the result.
About why Axios.post is accepting customer[0] that's because it accepts anything in the second parameter it can send in a POST request. You're always setting your customer as an array with the inputValue variable inside (setCustomer([inputValue]);) so inputValue is what you send as customer[0].

ohhh! I got it, I think this version is better :
const handleSubmit = (event) => {
event.preventDefault();
const url = 'http://localhost:5000/api/clients';
Axios.post(url, customer);
};
thank you #jonrsharpe

Related

Onchange in input field is not working while editing a form

I am developing a small application in react, in which I have an edit option. On clicking the edit button, it will load the existing data and allows the user to edit any of the fields and submit.
Fetching the data and loading it in a form are working fine, but when I edit a textbox, the value changes to the existing fetched value, and it is not allowing me to hold the edited value.
Please note, the problem is with editing the input in a form not in submitting. Below is the edit component that I am using.
mport { useState, useEffect } from 'react';
import { json, Link } from 'react-router-dom';
import { useParams } from 'react-router-dom';
const EditTask = ({ onEdit }) => {
const [text, setText] = useState('');
const [day, setDay] = useState('');
const [reminder, setReminder] = useState(false);
const params = useParams();
useEffect(() => {
fetchTask();
});
const fetchTask = async () => {
const res = await fetch(`http://localhost:5000/tasks/${params.id}`);
const data = await res.json();
setText(data.text);
setDay(data.day);
setReminder(data.reminder);
};
const onSubmit = async (e) => {
e.preventdefault();
if (!text) {
alert('Please enter task name');
return;
}
onEdit({ text, day, reminder });
setText('');
setDay('');
setReminder(false);
};
const handleChange = ({ target }) => {
console.log(target.value); // displaying the input value
setText(target.value); // changes to existing value not the one I entered
};
return (
<form className="add-form" onSubmit={onSubmit}>
<div className="form-control">
<label>Task</label>
<input
id="AddTask"
type="text"
placeholder="Add Task"
value={text}
onChange={handleChange}
/>
</div>
<div className="form-control">
<label>Date & Time</label>
<input
id="Date"
type="text"
placeholder="Date & Time"
value={day}
onChange={(e) => setDay(e.target.value)}
/>
</div>
<div className="form-control form-control-check">
<label>Set Reminder</label>
<input
id="Reminder"
type="checkbox"
checked={reminder}
value={reminder}
onChange={(e) => setReminder(e.currentTarget.checked)}
/>
</div>
<input className="btn btn-block" type="submit" value="Save Task" />
<Link to="/">Home</Link>
</form>
);
};
export default EditTask;
Can someone explain what I am missing here? Happy to share other information if needed.
Expecting the input fields to get the value entered and submitting.
You missed adding dependency to useEffect
Yours
useEffect(() => {
fetchTask()
}
)
Should be changed
useEffect(()=>{
fetchTask()
}, [])
becasue of this, fetchTask is occured when view is re-rendered.

Keep current state of data of clicked element in form inputs when updating in React?

I am trying to update some data in my React component. The update does work, but to improve the UI/UX, I want the current state of the data that will updated to be in the form inputs before I update.
Currently, when clicking an element to update, the form inputs are blank and you have to completely re-type all form inputs to update.
Instead, I need all form inputs to consist of the current state of the data and not be blank.
Component with useState hooks, axios.patch method, and input forms
import React, { useState, useEffect } from 'react';
import { getCookie } from "../../../utils/cookies";
import axios from "axios";
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import Modal from 'react-bootstrap/Modal';
import { PencilSquare } from 'react-bootstrap-icons';
export default function UpdateService({ fetchData, id }) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const [name, setName] = useState("");
const [desc, setDesc] = useState("");
const isEnabled = name.length > 0 && desc.length > 0;
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const updateService = async (id: number) => {
try {
const JWT = await getCookie("auth");
const { data } = await axios(
`/api/serviceType/${id}`, {
method: "PATCH",
data: {
name: name,
description: desc,
},
headers: {
"Content-Type": "application/json",
Authorization: JWT,
},
});
setData(data);
setIsLoading(false);
// Hides Modal on submission
setShow(false);
} catch (e) {
setIsLoading(false);
alert("error updating data");
}
};
// Table state update on submit
useEffect(() => {
fetchData(data);
}, [data]);
return (
<>
<PencilSquare onClick={handleShow}/>
<Modal show={show} backdrop="static" onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Update Service</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={() => updateService(id)}>
<Form.Group className="mb-3" controlId="exampleForm.ControlInput1">
<Form.Label>Service Name</Form.Label>
<Form.Control
type="text"
value={name}
placeholder="Service Name"
autoFocus
onChange={(e) => {setName(e.target.value)}}
/>
</Form.Group>
<Form.Group
className="mb-3"
controlId="exampleForm.ControlTextarea1"
>
<Form.Label>Description</Form.Label>
<Form.Control
as="textarea"
rows={3}
placeholder="Please describe this service"
value={desc}
onChange={(e) => setDesc(e.target.value)}
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="outline-dark" onClick={handleClose}>
Cancel
</Button>
<Button type="submit" variant="dark" onClick={() => updateService(id)} disabled={!isEnabled}>
Update
</Button>
</Modal.Footer>
</Modal>
</>
);
}
I've tried using a spread operator within my useState hook but it didn't seem to like how I was passing it in, plus I ran into a few scope related issues as well.
In your updateService function just before your request you need to put e.preventDefault() to cancel the <Form> events from happening. then you can create your own
function to call to handle clearing the inputs when you want like so:
const clearFormInputs = () => {
setName('')
setDesc('')
}

Can't type in react input field

I have a simple form with an input field that I can't type on. I first thought the problem was with the onChange or the value props that were setting the input to readonly, but the fact is that I cant type with the browser suggestions and the state updates perfectly (See gif here) it's just that I won't let me type with the keyboard, even after reloading the page.
I also have a Login page that works perfectly except when I log out and redirect back to that page, it won't work until I reload the page, now it will work.
<input
value={name}
onChange={handleChange}
name="name"
/>
const [name, setName] = useState("");
const handleChange = (e:any) => {
setName(e.target.value);
}
Weird thing is that it's in like a readonly state but when I use browser suggestions it works and updates the state.
Here is the whole component:
import React, { useEffect, useState } from 'react';
import { useForm } from '../../utils/useForm';
import { CubeType } from '../../interfaces';
//import useStore from '../store/Store';
import { Modal, Button, Row, Col, FormGroup, FormLabel, FormControl } from 'react-bootstrap';
type Props = {
show: Boolean,
onClose: () => void,
cubeTypes: CubeType[]
};
const ModalTimelist = (props: Props) => {
//const store = useStore();
const [values, handleChangee] = useForm({ cubeType: 1, name: '' });
const [name, setName] = useState("");
const handleChange = (e:any) => {
setName(e.target.value);
}
useEffect(() => {
const modal = document.getElementsByClassName('modal')[0];
if(modal) modal.removeAttribute('tabindex');
}, [props.show]);
return (
<>
<Modal show={props.show} onHide={ props.onClose }>
<Modal.Header>
<Modal.Title>Timelist { name }</Modal.Title>
</Modal.Header>
<Modal.Body>
<Row>
<Col md="3">
<FormGroup>
<FormLabel>Cube Type</FormLabel>
<select
value={values.cubeType}
onChange={ handleChangee }
className="form-select"
name="cubeType"
>
{props.cubeTypes.map((it, idx) => {
return (<option value={ idx } key={"cube"+idx}>{it.name}</option>);
}) }
</select>
</FormGroup>
</Col>
<Col md="9">
<FormGroup>
<FormLabel>Name</FormLabel>
<FormControl
value={name}
onChange={handleChange}
name="name"
/>
</FormGroup>
</Col>
</Row>
</Modal.Body>
<Modal.Footer>
<Button variant="success" onClick={() => props.onClose()}>
Save
</Button>
<Button variant="outline-danger" onClick={() => props.onClose()}>
Cancel
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default ModalTimelist;
value of input must be the state value otherwise it will not change use this code
const App = () => {
const [name,setName] = useState("")
const handle = ({target:{value}}) => setName(value)
return <input
value={name}
onChange={handle}
name="name"
/>
}
Use a debounce for setting name on state.
Example:
const handleChange = (e:any) => {
debounce(() => { setName(e.target.value) }, 300);
}
I tried the code and it works fine I think you should change the browser
and if you want
change this
const ModalTimelist = (props: Props) => {
with
const ModalTimelist:React.FC<Props> = (props) => {
Names specified by you in input field attributes must be same as useState names. Otherwise this problem occurs.
Example:
<input type={"text"} className="form-control" placeholder='Enter your User Name' name="username" value={username} onChange={(e)=>onInputChange(e)}/>
In name="username" , username spell must be same as the spell you used in State.

TypeError: Cannot destructure property 'loading' of 'state' as it is undefined

This is my user signup code in REACTJS, whenever I hit the signup button the redux for request works and then it goes to request_fail instead of success. And after this when I see my mongo collection then the data is stored perfectly. I checked the API in postman so it is working fine, so I tried my best to resolve the error but not get success.
It will be a great help if anyone find solution for this
import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Container, Col, Form,
FormGroup, Label, Input,
Button,
} from 'reactstrap';
import { signupuserAction } from '../../redux/actions/users/userActions';
const Signup = ({ history }) => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [bio, setBio] = useState('');
const [jobtitle, setJobtitle] = useState('');
const [tech, setTech] = useState('');
const dispatch = useDispatch();
//getting user login from store
const state = useSelector(state => {
return state.userLogin;
});
const { loading, userInfo, error } = state
//Redirecting if user is login/authenticated
useEffect(() => {
if (userInfo) {
history.push('/');
}
}, [userInfo])
const submitHandler = e => {
e.preventDefault();
//dispatching action
dispatch(signupuserAction(name, email, password, bio, jobtitle, tech));
}
return (
<Container className="signup">
<h2>Sign Up</h2>
<Form className="form" onSubmit={submitHandler}>
<Col>
<FormGroup>
<Label for="name">Name*</Label>
<Input
type="text"
name="name"
id="name"
placeholder="Enter Your Full Name"
onChange={e => setName(e.target.value)}
value={name}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="email">Email*</Label>
<Input
type="email"
name="email"
id="email"
placeholder="myemail#email.com"
onChange={e => setEmail(e.target.value)}
value={email}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label for="password">Password*</Label>
<Input
type="password"
name="password"
id="password"
placeholder="********"
onChange={e => setPassword(e.target.value)}
value={password}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label>Bio*</Label>
<Input
type="text"
name="bio"
id="bio"
placeholder="Enter a breif Introduction about Yourself"
onChange={e => setBio(e.target.value)}
value={bio}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label>Job Title*</Label>
<Input
type="text"
name="jobtitle"
id="jobtitle"
placeholder="Sotware Developer"
onChange={e => setJobtitle(e.target.value)}
value={jobtitle}
/>
</FormGroup>
</Col>
<Col>
<FormGroup>
<Label>Technology*</Label>
<Input
type="text"
name="tech"
id="tech"
placeholder="ReactJS, NodeJS, Python "
onChange={e => setTech(e.target.value)}
value={tech}
/>
</FormGroup>
</Col>
<Button color="primary" className="btn-submit">Submit</Button>
</Form>
</Container>
);
}
export default Signup;
Your userReducer function seems ok, so I checked out the signupuserAction action. I suspect it's throwing an error in your asynchronous action after the successful POST request. I believe you have a logical error in the Promise chain when processing the POST request response.
Reducer/Action Issue
You attempt to console log the response res.data. Since console.log is a VOID return you actually return undefined from the Promise chain.
You attempt to destructure data from undefined and this throws an error.
The error is caught by the outer try/catch and the USER_REGISTER_FAIL action is dispatched.
Action
const signupuserAction = (name, email, password, bio, jobtitle, tech) => {
return async dispatch => {
try {
dispatch({
type: USER_REGISTER_REQUEST
});
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const {
data // <-- (2) attempt to destructure from undefined
} = await axios.post('http://localhost:4000/app/signup', {
name,
email,
password,
bio,
jobtitle,
tech
},
config
).then((res) => console.log(res.data)) // <-- (1) void return
.catch((err) => console.log(err));
dispatch({
type: USER_REGISTER_SUCCESS,
payload: data
});
console.log(data)
//saving user to localstorage
//localStorage.removeItem('userAuthData');
localStorage.setItem('userAuthData', JSON.stringify(data));
} catch (error) { // <-- (3) catch thrown error & failure action dispatch
console.log(error);
dispatch({
type: USER_REGISTER_FAIL,
payload: error.response && error.response.data.message,
});
}
};
};
Perhaps you meant to parse the JSON, or you wanted to log the result and forgot to return it.
const { data } = await axios.post('http://localhost:4000/app/signup', {
name,
email,
password,
bio,
jobtitle,
tech
},
config
).then((res) => {
console.log(res.data);
return res.data;
})
.catch((err) => console.log(err));
Regarding the "TypeError: Cannot destructure property 'loading' of 'state' as it is undefined" error, I wasn't able to find a path through the code to lead to state being undefined. My hunch is it is on some initial rendering and your redux state perhaps isn't instantiated yet, but this seems unlikely. You can, however, provide a fallback value for state in your UI to guard against the undefined state.
const { loading, userInfo, error } = state || {};
I left a comment above to double check where the state is undefined when attempting to destructure loading.

useState updated state not available in the same handler even with timeout [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed last year.
I have a simple registration form with 3 fields. I have stored the state in formValues with value & error associated with each field. Now when i submit the form without filling any or at least one field the form should be invalid but instead it shows validation messages with invalid fields but makes form valid. Even if i have added setTimeout the updated state is not available in the same handleSubmit. If i submit again the process works just fine. I understand that the state updation is async but if we see the logs in console the form's validation message is logged after formValues log in the render and those logs show that the state was updated correctly but the final validation message shows invalid state. If i change it to class component it works. Here's a link to codesandbox.
import React, { useState } from "react";
import { Button, Form, Col } from "react-bootstrap";
const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout));
const RegistrationForm = () => {
const [formValues, setFormValues] = useState({
name: { value: "", error: null },
email: { value: "", error: null },
password: { value: "", error: null }
});
const handleInputChange = (e, field) => {
const { value } = e.target;
setFormValues(prevValues => ({
...prevValues,
[field]: { value, error: null }
}));
};
const validateForm = () => {
let updatedFormValues = { ...formValues };
Object.keys(formValues).forEach(field => {
if (!formValues[field].value) {
updatedFormValues = {
...updatedFormValues,
[field]: { ...updatedFormValues[field], error: "required" }
};
}
});
setFormValues(updatedFormValues);
};
const isFormValid = () =>
Object.keys(formValues).every(field => formValues[field].error === null);
const handleSubmit = async e => {
e.preventDefault();
validateForm();
await sleep(100);
if (!isFormValid()) {
console.log("form is not valid", formValues);
return;
}
console.log("form is valid", formValues);
// make api call to complete registration
};
console.log({ formValues });
return (
<Form className="registration-form" onSubmit={handleSubmit}>
<Form.Row>
<Col>
<Form.Group controlId="name">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter name"
value={formValues.name.value}
onChange={e => handleInputChange(e, "name")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.name.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
<Col>
<Form.Group controlId="email">
<Form.Label>Email</Form.Label>
<Form.Control
type="email"
placeholder="Enter email"
value={formValues.email.value}
onChange={e => handleInputChange(e, "email")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.email.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
</Form.Row>
<Form.Row>
<Col>
<Form.Group controlId="password">
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Enter password"
value={formValues.password.value}
onChange={e => handleInputChange(e, "password")}
/>
<Form.Control.Feedback type="invalid" className="d-block">
{formValues.password.error}
</Form.Control.Feedback>
</Form.Group>
</Col>
<Col />
</Form.Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
);
};
export default RegistrationForm;
State updates are not just async but are als affected by closures in functional components, so using a sleep or timeout isn't going to leave your with an updated value in the same render cycle
You can read more about it in this post:
useState set method not reflecting change immediately
However, one solution in your case is to maintain a ref and toggle is value to trigger a useEffect in which you will validate the form post handleSubmit handler validates it and sets the formValues
Relevant code:
const validateFormField = useRef(false);
const handleInputChange = (e, field) => {
const { value } = e.target;
setFormValues(prevValues => ({
...prevValues,
[field]: { value, error: null }
}));
};
const validateForm = () => {
let updatedFormValues = { ...formValues };
Object.keys(formValues).forEach(field => {
if (!formValues[field].value) {
updatedFormValues = {
...updatedFormValues,
[field]: { ...updatedFormValues[field], error: "required" }
};
}
});
setFormValues(updatedFormValues);
validateFormField.current = !validateFormField.current;
};
const isFormValid = () =>
Object.keys(formValues).every(field => formValues[field].error === null);
const handleSubmit = async e => {
e.preventDefault();
validateForm();
// make api call to complete registratin
};
useEffect(() => {
if (!isFormValid()) {
console.log("form is not valid", formValues);
} else {
console.log("form is valid", formValues);
}
}, [validateFormField.current]); // This is fine since we know setFormValues will trigger a re-render
Working demo

Resources