React storing components in state with props not updating - reactjs

{article.map((ArticleComp, i) =>
<div className="light-blue p-4 mb-3 mt-3" key={i}>
{ArticleComp}
<button type="button" className="red-button ml-2" onClick={() => removeArticle(i)}>Delete</button>
</div>
)}
<div className="mt-4">
<button type="button" className="blue-button" onClick={() => setAddArticle(true)}>Add Job</button>
<button type="button" className="blue-button ml-2" onClick={() => { setArticle([...article, <Article create="true" post={addArticle} updateError={props.updateError} updateArticle={UpdateArticleCallback}/>]) }}>Add Article</button>
<Link to="/job/all"><button type="button" className="red-button ml-2">Cancel</button></Link>
</div>
export default function Article(props) {
const [title, setTitle] = useState("")
const [content, setContent] = useState("")
if (props.edit === "true" || props.create === "true") {
if (props.post !== "no") {
axios.post('/api/v1/article/create.php', {
title: title,
content: content,
job_id: props.post
}).then((response) => {
if(response.data.Message === 'OK'){
props.updateError(null)
props.updateArticle()
} else {
props.updateError(response.data.Error)
}
})
}
return (
<Row>
{props.post}
<Col>
<Form.Group>
<Form.Label>Article Title</Form.Label>
<Form.Control key="articleTitle" type="text" placeholder="Title" value={title} onChange={e => setTitle(e.target.value)} maxLength="200" />
</Form.Group>
<Form.Group>
<Form.Label>Article Content</Form.Label>
<Form.Control as="textarea" rows={3} key="articleContent" placeholder="Alert Content" value={content} onChange={e => setContent(e.target.value)} maxLength="500" />
</Form.Group>
</Col>
</Row>
)
} else {
return (
<>
{props.article.map((article, i) =>
<div key={i} className="light-blue p-4">
<Row><Col><h3>{article.title}</h3><p>{article.content}</p></Col></Row>
</div>
)}
</>
)
}
}
I am passing the state variable addArticle as a prop however when I update the state, the component does not update. The component works as expected when not in the article state and then being mapped out. Shown is the Article component being added to the article state which is then being rendered. I rendered the props.post in the Article component so I could visually see the state updating. The props.post only takes the latest state when a new article is appended to the state
Thanks

Related

ReactStars component can't set useState - calls TypeError: Cannot read properties of undefined (reading 'name')

