React getting empty state - reactjs

I am trying to create an advanced regular expression search application to let me search for some content and replace it in other lines but I am struggling with a much easier task: Cannot delete the rules since the rules state seems to be empty:
export default function App() {
const [displayNewRule, setDisplayNewRule] = useState(false);
const [replaceType, setReplaceType] = useState("line");
const [rules, setRules] = useState([]);
function handleClick() {
setDisplayNewRule(true);
}
function handleSubmit(e) {
e.preventDefault();
const rule = {
name: e.target.name.value,
sourceSearchPattern: e.target.sourceSearchPattern.value,
replaceType,
value: e.target.value.value
};
if (replaceType === "occurrence") {
rule.targetSearchPattern = e.target.targetSearchPattern.value;
rule.location = e.location.value;
}
let $rules = rules;
$rules.push(rule);
setRules($rules);
e.target.reset();
handleReset();
}
function handleReset() {
setDisplayNewRule(false);
}
function deleteRule(i) {
let $rules = rules;
$rules.splice(i, 1);
setRules($rules);
// this gets an empty rules
}
return (
<div className="App">
<form
onSubmit={handleSubmit}
onReset={handleReset}
style={{ display: displayNewRule || "none" }}
>
<h3>Create new rule</h3>
<div className="input-group">
<label htmlFor="name">Name:</label>
<input type="text" id="name" name="name" required />
</div>
<div className="input-group">
<label htmlFor="sourceSearchPattern">Source search pattern:</label>
<input
type="text"
id="sourceSearchPattern"
name="sourceSearchPattern"
required
/>
</div>
<div className="input-group">
<label htmlFor="replaceType">Replace type:</label>
<select
id="replaceType"
name="replaceType"
onChange={(e) => {
setReplaceType(e.target.value);
}}
required
>
<option value="">Select item</option>
<option value="line">Replace line</option>
<option value="occurrence">Replace occurrence</option>
</select>
</div>
{replaceType === "occurrence" && (
<div className="input-group">
<label htmlFor="targetSearchPattern">Target search pattern:</label>
<input
type="text"
id="targetSearchPattern"
name="targetSearchPattern"
required
/>
</div>
)}
<div className="input-group">
<label htmlFor="value">Value:</label>
<input type="text" id="value" name="value" required />
</div>
{replaceType === "occurrence" && (
<div className="input-group">
Occurrence location:
<div className="option-group">
<input
type="radio"
id="next-occurrence"
name="location"
value="next"
required
/>
<label htmlFor="next-occurrence">Next occurrence</label>
</div>
<div className="option-group">
<input
type="radio"
id="previous-occurrence"
name="location"
value="previous"
/>
<label htmlFor="previous-occurrence">Previous occurrence</label>
</div>
</div>
)}
<button type="submit">Submit</button>
<button type="reset">Cancel</button>
</form>
<button onClick={handleClick}>Add rule</button>
<div className="rules">
<h3>Rules</h3>
{rules.map((rule, i) => ( // These rules are being displayed.
<div className="rule" key={i + 1}>
<h5>
{rule.name}
<button
onClick={() => {
deleteRule(i);
}}
>
Delete rule
</button>
</h5>
<p>Replace type: {rule.replaceType}</p>
<p>Source search pattern: {rule.sourceSearchPattern}</p>
</div>
))}
</div>
</div>
);
}
Any clues?

Instead of
let $rules = rules;
$rules.push(rule);
setRules($rules);
try
setRules(oldRules => [...oldRules, rule]);
You're mutating state in your current code, which is a huge anti-pattern. I'm not sure if it's causing the issue you're seeing, but changing to this pattern will make it easier to debug and eliminated a variable. Also change your deleteRule function so that it's likewise not mutating state (make a copy of state with [...rules] and operate on that before setting the new rules).

