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>
CodePudding user response:
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 auseState
hook. - Convert the
componentDidMount
,componentDidUpdate
, andcomponentWillUnmount
lifecycle methods into one or moreuseEffect
hooks with appropriate dependency array. - Convert all references to
this.state
to the new state variables, andthis.props
toprops
or any variables destructured fromprops
.
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));
};
...