I'm quite new to React and can't seem to figure out how to resolve this issue. Maybe a stupid question but here it goes.
I'm trying to make a system where each Person can add skills and a rating of those skills. Each array consist of a skill element as a string and a star element as an integer.
I've made some dynamic inputfields with an add and a remove function for more/less inputfields.
For each inputfield I have an onchange function handleChangeInput and this works fine with skills as this is has a property of "name".
Now when trying to fetch the value from ReactStars I get the error: Uncaught TypeError: Cannot read properties of undefined (reading 'name'). This error is called from the handleChangeInput function. ReactStars doesn't have any property of type either.
How will I be able to fetch the value from ReactStars, if it's even possible? I want to set the useState of inputfields with the values of skill and stars of course.
Image of the TypeError.
The component I use is: react-rating-stars-component
https://www.npmjs.com/package/react-rating-stars-component
Hope this explains the problem. Otherwise let me know. Thanks.
const [data, setData] = useState({});
const [inputField, setInputField] = useState([{ skill: "", stars: 0 }]);
const handleChangeInput = (index, event) => {
const values = [...inputField];
values[index][event.target.name] = event.target.value;
setInputField(values);
setData(values);
};
const handleAddFields = () => {
setInputField([...inputField, { skill: "", stars: 0 }]);
};
const handleRemoveFields = (index) => {
const values = [...inputField];
console.log(inputField.length);
values.splice(index, 1);
setInputField(values);
setData(values);
};
return(
<div>
{inputField?.map((inputField, index) => (
<div key={index}>
<Row>
<Col xs={5} md={5}>
<Form.Group as={Col}>
<Form.Control
className="mb-3"
type="text"
id="skill"
name="skill"
value={inputField?.skill}
onChange={(event) => handleChangeInput(index, event)}
/>
</Form.Group>
</Col>
<Col xs={4} md={4}>
<Form.Group>
<ReactStars
type="number"
name="stars"
count={5}
size={24}
id="stars"
onChange={(event) => handleChangeInput(index, event)}
color="rgba(230, 230, 230, 255)"
emptyIcon={<i className="fa-solid fa-star"></i>}
filledIcon={<i className="fa-solid fa-star"></i>}
value={inputField.stars}
/>
</Form.Group>
</Col>
<Col xs={3} md={3}>
<div className="btn-section">
<button
type="button"
className="round-btn"
onClick={() => handleAddFields()}
>
<i className="fa-solid fa-plus"></i>
</button>
<button
type="button"
className="round-btn"
onClick={() => handleRemoveFields(index)}
>
<i className="fa-solid fa-minus"></i>
</button>
</div>
</Col>
</Row>
</div>
))}
</div>
);
The package passes the new value, not an event containing it.
You can instead simply bind the change handler differently in each case such that the context you need is passed through:
const [data, setData] = useState({});
const [inputField, setInputField] = useState([{ skill: "", stars: 0 }]);
const handleChangeInput = (index, name, value) => {
const values = [...inputField];
values[index][name] = value;
setInputField(values);
setData(values);
};
const handleAddFields = () => {
setInputField([...inputField, { skill: "", stars: 0 }]);
};
const handleRemoveFields = (index) => {
const values = [...inputField];
console.log(inputField.length);
values.splice(index, 1);
setInputField(values);
setData(values);
};
return(
<div>
{inputField?.map((inputField, index) => (
<div key={index}>
<Row>
<Col xs={5} md={5}>
<Form.Group as={Col}>
<Form.Control
className="mb-3"
type="text"
id="skill"
name="skill"
value={inputField?.skill}
onChange={(event) => handleChangeInput(index, 'skill', event.target.value)}
/>
</Form.Group>
</Col>
<Col xs={4} md={4}>
<Form.Group>
<ReactStars
type="number"
name="stars"
count={5}
size={24}
id="stars"
onChange={(newValue) => handleChangeInput(index, 'stars', newValue)}
color="rgba(230, 230, 230, 255)"
emptyIcon={<i className="fa-solid fa-star"></i>}
filledIcon={<i className="fa-solid fa-star"></i>}
value={inputField.stars}
/>
</Form.Group>
</Col>
<Col xs={3} md={3}>
<div className="btn-section">
<button
type="button"
className="round-btn"
onClick={() => handleAddFields()}
>
<i className="fa-solid fa-plus"></i>
</button>
<button
type="button"
className="round-btn"
onClick={() => handleRemoveFields(index)}
>
<i className="fa-solid fa-minus"></i>
</button>
</div>
</Col>
</Row>
</div>
))}
</div>
);

Todo list Reverse state to false on click onclick