tl;dr - your component does not re-render at all.
More description: $rules keeps the reference of state object rules (it's the same instance). When calling setRules shallow comparison goes in and prevents you from updating the component (the new value is exactly the same as the current one, because of the direct mutation).
Someone would ask why shallow comparison prevents component from re-rendering since it works only with primitives (which an array is not). The thing is shallow comparison still works on objects that holds the same reference.
const array = [];
array === array // true;
Code with step-by-step description:
function handleSubmit() {
let $rules = rules; // $rules holds the reference and is the same instance as rules
$rules.push(rule); // directly updated state (mutated)
// value of $rules and rules is again exactly the same!
setRules($rules); // the component is not even updated because of the
// shallow comparison - $rules and rules is the same instance
// with exactly the same value
}
Solution is to avoid direct mutation.
setRules((oldRules) => [...oldRules, rule]); as #Nathan posted above.

Related

How to add no of fields dynamically by taking input from user react

I want to add the input no of fields given by the user when clicking on submit button.How to do this in react functional component.
screenshot:
I have an input field,when any user input on that field and submit,according to the input given by the user the no fields will be created.like in above screenshot if a user gives input 6 then 6 fields will be added
I am trying in this way,
import React, { useState } from 'react'
import cal from './image/bgimg.jpg'
function Home() {
const [state,setState]=useState({
semester:'',
credit:'',
sgpa:''
})
const [noOfSem,setNoOfSem]=useState()
const handleChange=(e)=>{
setState({...state,[e.target.name]:e.target.value})
}
const handleClick=()=>{
console.log('hyy',state.semester)
setNoOfSem([state.semester])
}
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<img src={cal} alt="" className='imgcal img-fluid' />
</div>
<div className="col-md-6">
<div className="col-md">
<div className="form1">
<input type="number" value={state.semester} name='semester' onChange={handleChange} placeholder='Enter Total Semester' />
<button type="button" class="btn btn-success" onClick={handleClick}>Submit</button>
<div className="form2">
{noOfSem?.map((item,index)=>
<>
<input type="text" placeholder={`Enter your Semester ${index+1} credit`} key={index}/>
</>
)}
</div>
</div>
</div>
</div>
</div>
</div>
)
}
export default Home
thanks......
I think there's a few changes you can make to improve the code and get it working.
Firstly, I would avoid storing your number of semesters in both the state.semester and noOfSem state, as it means you have to update both of them to keep them in sync.
Given that your component only needs to know the number of semesters when the user presses Submit, you can remove the handleChangeCall and instead only access the value upon submit.
It is also good practice to make use of the <form onSubmit={}> and <input type='submit'> elements, to handle form submission. Instead of using the onClick event from a regular <button>. Some info here.
When using the form, you can then access the value of the semester input directly by storing a reference to it using useRef.
Then in order to iterate over the number of semester, you can construct a new Array to map over. One caveat here is that you have to call the array fill method.
See solution here:
import React, { useState, useRef } from "react";
function Home() {
const [state, setState] = useState({
semester: '',
credit: "",
sgpa: ""
});
const semesterInputRef = useRef();
const handleForm1Submit = (e) => {
e.preventDefault();
if (semesterInputRef?.current.value) {
setState({ ...state, semester: Number(semesterInputRef.current.value) });
}
};
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<div className="col-md">
<form className="form1" onSubmit={handleForm1Submit}>
<input
type="number"
name="semester"
ref={semesterInputRef}
placeholder="Enter Total Semester"
></input>
<input type="submit" className="btn btn-success"></input>
</form>
<form className="form2">
{state.semester &&
Array(state.semester).fill().map((_item, index) => (
<input
key={index}
type="text"
placeholder={`Enter your Semester ${index + 1} credit`}
></input>
))}
</form>
</div>
</div>
</div>
</div>
);
}
export default Home;
I've also created a code sandbox to show that this works as expected, here.

ReactJS Form Input - can't get a "const" variable in validation function

