Setting State From props Values To Make An UPDATE Request - javascript

I have Project objects:
{
id: "",
projectName: "" ,
projectIdentifier: "" ,
description:"" ,
startDate: Date ,
endDate: Date ,
}
I have a component that gets called when a user clicks on an "Update Project" button. The specific Project that they clicked on gets passed into the UpdateProject component via props.
When I try to console.log(props.project) right below the const updateProject = (props) => { line, I can see the object coming in. Great. :-)
{
id: 3,
projectName: "Name" ,
projectIdentifier: "1234" ,
description:"Project description" ,
startDate: "02-11-2022" ,
endDate: "02-11-2022" ,
}
However, I am having trouble setting the state in this component. I need to be able to update the details of the Project object and send that UPDATE request to the server.
Any help is greatly appreciated.
import React, { useState, useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { useNavigate, useParams } from "react-router";
import { getProject, createProject } from "../../actions/projectActions";
const UpdateProject = (props) => {
console.log(props.project); //the Project object is coming in!!!
let navigate = useNavigate();
const { id } = useParams();
const [projectName, setProjectName] = useState(props.project.projectName);
console.log(props.project.projectName); <---- this is defined in console.log :-)
console.log(projectName); <---- this is "undefined"
const [projectIdentifier, setProjectIdentifier] = useState(
props.project.projectIdentifier
);
const [description, setDescription] = useState(props.project.description);
const [startDate, setStartDate] = useState(props.project.startDate);
const [endDate, setEndDate] = useState(props.project.endDate);
const [errors, setErrors] = useState(props.project.errors);
const [state, setState] = useState({
id,
projectName: props.project.projectName,
projectIdentifier: props.project.projectIdentifier,
description: props.project.description,
startDate: props.project.startDate,
endDate: props.project.endDate,
});
console.log("state before useEffect: ", state); // <--- here, id is the only thing console logging. Every other key is returning 'undefined'
useEffect(() => {
if (props.errors) {
setErrors(props.errors);
}
props.onGetProject(id, navigate);
console.log("STATE: ", state); <--- again, id is the only thing console logging. Every other key is returning 'undefined'
}, [props.errors]);
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
id: this.state.id,
projectName: this.state.projectName,
projectIdentifier: this.state.projectIdentifier,
description: this.state.description,
startDate: this.state.startDate,
endDate: this.state.endDate,
errors: {},
};
props.onCreateProject(updateProject, navigate);
};
For the rest of the component, my inputs in the return() method look like this, for example:
<form onSubmit={onSubmit}>
<div className="form-group">
<input
type="text"
className={classNames("form-control form-control-lg ", {
"is-invalid": errors.projectName,
})}
placeholder="Project Name"
name="projectName"
defaultValue={props.project.projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
{errors.projectName && (
<div className="invalid-feedback">{errors.projectName}</div>
)}
</div>

It's anti-pattern to store passed props into local component state, just consume the prop value directly. You are also incorrectly referencing a this in the submit handler. this is OFC simply undefined in function components.
From what I can tell you are wanting to initialize the projectName from props, update this value in a form, and upon submitting the form, use this projectName state along with the project object passed as props to update a project.
Use an useEffect hook to keep the local state synchronized with the project object if/when the props.project updates.
const [projectName, setProjectName] = useState(props.project.projectName);
useEffect(() => {
setProjectName(props.project.projectName);
}, [props.project.projectName]);
Merge this projectName state with the props.project object in the submit handler.
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
...props.project, // <-- shallow copy props.project object
id, // <-- id from params
projectName, // <-- projectName state
errors: {},
};
props.onCreateProject(updateProject, navigate);
};
The form should use a controlled input, using the projectName state as the value.
<form onSubmit={onSubmit}>
<div className="form-group">
<input
type="text"
className={classNames(
"form-control form-control-lg ",
{ "is-invalid": errors.projectName }
)}
placeholder="Project Name"
name="projectName"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
{errors.projectName && (
<div className="invalid-feedback">{errors.projectName}</div>
)}
</div>
</form>
Update
Since you are attempting to convert a class component to a function component, here are some basic steps:
Convert class to function and body, change render method to be the function return.
Convert component state into a useState hook.
Convert the componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods into one or more useEffect hooks with appropriate dependency array.
Convert all references to this.state to the new state variables, and this.props to props or any variables destructured from props.
Class component being converted
Function component version:
const UpdateProject = ({ createProject, getProject, project }) => {
const navigate = useNavigate();
const { id } = useParams();
const [state, setState] = useState({
id: "",
projectName: "",
projectIdentifier: "",
description: "",
startDate: "",
endDate: ""
});
useEffect(() => {
setState(project); // update project state when project prop updates
}, [project]);
useEffect(() => {
getProject(id, navigate); // fetch project when id updates
}, [id]);
const onChange = (e) => {
setState((state) => ({
...state,
[e.target.name]: e.target.value
}));
};
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
...state
};
createProject(updateProject, navigate);
};
return (
<div className="project">
<div className="container">
<div className="row">
<div className="col-md-8 m-auto">
<h5 className="display-4 text-center">Update Project form</h5>
<hr />
<form onSubmit={onSubmit}>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg "
placeholder="Project Name"
name="projectName"
value={state.projectName}
onChange={onChange}
/>
</div>
<div className="form-group">
<input
type="text"
className="form-control form-control-lg"
placeholder="Unique Project ID"
name="projectIdentifier"
value={state.projectIdentifier}
onChange={onChange}
disabled
/>
</div>
<div className="form-group">
<textarea
className="form-control form-control-lg"
placeholder="Project Description"
name="description"
onChange={onChange}
value={state.description}
/>
</div>
<h6>Start Date</h6>
<div className="form-group">
<input
type="date"
className="form-control form-control-lg"
name="startDate"
value={state.startDate}
onChange={onChange}
/>
</div>
<h6>Estimated End Date</h6>
<div className="form-group">
<input
type="date"
className="form-control form-control-lg"
name="endDate"
value={state.endDate}
onChange={onChange}
/>
</div>
<input type="submit" className="btn btn-primary btn-block mt-4" />
</form>
</div>
</div>
</div>
</div>
);
};
Adding Redux
While you could still use the connect Higher Order Component from react-redux it's more common now to use the useDispatch and useSelector hooks.
import { useDispatch, useSelector } from 'react-redux';
const UpdateProject = ({ createProject, getProject, project }) => {
...
const dispatch = useDispatch();
const project = useSelector(state => state.project.project);
...
useEffect(() => {
dispatch(getProject(id, navigate)); // fetch project when id updates
}, [id]);
...
const onSubmit = (e) => {
e.preventDefault();
const updateProject = {
...state
};
dispatch(createProject(updateProject, navigate));
};
...

Related

Unable to get input type text name in Reactjs

I am working with Reactjs and nextjs,Right now i am trying to get input type text value but right now i am not getting any value(name is empty during alert), here is my current code
import dynamic from 'next/dynamic';
import React, { FormEventHandler, useRef } from 'react';
import { useEffect, useState } from "react";
import axios from 'axios';
export default function Testform() {
const [state, setState] = useState({ name: '' });
const [Name, setName] = useState('');
const handleChange = (event:any) => setState({...state, name: event.target.value })
const submitHandler: FormEventHandler<HTMLFormElement> = async (event) => {
event.preventDefault();
const name = Name;
alert('name is '+ name);
}
return (
<form className="forms-sample" onSubmit={submitHandler}>
<div className='flex-dvs'>
<div className="form-group">
<h3>Blog title</h3>
<input type="text"
className="form-control"
id="exampleInputName1"
placeholder="Title"
name="name"
value={state.name}
onChange={handleChange}
/>
</div>
</div>
<div className='save-btn text-right'>
<button className='btn btn-primary mr-2'>Save</button>
</div>
</form>
)
}
You are setting the state variable. So use state.name instead of Name
const name = state.name;

How do I push multiple data as an object into my backend using Typescript in React?

I am quite new to typescript and am struggling to push an object into the backend. I am creating a form where I get the user's graduation info and post it into the backend.
The data should look like this in the backend:
"education":[{"place":"A","description":"Learned subject A","year":"2000"}, {"place":"B","description":"Learned subject B","year":"2011"}]
However, I am getting errors where "the type is not assignable to string" or "Property 'name' does not exist on type 'IntrinsicAttributes'".
Here is my parent component and the handleEducation function is not working too:
import React, { useState, ChangeEvent, FormEvent } from "react";
import InputForm from "./InputForm";
import { useNavigate } from "react-router-dom";
import axios from "axios";
export default function Form() {
const [name, setName] = useState("");
const [education, setEducation] = useState({
place: "",
description: "",
year: "",
});
const navigate = useNavigate();
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
switch (event.target.name) {
case "country":
setName(event.target.value);
break;
default:
}
};
const handleEducation = (event: ChangeEvent<HTMLInputElement>) => {
setEducation({
place: event.target.value,
description: event.target.value,
year: event.target.value,
});
};
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
await axios.post(`http://localhost:3000`, {
name,
education,
});
};
return (
<div>
<InputForm
name={name}
education={education}
handleChange={handleChange}
handleSubmit={handleSubmit}
handleEducation={handleEducation}
/>
</div>
);
}
Here is the child component:
interface currUser {
name: string;
education: Array<"">;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
handleEducation: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
export default function InputForm({
name,
education,
handleChange,
handleSubmit,
handleEducation,
}: currUser) {
return (
<div>
<form onSubmit={handleSubmit}>
<div>
<label placeholder="name">Name</label>
<input
type="text"
placeholder="Test"
name="name"
value={name}
onChange={handleChange}
/>
</div>
<div className="container">
<div>
<h3 className="card-title">Education</h3>
</div>
<form>
<div>
<div>
<label>Institute/Organisation*</label>
<input
type="text"
name="organisation"
value={education.place}
onChange={handleEducation}
/>
</div>
<div>
<label>Graduation year</label>
<input
type="text"
name="year"
value={education.year}
onChange={handleEducation}
/>
</div>
</div>
<div>
<label>Description*</label>
<input
type="text"
name="description"
value={education.description}
onChange={handleEducation}
/>
</div>
</form>
</div>
</form>
</div>
);
}
Here is my DB Schema:
How should I fix it? Thank you very much!

Need to Pass props to other components in hook

i want to pass a prop from one(App.jsx) component to other component(form.jsx) in state hooks
App.jsx
import React, {useEffect, useState} from 'react';
import Form from './components/Form';
import Table from './components/Table';
import axios from 'axios';
const App = () => {
const [data, setData] = useState({data:[]});
const [editData, setEditData] = useState([]);
const create = (data) => {
axios.post('http://localhost:5000/info',data).then((res) =>{
getAll();
})
}
useEffect(() =>{
getAll();
},[])
const getAll = () =>{
axios.get("http://localhost:5000/info").then((response) =>{
setData({
data:response.data
})
})
}
const update = event =>{
setEditData(data)
console.log(data); // THIS "data" is the prop that i need to pass to Form.jsx component
}
return (
<div>
<div>
<Form myData={create} editForm={editData} />
</div>
<div>
<Table getData={data} edit={update} />
</div>
</div>
);
};
export default App;
i want that "data" value form App.jsx component as props in this Form.jsx component
Form.jsx
import React, {useState} from 'react';
const Form = (props) => {
const [formData, setFormData] = useState({ Name:'', Age:'', City:''});
const infoChange = e => {
const { name,value} = e.target;
setFormData({
...formData,
[name]: value,
})
}
const infoSubmit = e =>{
e.preventDefault();
let data={
Name:formData.Name,
Age:formData.Age,
City:formData.City
}
props.myData(data);
}
const componentWillReceive = (props) => { // i want the props data here
console.log(props.data); // in class component they use componentWillReceiveRrops ,
} // is there any alternative for function based component to receive props?
return (
<div>
<form onSubmit={infoSubmit} autoComplete="off">
<div>
<label>Name:</label>
<input type="text" onChange={infoChange} name="Name" value={formData.Name} placeholder="Enter Name" />
</div>
<div>
<label>City:</label>
<input type="text" onChange={infoChange} name="City" value={formData.City}
placeholder="Enter City" />
</div>
<div>
<label>Age:</label>
<input type="text" onChange={infoChange} name="Age" value={formData.Age} placeholder="Enter Age" />
</div>
<button type="submit">Submit</button>
</form>
</div>
);
};
export default Form;
i have commented the area of problem within the code , you can ignore the return () block of code.
Sorry for silly questions but THANKYOU Very Much !!! in advance
Use the following code in Form.jsx, the useEffect will listen the change of props.data and update the value
useEffect(() => {
setFormData(props.data);
},
[props.data]);
For more information, you may check the following answer
https://stackoverflow.com/a/65842783/14674139

React Js : How to use UseState In CallBack?

I have below code :
import React,{useState} from 'react'
function ReactForm() {
const iState =[{
Name : '',
Email :'',
Salary :0
}]
const [state, setstate] = useState(iState);
function validationHandler()
{
console.log(state);
}
return (
<div>
Name : <input type="text" onChange={(e)=>{setstate(...state, state.Name=e.target.value)}}></input>
<br></br>
Email : <input type="text" onChange={(e)=>{setstate(...state, state.Email=e.target.value)}}></input>
<br></br>
Salary : <input type="text" onChange={(e)=>{setstate(...state, state.Salary=e.target.value)}}></input>
<br></br>
<button onClick={validationHandler}>Validate Us</button>
</div>
)
}
export default ReactForm
I am performing basic validations here. I am receiving error : TypeError: state is not iterable
After going through few links on stackoverflow , I added - [ ] over state , but it did not helped.
EDIT 1 :
After Adding :- setstate({...state, state.Name: e.target.value}) : Unexpected token, expected "," (18:79)
Instead of having the setState called for each of the inputs you can make use of the name attribute and can refactor the code as below
import React, {useState} from 'react';
function ReactForm() {
const [state, setstate] = useState({
Name: '',
Email: '',
Salary: 0,
});
const handleChange = (e) => {
const {name, value} = e.target;
setstate((prevState) => ({...prevState, [name]: value}));
};
function validationHandler() {
console.log(state);
}
return (
<div>
Name :{' '}
<input
type="text"
value={state.Name}
name="Name"
onChange={handleChange}
/>
<br></br>
Email :{' '}
<input
type="text"
value={state.Email}
name="Email"
onChange={handleChange}
/>
<br></br>
Salary :{' '}
<input
type="text"
value={state.Salary}
name="Salary"
onChange={handleChange}
/>
<br></br>
<button onClick={validationHandler}>Validate Us</button>
</div>
);
}
export default ReactForm;
Refer:
Controlled Component
Your initial state is an array of objects. I'm not sure whether this is what you are looking for.
Assume your iState is (Just an object)
const iState = {
Name: '',
Email: '',
Salary: 0
}
Then you should do something like this in your onChange listener
// setState should use camel case for best pratice BTW
const handleChangeName = e => setstate({
...state,
Name: e.target.value
});
If you are sticking to the array state, the listener should look something like this instead.
const handleChangeName = e => setstate([
...state,
{
...state[0], // or whatever index you may use in the future
Name: e.target.value
}
]);
You can do the following assignment state.Name=e.target.value ****:
You are using an array not an object, so there is nothing you can access using state.Name=e.target.value
So if wanna access it directly the same way you used you have to use state property as OBJECT not as ARRAY:
const iState = {
Name: '',
Email: '',
Salary: 0
}
And the standard for the component that has form to handle is to use stateful component
OR
You can use stateless (functional) component and make form each form field its own state:
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [salary, setSalary] = useState(0);
So the component will be:
import React, { useState } from 'react'
function ReactForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [salary, setSalary] = useState(0)
function validationHandler() {
console.log('Name: ' + name);
console.log('Email: ' + email);
console.log('Salary: ' + salary);
}
return (
<div>
Name : <input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}></input>
<br></br>
Email : <input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}></input>
<br></br>
Salary : <input
type="text"
value={salary}
onChange={(e) => setSalary(e.target.value)}></input>
<br></br>
<button onClick={validationHandler}>Validate Us</button>
</div>
)
}
export default ReactForm;