I am trying to reverse a state to true or false when user is clicking the cancel button.
The full project is available on sandbox via the link below. I am new to react developing and struggling with state. Can someone help, please?
To see what i mean by the above, please enter some input and click add
https://codesandbox.io/s/eloquent-feather-gc88n?file=/src/header/header.js
Thank Leo
Update your handleCancelClick function to this:
handleCancelClick = (e) => {
setIsEditing((prevState) => !prevState)
console.log('Cancel edit', isEditing)
}
Few more amends that you might need:
In skillist.js:
<EditSkillsForm
inputs={inputs}
isEditing={isEditing}
setIsEditing={setISEditing}
onCancelClick={handleCancelClick}
onUpdateInput={handleUpdateInput}
onCurrentInput={currentInput}
/>
In editSkillsForm.js, we get isEditing and setIsEditing props also :
const EditSkillsForm = ({
handleUpdateInput,
inputs,
handleCancelClick,
setCurrentSkill,
isEditing,
setIsEditing
}) => {
Full file code (just in case):
editSkillsForm.js:
import React, { useState } from "react";
const EditSkillsForm = ({
handleUpdateInput,
inputs,
handleCancelClick,
setCurrentSkill,
isEditing,
setIsEditing
}) => {
//const [ currentSkill, setCurrentSkill ] = useState({});
// const [isEditing, setIsEditing] = useState(true);
// const [ onSubmitEditing, SetOnSubmitEditing ] = useState("")
function handleEditInputChange(e) {
setCurrentSkill(e.target.value);
}
handleCancelClick = (e) => {
setIsEditing(false);
console.log("Cancel edit", isEditing);
};
return (
<>
{isEditing ? (
<div>
<h4>Edit Skill</h4>
<input
className="mb-2"
size="lg"
onChange={handleEditInputChange}
type="text"
name="Update skill"
placeholder="Update skill"
value={inputs}
/>
<button className="btn btn-primary mx-2" onClick={handleUpdateInput}>
Update
</button>
<button onClick={() => handleCancelClick()}>Cancel</button>
</div>
) : null}
{/* <div>
<h4>Edit Skill</h4>
<input
className="mb-2"
size="lg"
onChange={handleEditInputChange}
type="text"
name="Update skill"
placeholder="Update skill"
value={inputs}
/>
</div> */}
{/* <input
className="mb-2"
size="lg"
onChange={handleEditInputChange}
type="text"
name="Update skill"
placeholder="Update skill"
value={inputs}
/> */}
{/* <button className="btn btn-primary mx-2">Update</button> */}
{/* <button onClick={() => handleCancelClick()}>Cancel</button> */}
</>
);
};
export default EditSkillsForm;

React Formik: My custom onBlur override default touched behavior and not displaying my ErrorMessage

I just started learning to code 2 months ago, this week I tried to work with Formik and had this issue where I can't display my ErrorMessage after I added onBlur on the field, before added it, was working normally.
Also, I didn't find another way to work with onBlur, that's why I code it on the field.
I appreciate it if have a hand.
import { Formik, Field, Form, ErrorMessage } from "formik";
import Schema from "./Schema";
import { Container, Row, Col } from "react-bootstrap";
import { useState } from "react";
import Result from "./Result";
import { BsEyeSlashFill } from "react-icons/bs";
const Register = () => {
const [data, setData] = useState(false);
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setConfirmShowPassword] = useState(false)
const [checkMarkName, setCheckMarkName] = useState(null)
const [checkMarkSurname, setCheckMarkSurname] = useState(false)
const [checkMarkEmail, setCheckMarkEmail] = useState(false)
const [checkMarkPass, setCheckMarkPass] = useState(false)
const [checkMarkConfirmPass, setCheckMarkConfirmPass] = useState(false)
const hidePassword = () => {
setShowPassword(!showPassword)
}
const hideConfirmPassword = () => {
setConfirmShowPassword(!showConfirmPassword)
}
function onSubmit(values, actions) {
setData(values)
}
return (
<Formik
validationSchema={Schema}
onSubmit={onSubmit}
validateOnMount
initialValues={{
name: "",
surname: "",
email: "",
password: "",
confirmPassword: "",
}}
>
{(props) => (
<Container>
<Row className="justify-content-center mt-3" >
<Col className="text-center d-flex justify-content-center" md={8}>
<Form className="mt-5 div-form w-100">
{console.log(props)}
<div className="errors mt-3">
<label>Nome</label>
<Field onBlur={() => {
if(!props.errors.name)(
setCheckMarkName(true)
)
}} name="name" className={checkMarkName ? "form-control form-control-green" : "form-control"} type="text" />
<div><ErrorMessage name="name" /></div>
</div>
<div className="errors mt-3">
<label>Surname</label>
<Field onBlur={() => {
if(!props.errors.surname)(
setCheckMarkSurname(true)
)
}} name="surname" className={checkMarkSurname ? "form-control form-control-green" : "form-control"} type="text" />
<div><ErrorMessage name="surname" /></div>
</div>
<div className="errors mt-3">
<label>Email</label>
<Field onBlur={() => {
if(!props.errors.email)(
setCheckMarkEmail(true)
)
}} name="email" className={checkMarkEmail ? "form-control form-control-green" : "form-control"} type="email" />
<div><ErrorMessage name="email" /></div>
</div>
<div className="errors mt-3 position-relative">
<label>Password</label>
<Field onBlur={() => {
if(!props.errors.password)(
setCheckMarkPass(true)
)
}}
name="password"
className={checkMarkPass ? "form-control form-control-green" : "form-control"}
type={showPassword ? 'text' : 'password'}
></Field>
<span className="position-relative eyes text-white"><BsEyeSlashFill onClick={() => hidePassword()} /></span>
<div className="position-relative error-msg"><ErrorMessage name="password" /></div>
</div>
<div className="errors mb-4 position-relative">
<label>Confirm Password</label>
<Field onBlur={() => {
if(!props.errors.confirmPassword)(
setCheckMarkConfirmPass(true)
)
}}
name="confirmPassword"
className={checkMarkConfirmPass ? "form-control form-control-green" : "form-control"}
type={showConfirmPassword ? 'text' : 'password'}
/>
<span className="position-relative eyesConfirm text-white"><BsEyeSlashFill onClick={() => hideConfirmPassword()} /></span>
<div className="position-relative error-msg"><ErrorMessage name="confirmPassword" /></div>
</div>
<button
className="btn btn-primary mb-3"
disabled={!props.isValid}
type="submit"
>
Enviar
</button>
<button
className="btn btn-primary mb-3 ml-3"
type="button"
onClick={() => setData(false)}
>
Reset
</button>
</Form>
</Col>
{
data &&
<Result data={data}/>
}
</Row>
</Container>
)}
</Formik>
);
};
export default Register;