I'm trying to compare two inputs - password and password confirmation. For each input I have a function where I return alert.
Solution that I have is from this tutorial in Register Page.
I'm new in ReactJS and don't understand everything, but here is my code sample:
<Form onSubmit={handleRegister} ref={form}>
{!successful && (
<div>
<div className="form-group">
<label htmlFor="password"></label>
<Input
type="password"
className="form-control"
placeholder="Password"
name="password"
value={password}
onChange={onChangePassword}
validations={[required, vpassword]}
/>
</div>
<div className="form-group">
<label htmlFor="passConfirm"></label>
<Input
type="password"
placeholder="Confirm password"
className="form-control"
name="passConfirm"
value={passConfirm}
onChange={onChangePassConfirm}
validations={[required, vPassConfirm]}
/>
</div>
<div className="form-group">
<button className="btn btn-primary btn-block" id="registerButton">Register</button>
</div>
</div>
)}
{message && (
<div className="form-group">
<div
className={ successful ? "alert alert-success" : "alert alert-danger" }
role="alert"
>
{message}
</div>
</div>
)}
<CheckButton style={{ display: "none" }} ref={checkBtn} />
</Form>
and there I pass this function:
const vPassConfirm = (value) => {
console.log(password);
if (value != password) {
return (
<div className="alert alert-danger" role="alert">
The password does not match!
</div>
);
}
In this vPassConfirm I want to compare my variables
const[password, setPassword] = useState("");
const[passConfirm, setPassConfirm] = useState("");
but in:
console.log(password)
I get only null or something like that - its empty. I cant get the variable.
My question is - is there a possibility to get this variable 'password' inside vPassConfirm function?
EDIT
const onChangePassword = (e) => {
const password = e.target.value;
setPassword(password);
};
const onChangePassConfirm = (e) => {
const passConfirm = e.target.value;
setPassConfirm(passConfirm);
};
there is a declaration above:
const form = useRef();
const checkBtn = useRef();
I use react-valiadtion
After reviewing the documentation for react-validation, it seems you can use the additional arguments passed to validation functions to check other form values.
Validation functions accept (at least) the arguments: value, props, components. Values for other inputs can be found in the components argument.
Here's how you can update your vPassConfirm function so that it checks the current value of the password input:
const vPassConfirm = (value, props, components) => {
if (value !== components['passConfirm'][0].value) {
return (
<div className="alert alert-danger" role="alert">
The password does not match!
</div>
)
}
};
If you want a validation function that would work for either the password or passConfirm input, you could change the conditional line to:
components['password'][0].value !== components['passConfirm'][0].value

Adding new objects multiple values to list using UseState & form ReactJS

I've been trying to create a form that can add a list of objects with multiple attributes to a list. I managed to get this right with one string attribute. But, I cannot figure out how to add an entire object with property values passed from the form. I'm using functional components to do this........How can I create a form that adds new objects of items to a list? I'm fairly new to ReactJS, btw.
resume.jsx
function App() {
const [jobExperience, setjobExperience] = useState([{
jobCompanyName: '',
jobDateRangeStart: '',
jobDateRangeEnd: '',
jobDescription: '',
reference_up_count: 0,
reference_down_count: 0,
}]);
const refUpvoteCount = index => {
const newReferences = [...jobExperience];
newReferences[index].reference_upvote_count++;
setjobExperience(newReferences)
}
const refDownvoteCount = index => {
const newReferences = [...jobExperience];
newReferences[index].reference_downvote_count++;
setjobExperience(newReferences)
}
return(
<Container className="container-fluid g-0">
<Row>
<Col>
<div>
{jobExperience.map((jobExp, index) => (
<JobExperience key={index} jobExperience={jobExp} refUpvote={refUpvoteCount} refDownvote={refDownvoteCount}
))}
</div>
</Col>
<Col>
<div className="pl-5 pr-5 pb-2">
<form onSubmit={//Add To Array of item Objects}>
<div className="form-group">
<label>Company Name</label>
<input type="text" className="form-control" placeholder="Add Company Name" name="jobCompanyName" onChange={handleJobExperienceChange} />
</div>
<div className="form-row">
<div className="col">
<div className="form-group">
<label>Start Date</label>
<Datetime dateFormat="YYYY" timeFormat={false} onChange={(date) => setstartDate(date.year())} value={jobExperience.jobDateRangeStart} />
</div>
</div>
<div className="col">
<div className="form-group">
<label>End Date</label>
<Datetime dateFormat="YYYY" name="jobDateRangeEnd" timeFormat={false} onChange={(date) => setendDate(date.year())} value={jobExperience.jobDateRangeEnd} />
</div>
</div>
</div>
<div className="pt-1">
<div className="form-group">
<label>Job Role/Responsibilities</label>
<textarea style={{width: '100%'}} name="jobDescription" onChange={handleJobExperienceChange} />
<button type="submit" onClick={handleJobExperienceAdd} className="btn btn-success btn-sm btn-block">Add Job Experience</button>
</div>
</div>
</div>
</form>
</Col>
</Row>
</Container>
)
}
function JobExperience({jobExperience, index, refUpvote, refDownvote}) {
return (
<div>
<Card style={{width: '18rem'}} className="remove-border-radius">
<Card.Body>
<Card.Title><span><i className="fa fa-building"></i> {jobExperience.jobCompanyName}</span></Card.Title>
</Card.Body>
<Card.Text>
<i className="fa fa-calendar"></i> {jobExperience.jobDateRangeStart}-{jobExperience.jobDateRangeEnd}
</Card.Text>
<Card.Text>
<span><i className="fa fa-info-circle"></i> {jobExperience.jobDescription}</span>
</Card.Text>
<Button variant="primary" onClick={() => refUpvote(index)} className="remove-border-radius"><i className="fa fa-plus"></i> Reference {jobExperience.reference_upvote_count}</Button>
<Button variant="danger" onClick={() => refDownvote(index)} className="remove-border-radius"><i className="fa fa-minus-circle"></i> Reference {jobExperience.reference_downvote_count}</Button>
</Card>
</div>
)
}
Change the way you set your state from this:
const refUpvoteCount = (index) => {
const newReferences = [...jobExperience];
newReferences[index].reference_upvote_count++;
setjobExperience(newReferences);
};
const refDownvoteCount = (index) => {
const newReferences = [...jobExperience];
newReferences[index].reference_downvote_count++;
setjobExperience(newReferences);
};
To this:
const refUpvoteCount = (index) => {
setjobExperience((previousState) => {
const newReferences = [...previousState];
newReferences[index].reference_upvote_count++;
return newReferences;
});
}
const refDownvoteCount = (index) => {
setjobExperience((previousState) => {
const newReferences = [...previousState];
newReferences[index].reference_downvote_count++;
return newReferences;
});
}
You may also take note the difference to understand this other way of setting-up state that needs to have the the value of the previous state
Do it like this.
const myFunction = () => {
setState((previousState)=> newState)
}
If you need to get the reference of the previous state pass a callback function on setState and that call back function can take 1 parameter which that represent the previous state. And on the callback function you can do some operations if you need to. The return value of callback function will be the new state
And not like this
const myFunction = () => {
const newState = state
setState(newState)
}
This last code sample reference the previous state the wrong way and will not work
const [form, setForm] = useState({}); // form is the previous jobExperience object
const onChange = (event) => {
const { name, value } = event.target;
let savedValue = value;
/*
condition your changes below, you can also extract
the content of the condition to separate functions
*/
if (name === 'jobDateRangeStart') {
savedValue = []; // whatever you need to do with the value
}
if (name === 'jobDateRangeEnd') {
savedValue = []; // whatever you need to do with the value
}
if (name === 'jobDateRangeEnd') {
savedValue = []; // whatever you need to do with the value
}
setForm({ ...form, [name]: savedValue });
};
return (
<div className="pl-5 pr-5 pb-2">
<div className="form-group">
<label>Company Name</label>
<input
className="form-control"
name="jobCompanyName"
onChange={handleChange}
placeholder="Add Company Name"
type="text"
value={form.jobCompanyName || ''}
/>
</div>
<div className="form-row">
<div className="col">
<div className="form-group">
<label>Start Date</label>
<Datetime
dateFormat="YYYY"
onChange={handleChange}
timeFormat={false}
value={form.jobDateRangeStart || ''}
/>
</div>
</div>
<div className="col">
<div className="form-group">
<label>End Date</label>
<Datetime
dateFormat="YYYY"
name="jobDateRangeEnd"
onChange={handleChange}
timeFormat={false}
value={form.jobDateRangeEnd || ''}
/>
</div>
</div>
</div>
<div className="pt-1">
<div className="form-group">
<label>Job Role/Responsibilities</label>
<textarea
name="jobDescription"
onChange={handleChange}
value={form.jobDescription || ''}
style={{width: '100%'}}
/>
<button
className="btn btn-success btn-sm btn-block"
onClick={handleChange}
type="submit"
>
Add Job Experience
</button>
</div>
</div>
</div>
);
As far as i understood you are trying to add an object into an array with multiple fields . and the value of object will come from the values of your form . Here's how can you do it.
# Step 1 :
first create a state that will hold the array of objects .
const [arrayOfObjects , setArrayOfObjects ] = useState([]) ; // empty array initially
# Step 2 :
grab the value from your form's submit function and create the object
onSubmitHandler = () => {
const newObject = {
property1 : "some value " // this values will come from your form
property2 : "some value " // depending on your implementation you may have to maintain separate state for each property
}
const newState = [ ...arrayOfObjects , newObject ];
setArrayOfObjects(newState);
}

React Hooks Form not returning values on submit

I want to build a form where I loop over an array of questions and collect the values of radio buttons for a quiz. Later I want to store those values, so I can can perform some calculations on the result, but for now I just want to log the value (1,2,3,4) and the name of the input to the console. As I'm new to React and Hooks, I have tried the following, but the result that is logged is always an epmty object. The desired result should look like this (built in vanilla JS):
https://florestankorp.github.io/D-D-AlignmentTest/
App.tsx
import React from 'react';
import { useForm } from 'react-hook-form';
import { Question } from '../shared/interfaces';
const QUESTIONS: Question[] = [
{
id: 'q201',
question:
'1. Family elders are expressing disapproval of you to the rest of the family. Do you:',
option1: 'Accept the criticism and change your ways?',
option2: 'Seek a compromise with them?',
option3:
'Besmirch the reputation of those expressing disapproval as you ignore their scorn?',
option4: 'Silence them any way you can?',
},
];
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{QUESTIONS.map((question) => (
<Question {...question} />
))}
<input type="submit" />
</form>
);
}
function Question(props: any): any {
return (
<div>
<p className="question">{props.question}</p>
<div>
<input name={props.id} value="1" type="radio" />
<label htmlFor={props.id}>{props.option1}</label>
</div>
<div>
<input name={props.id} value="2" type="radio" />
<label htmlFor={props.id}>{props.option2}</label>
</div>
<div>
<input name={props.id} value="3" type="radio" />
<label htmlFor={props.id}>{props.option3}</label>
</div>
<div>
<input name={props.id} value="4" type="radio" />
<label htmlFor={props.id}>{props.option4}</label>
</div>
</div>
);
}
I see you are using the useForm hook. Looking at the docs, they provide a method called "register" which you use to register each input component with the hook. That needs to be incorporated into your Question component.
I would suggest
Ensure you add a "key" attribute on the Question component when you map the QUESTIONS array
pass register as a prop to Question, and use it as ref={props.register} on every input component.
see below
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = (data: any) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
{QUESTIONS.map((question) => (
<Question {...question} register={register} key={question.id} /> //<-- notice register and key attribute
))}
<input type="submit" />
</form>
);
}
now you can include this prop in your question component
function Question(props: any): any {
return (
<div>
<p className="question">{props.question}</p>
<div>
<input name={props.id} value="1" type="radio" ref={props.register} /> //<-- note ref={props.register}
<label htmlFor={props.id}>{props.option1}</label>
</div>
<div>
<input name={props.id} value="2" type="radio" ref={props.register} />
<label htmlFor={props.id}>{props.option2}</label>
</div>
<div>
<input name={props.id} value="3" type="radio" ref={props.register} />
<label htmlFor={props.id}>{props.option3}</label>
</div>
<div>
<input name={props.id} value="4" type="radio" ref={props.register} />
<label htmlFor={props.id}>{props.option4}</label>
</div>
</div>
);
}
This should then update the state for the submit event. See this CodeSandbox
EDIT
Added the Answer of #Amit from the comment added below
react-hook-form updated to 7.0.0 from 6.X.X and has breaking changes:
You have to replace all ref={register} with
{...register('value_name')}
Example: Version 6.X.X:
<input ref={register({ required: true })} name="test" />
Version 7.0.X:
<input {...register('test', { required: true })} />