Test a button of a child component in the parent component

I'm new in tests and search since 3 days how to resolve my problem. Hope you could help me..
I have a parent component :
import React from 'react';
import './Subscribe.scss';
import Button from '../../Components/Button/Button';
class Subscribe extends React.Component {
state = {
user: {
firstName: '',
pseudo:'',
email: '',
password:''
}
}
handleChange = (e) => {
this.setState({
user: {...this.state.user, [e.target.name]: e.target.value}
}, () => console.log(this.state))
}
onSubmit = (e) => {
// e.preventDefault()
console.log("you've clicked")
//todo
}
render() {
return(
<form className='subscribe' id='subscriptionForm'>
<label htmlFor='firstName'>Prénom :</label>
<input
data-testid='inputString'
type='text'
name='firstName'
onChange={this.handleChange}
value={this.state.user.firstName}
/>
<label htmlFor='pseudo'>Pseudo :</label>
<input
data-testid='inputString'
type='text'
name='pseudo'
onChange={this.handleChange}
value={this.state.user.pseudo}
/>
<label htmlFor='email'>Email :</label>
<input
data-testid='inputString'
type='email'
name='email'
onChange={this.handleChange}
value={this.state.user.email}
/>
<label htmlFor='password'>
password :
</label>
<Button id='submitSubscription' text='Go go !' onSubmit={this.onSubmit}/>
<Button text='Annuler'/>
</form>
)
}
}
export default Subscribe;
A child component :
import React from "react";
const Button = (props) => {
return (
<button type="button" onClick={props.onClick}>{props.text}</button>
)
}
Button.displayName = 'Button'
export default Button
i wanna test it but nothing works...
My test :
import React from 'react';
import { shallow, mount } from 'enzyme';
import Subscribe from './Subscribe.js';
import Button from "./../../Components/Button/button.js"
describe('<LogIn />', () => {
it('Should call onSubmit on subscribe component when button component is clicked and allow user to subscribe ', () => {
// Rend le composant et les enfants et renvoie un wrapper Enzyme
const wrapper = mount(<Subscribe />);
// Trouve la première balise bouton
const button = wrapper.find("#submitSubscription");
// Récupère l'instance React du composant
const instance = wrapper.instance();
// Ecoute les appels à la fonction on Submit
jest.spyOn(instance, "onSubmit");
button.simulate('click');
expect(instance.onSubmit).toHaveBeenCalled();
})
Comments are what I tried.
The answer is still Expected number of calls: >= 1 Received number of calls: 0
I'm open to try by react test too, I begin so any help would be a pleasure.
Thanks in advance !
Your form setup has some mistakes. Mainly, you'll want to put a handleSubmit on the form's onSubmit prop and change one of the buttons to have a type prop of submit. Please see the codesandbox for a working version:
Working example (you can run the tests by clicking on the Tests tab next to the Browser tab):
This example uses some es6 syntax, so if you're unfamilar, please read the following:
Object Destructuring
Spread syntax
Arrow Function Expressions (Fat Arrow/Lamba Functions) - Implict Returns
components/Button/Button.js
import React from "react";
const Button = props => (
<button
style={{ marginRight: 10 }}
type={props.type || "button"}
onClick={props.onClick}
>
{props.text}
</button>
);
Button.displayName = "Button";
export default Button;
components/Subscribe/Subcribe.js
import React, { Component } from "react";
import Button from "../Button/Button";
// import './Subscribe.scss';
const initialState = {
user: {
firstName: "",
pseudo: "",
email: "",
password: ""
}
};
class Subscribe extends Component {
state = initialState;
handleChange = ({ target: { name, value } }) => {
// when spreading out previous state, use a callback function
// as the first parameter to setState
// this ensures state is in sync since setState is an asynchronous function
this.setState(
prevState => ({
...prevState,
user: { ...prevState.user, [name]: value }
}),
() => console.log(this.state)
);
};
handleCancel = () => {
this.setState(initialState);
};
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.state);
};
render = () => (
<form
onSubmit={this.handleSubmit}
className="subscribe"
id="subscriptionForm"
>
<label htmlFor="firstName">Prénom :</label>
<input
data-testid="inputString"
type="text"
name="firstName"
onChange={this.handleChange}
value={this.state.user.firstName}
/>
<br />
<label htmlFor="pseudo">Pseudo :</label>
<input
data-testid="inputString"
type="text"
name="pseudo"
onChange={this.handleChange}
value={this.state.user.pseudo}
/>
<br />
<label htmlFor="email">Email :</label>
<input
data-testid="inputString"
type="email"
name="email"
onChange={this.handleChange}
value={this.state.user.email}
/>
<br />
<label htmlFor="password">password :</label>
<input
data-testid="inputString"
type="password"
name="password"
onChange={this.handleChange}
value={this.state.user.password}
/>
<br />
<br />
<Button type="button" text="Annuler" onClick={this.handleCancel} />
<Button id="submitSubscription" type="submit" text="Soumettre" />
</form>
);
}
export default Subscribe;
components/Subscribe/__tests__/Subscribe.test.js (I'm passing in a onSubmit prop to mock it and I expect that to be called. This is a more common test case versus testing against React's event callback implementation which forces you to unnecessarily spy on the class field. By testing against the prop (or a state change or some secondary action), we already cover whether or not the callback works!)
import React from "react";
import { mount } from "enzyme";
import Subscribe from "../Subscribe.js";
const onSubmit = jest.fn();
const initProps = {
onSubmit
};
describe("<LogIn />", () => {
it("Should call onSubmit on subscribe component when button component is clicked and allow user to subscribe ", () => {
const wrapper = mount(<Subscribe {...initProps} />);
const spy = jest.spyOn(wrapper.instance(), "handleSubmit"); // not necessary
wrapper.instance().forceUpdate(); // not necessary
wrapper.find("button[type='submit']").simulate("submit");
// alternatively, you could simply use:
// wrapper.find("form").simulate("submit");
expect(spy).toHaveBeenCalledTimes(1); // not necessary
expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
index.js
import React from "react";
import { render } from "react-dom";
import Subscribe from "./components/Subscribe/Subscribe";
import "./styles.css";
const onSubmit = formProps => alert(JSON.stringify(formProps, null, 4));
render(
<Subscribe onSubmit={onSubmit} />,
document.getElementById("root")
);

Categories

Resources