How to get values from react FieldArray in formik form with other fields?

I have created a Formik form that contains a field array, form and fieldArray is in two separate classes as separate components.
My form:
<Formik onSubmit = {(values, { setSubmitting }) => { setSubmitting(false);}}
enableReinitialize>
{({handleSubmit, errors})=> (
<Form onSubmit= { handleSubmit }>
<Form.Group as= { Row } controlId= "cpFormGroupTitle" className="required">
<Form.Label className="post-create-label" column sm={ 2 } >
Title
</Form.Label>
<Col sm={ 10 }>
<Field name="title" component={ renderTextField } type="text"
isinvalid={ !!errors.title ? "true": "false" }
placeholder="Title *" />
</Col>
</Form.Group>
<Form.Group as= { Row } controlId= "cpFrmGroupShortDesc" className="required">
<Form.Label className="post-create-label" column sm={ 2 } >
Short Description
</Form.Label>
<Col sm={ 10 }>
<Field name="short-desc" component={ renderTextArea } type="text"
isinvalid={ !!errors.shortDescription ? "true": "false" }
placeholder="Short Description *" />
</Col>
</Form.Group>
<Form.Group as= { Row } controlId= "cpFormGroupFeatures">
<Form.Label className="post-create-label" column sm={ 2 }>
Features
</Form.Label>
<Col sm={ 10 }>
<TextFieldArray initialValues={{ features: [] } } name="features"/>
</Col>
</Form.Group>
<Form.Group as={ Row }>
<Col sm= { { span: 2, offset:2 } }>
<Button type="submit" variant="primary">Submit</Button>
</Col>
<Col sm={ 2 }>
<Button variant="secondary">Save as draft</Button>
</Col>
</Form.Group>
</Form>
)}
</Formik>
Here, <TextFieldArray> is field array , I need to get values from field array when form is submitted.
TextFieldArray:
export const TextFieldArray = (props) => (
<React.Fragment>
<Formik initialValues= { props.initialValues } render={({ values }) => (
<Form>
<FieldArray name= { props.name } render={arrayHelper => (
<div>
{ values[props.name] && values[props.name].length > 0 ?
(
values[props.name].map((item, index) => (
<div key={index}>
<Form.Group as= { Row }>
<div className="col-md-8">
<Field name={`${props.name}.${index}`}
className="form-control"/>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.remove(index)}>
Remove
</Button>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.insert(index, '')}>
Add
</Button>
</div>
</Form.Group>
</div>
))
) : (
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.push('')} >
{`Add ${ props.name }`}
</Button>
)
}
</div>
)} />
</Form>
)} />
</React.Fragment>
);
I'm a beginner to ReactJS, so someone help me please, that will be huge help from you all.
Thanks.
To have field array be part of same form as other fields, only have one <Formik> and one <Form>. Then make initialValues on Formik that describes all the fields:
<Formik
initialValues={{ friends: someFriends, random: randomText }}
As seen in the following code from Formik FieldArray docs, with another form field added that is not part of the array:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Formik, Form, Field, useField, FieldArray } from "formik";
const someFriends = ["jared", "ian", "brent"];
const randomText = "Four score and seven years ago...";
function MyTextInput({ label, ...props }) {
// useField() returns [formik.getFieldProps(), formik.getFieldMeta()]
// which we can spread on <input> and alse replace ErrorMessage entirely.
const [field, meta] = useField(props);
return (
<>
<label
htmlFor={props.id || props.name}
css={{ backgroundColor: props.backgroundColor }}
>
{label}
</label>
<input className="text-input" {...field} type="text" {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
}
// Here is an example of a form with an editable list.
// Next to each input are buttons for insert and remove.
// If the list is empty, there is a button to add an item.
export const FriendList = () => (
<div>
<h1>Friend List</h1>
<Formik
initialValues={{ friends: someFriends, random: randomText }}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500)
}
render={({ values }) => (
<Form>
<MyTextInput label="Random comment" name="random" />
<FieldArray
name="friends"
render={arrayHelpers => (
<div>
{values.friends &&
values.friends.length > 0 &&
values.friends.map((friend, index) => (
<div key={index}>
<Field name={`friends.${index}`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)} // remove a friend from the list
>
-
</button>
<button
type="button"
onClick={() => arrayHelpers.insert(index, "")} // insert an empty string at a position
>
+
</button>
</div>
))}
{/* Add a new empty item at the end of the list */}
<button type="button" onClick={() => arrayHelpers.push("")}>
Add Friend
</button>
<div>
<button type="submit">Submit</button>
</div>
</div>
)}
/>
</Form>
)}
/>
</div>
);
ReactDOM.render(<FriendList />, document.getElementById("root"));
Code in codesandbox.
I don't think you need to create second form for child component.
You need to just pass the values from the parent to the TextFieldArray
<TextFieldArray values={values.myArr} name="features"/>
And the child component just receive the values and render them (as if it was in the parent component)
export const TextFieldArray = (props) => {
return (
<React.Fragment>
<FieldArray
name= { props.name }
render={arrayHelper => (
<div>
{
props.values[props.name] && props.values[props.name].length > 0 ?
(
props.values[props.name].map((item, index) => (
<div key={index}>
<Form.Group as= { Row }>
<div className="col-md-8">
<Field name={`${props.name}.${index}`} className="form-control"/>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.remove(index)}>
Remove
</Button>
</div>
<div className="col-md-2">
<Button type="button" variant="outline-secondary"
onClick={() => arrayHelper.insert(index, '')}
>
Add
</Button>
</div>
</Form.Group>
</div>
))
) : (
<Button type="button" variant="outline-secondary" onClick={() => arrayHelper.push('')} >
{`Add ${ props.name }`}
</Button>
)
}
</div>
)}
/>
</React.Fragment>
)
Of course don't forget to add the initial values of the array to the parent component.
And finally when you click on the submit button it would give you the values.