How to detect object property change and handle mapped object update?

I do not quite understand yet how react is working.
As far as I noticed I am supposed to handle detection of a change of form fields (for example angular and knockout detect fields changes with observables).
I have the following snippet in react:
constructor(props) {
super(props);
this.state = {
products: [],
branchId: document.getElementById('branchId').value
};
this.updateproduct = this.updateproduct.bind(this);
this.deleteproduct = this.deleteproduct.bind(this);
}
render() {
return (
<Container>
{
this.state.products.map(product => (
<div key={product.id}>
<div className="col-xs-12 col-sm-3">
<div className="user-label">Title</div>
<input type="text" name="title" className="form-control" defaultValue={product.title} onChange={this.handleChange} />
</div>
<div className="col-xs-12 col-sm-3">
<div className="user-label">Upload image</div>
<input type="text" name="image" className="form-control" defaultValue={product.image} onChange={this.handleChange} />
<img src={product.image} height="100" width="auto" style={{ marginTop: '5px' }} />
</div>
<div className="col-xs-12 col-sm-5">
<div className="user-label">Description</div>
<textarea name="description" className="form-control" rows="7" defaultValue={product.description} onChange={this.handleChange}>
</textarea>
</div>
<div className="col-xs-12 col-sm-1">
<div className="user-label"> </div>
<span className="btn btn-danger pull-right" data-btntype="delete" onClick={() => this.deleteproduct(product.id)}>
×
</span>
<span className="btn btn-success pull-right" data-btntype="update" onClick={() => this.updateproduct(this)} style={{ marginTop: '2px' }}>
✓
</span>
</div>
<div className="clearfix"></div>
</div>
))
}
</Container>
)
}
and don't quite know how to write a function that detect change of every field?
Also how can I handle the form update?
The above snippet is part of a bigger form (that's why form tag is not there) and I need to handle only the product object update above (not the entire form).
UPDATE
I applied the following solution based on #Kejt answer - with just a typo/ two corrections:
In the input:
onChange={e => this.handleChange(product.id, 'name', e.target.value) // Note 'this' must be added to the function whenever it's called
And then the handleChange method:
handleChange = (id, propertyName, value) => {
this.setState(
state => ({ // mapped 'products' need to be 'this.state.products'
products: this.state.products.map(product => product.id === id ? {...product, [propertyName]: value} : product)
})
)
}
Plus I had to add #babel/plugin-proposal-class-properties plugin to make the syntax working.
And in the class contructor added the usual declaration:
this.handleChange = this.handleChange.bind(this);
you already have onChange listener assigned to input onChange={this.handleChange}
you need to add handleChange method to your component
handleChange = event => {
console.log(event.target.value)
}
you can rebuild this method with adding product id and input name
onChange={e => handleChange(product.id, 'name', e.target.value)
and then in handleChange method
handleChange = (id, propertyName, value) => {
this.setState(
state => ({
products: products.map(product => product.id === id ? {...product, [propertyName]: value} : product)
})
}

Resources