React component is not re-rendered after the state is changed with a dropdown list [react hooks]

I have the following React component (using hooks), which lists a number of Tasks as a dropdown list. When an item is selected from the list, I want to display an Update form. This works only when an item is selected for the first time. When I select a new item, nothing happens (although console.log(e.target.value); prints the correct value). I store the selected task's id in st_taskId.
I wonder if you see any issues in the code below:
const ManageReviewTasks = props => {
const reviewRoundId = props.match.params.reviewRoundId;
const [st_taskId, set_taskId] = useState();
useEffect(() => {
if (props.loading == false && st_taskId == null)
props.fetchReviewTasksByReviewRound(reviewRoundId);
}, [reviewRoundId, st_taskId]);
if (props.loading == true) {
return <div>Loading...</div>;
}
return (
<>
{props.reviewTasks && (
<div>
<h3>Configure the Review Tasks</h3>
<br />
{
<div>
<div>
<h4>
Tasks for <span className="font-italic">students receiving</span> feedback:
</h4>
<select
className="form-control"
onChange={e => {
e.preventDefault();
console.log(e.target.value);
set_taskId(e.target.value);
}}>
<option>--SELECT--</option>
{Object.keys(props.reviewTasks).map(id => {
const task = props.reviewTasks[id];
{
if (task.isForStudent) {
return (
<option key={id} id={id} value={id}>
{task.title}
</option>
);
}
}
})}
</select>
</div>
{props.reviewTasks[st_taskId] && (
<UpdateReviewTaskForm task={props.reviewTasks[st_taskId]} />
)}
</div>
}
</div>
)}
</>
);
};
Below is the code for the UpdateReviewTaskForm component:
const UpdateReviewTaskForm = (props) => {
const [st_Title, set_Title] = useState(props.task.title);
const [st_Description, set_Description] = useState(RichTextEditor.createValueFromString(props.task.description, 'html'));
const [st_startDate, set_startDate] = useState(new Date(props.task.startDate.replace('-', '/')));
const [st_DueDate, set_DueDate] = useState(new Date(props.task.dueDate.replace('-', '/')));
const handleCancelClick = (event) => {
event.preventDefault();
history.goBack();
}
const onSubmit_saveTask = (e) => {
e.preventDefault();
props.updateReviewTask({
Id: props.task.id,
Title: st_Title,
Description: st_Description.toString('html'),
StartDate: format(st_startDate, 'DD/MM/YYYY'),
DueDate: format(st_DueDate, 'DD/MM/YYYY'),
})
}
if (props.loading)
return <div>Updating...</div>
return (
<div>
<br/>
<br/>
<div className="p-3 bg-light">
<h3 className="text-info">Update the Task:</h3>
{
props.task &&
<form onSubmit={onSubmit_saveTask}>
<div className="form-group">
<label>Enter the title</label>
<input
//placeholder="Enter a title..."
value={st_Title}
onChange={(event) => { set_Title(event.target.value) }}
className="form-control" />
</div>
<div className="form-group">
<label>Enter a description for the assessment</label>
<RichTextEditor
value={st_Description}
onChange={set_Description}
/>
</div>
<div className="form-group">
<label>Start date to start: </label>
<DatePicker
className="form-control"
selected={st_startDate}
onChange={(date) => set_startDate(date)}
/>
</div>
<div className="form-group">
<label>Due date to complete: </label>
<DatePicker
className="form-control"
selected={st_DueDate}
onChange={(date) => set_DueDate(date)}
/>
</div>
<br />
<button type="submit" className="btn btn-primary">Submit</button>
<button type="reset" className="btn btn-light" onClick={handleCancelClick}>Cancel</button>
</form>
}
</div>
</div>
)
}
Because you are using internal state in UpdateReviewTaskForm, even if this component re-render for the second time, its state will not be reset (to the default value props.task.title for example).
One way to force the state to reset is to use a key prop in UpdateReviewTaskForm like this :
{props.reviewTasks[st_taskId] && (
<UpdateReviewTaskForm key={st_taskId} task={props.reviewTasks[st_taskId]} />
)}
Another way is to use useEffect inside UpdateReviewTaskForm to run when props.task change
useEffect(() => {
// reset the state here
}, [props.task])

Resources