I designed a flash card and I want to change the text of the card when I click on the button, but I do not know how to send the form information to the App component to update the state.
App component
function App() {
const [flashCard, setFlashCard] = useState({
word: "test",
persianEquivalent: "test",
});
const setFlashCardHandler = (e) => {
setFlashCard({ flashCard: e });
};
return (
<div className="container">
<Form setFlashCard={setFlashCardHandler} />
<FlashCard flashCard={flashCard} />
</div>
);
}
export default App;
Form component:
function Form({ setFlashCard }) {
const [valueEn, setValueEn] = useState();
const [valuePer, setValuePer] = useState();
const setValueHandlerEn = (e) => {
setValueEn(e.target.value);
};
const setValueHandlerPer = (e) => {
setValuePer(e.target.value);
};
const setFlashCardHandler = (e) => {
e.preventDefault();
setFlashCard((e)=>{valueEn=e.target[0].value});
};
return (
<form onSubmit={setFlashCardHandler}>
<input
id="word-input"
placeholder="world"
value={valueEn}
onChange={setValueHandlerEn}
/>
<input
id="persian-equivalent-input"
placeholder="Equivalent"
value={valuePer}
onChange={setValueHandlerPer}
/>
<button id="submit-btn">send</button>
</form>
);
}
export default Form;
There is an issue with how you set values.
Pass setFlashCard as the prop to Form
<Form setFlashCard={setFlashCard} />
In Form change the setFlashCardHandler as below.
const setFlashCardHandler = (e) => {
e.preventDefault();
setFlashCard({ word: valueEn, persianEquivalent: valuePer });
};
Set empty string ("") as default state to avoid sending undefined.
const [valueEn, setValueEn] = useState("");
const [valuePer, setValuePer] = useState("");
Please add new updateFlashCard props to to component.
Like:
<Form updateFlashCard={(e) => setFlashCard(e)}/>
And change value in updateFlashCard state from form component
Like:
function Form({ updateFlashCard }) {
const [valueEn, setValueEn] = useState();
const [valuePer, setValuePer] = useState();
const setValueHandlerEn = (e) => {
setValueEn(e.target.value);
};
const setValueHandlerPer = (e) => {
setValuePer(e.target.value);
};
const setFlashCardHandler = (e) => {
e.preventDefault();
updateFlashCard(e.target[0].value) // update from here
};
return (
<form onSubmit={setFlashCardHandler}>
<input
id="word-input"
placeholder="world"
value={valueEn}
onChange={setValueHandlerEn}
/>
<input
id="persian-equivalent-input"
placeholder="Equivalent"
value={valuePer}
onChange={setValueHandlerPer}
/>
<button id="submit-btn">send</button>
</form>
);
}
export default Form;
Related
I am a beginner in react js programming. I'm trying to do the todo project, which is a classic project. When I delete or add an element from the list, the newly formed list appears on the screen by combining with the previous one, I will show it with a picture below. I did not understand the source of the eror so wanted to post it here to get some advices suggestions about why it is happening.Thank you.(I am getting and storing data in firebase firestore database)
Before Adding an element initial array state
After adding an element to the array.
I am using useState for array and using useEffect to get initial data
MainPage.js that contains form and the list components.
const MainPage = () => {
const [isLoading, setLoding] = useState(true);
const [array, setArray] = useState([]);
const sub = async (email) => {
var result = [];
await onSnapshot(doc(db, "users", email), (doc) => {
var data = doc.data().todos;
data.forEach((element) => {
Object.keys(element).map(() => {
result.push(element["title"]);
});
});
setArray(result);
setLoding(false);
});
};
useEffect(() => {
sub(auth.currentUser.email);
}, []);
const onAddToDo = (todoTitle) => {
setArray((prevAray) => {
return [...prevAray, todoTitle];
});
};
const onRemove = (title) => {
setArray((prevAray) => {
return [array.pop(array.indexOf(title))];
});
};
return (
<div>
{isLoading && <h1>Loading</h1>}
{!isLoading && (
<div>
<section>
<NavBar></NavBar>
<ToDoForm passData={onAddToDo} />
</section>
<section>
<CardList removeCards={onRemove} array={array} />
</section>
</div>
)}
</div>
);
};
export default MainPage;
Firebase.js that stores the firebase update methods
export const deleteItem = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayRemove({ title: title }),
});
};
export const addnewTodo = (title) => {
updateDoc(doc(db, "users", auth.currentUser.email), {
todos: arrayUnion({ title: title }),
});
};
TodoForm.js component
const ToDoForm = (props) => {
const [todoTitle, setTitle] = useState("");
const titleChangeHandler = (event) => {
setTitle(event.target.value);
};
const newTodoAdder = (event) => {
event.preventDefault();
addnewTodo(todoTitle);
props.passData(todoTitle);
};
return (
<div className="form_holder">
<div className="form_container">
<form onSubmit={newTodoAdder}>
<h3>Add Events</h3>
<label>Title</label>
<input
onChange={titleChangeHandler}
type="text"
placeholder="Title"
id="title"
></input>
<div className="holder">
<button type="sumbit">Add</button>
</div>
</form>
</div>
</div>
);
};
export default ToDoForm;
CardList.js component
const CardList = (props) => {
const array = props.array;
if (array.length === 0) {
return (
<div className="grid_container">
<h2>Found no todos</h2>
</div>
);
}
return (
<div className="grid_container">
{array.map((element, index) => {
return (
<Card
removeSelf={() => {
props.removeCards(element);
}}
key={index}
title={element}
/>
);
})}
</div>
);
};
export default CardList;
Card.js component
const Card = (props) => {
const handleRemove = (event) => {
event.preventDefault();
deleteItem(props.title);
props.removeSelf();
};
return (
<div className="card">
<h2 className="card__title">{props.title}</h2>
<button type="button" onClick={handleRemove}>
Delete
</button>
</div>
);
};
export default Card;
EDIT ;
Index.js file
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
SOLUTION
I fixed the issue by changing the add and remove functions that were inside of MainPage.js file You can see the new versions bellow. Hope someday it will help somebody.
Use effect was called once all I had to do get the data again after a change...
New Remove and Add functions
const onAddToDo = (todoTitle) => {
console.log(todoTitle + " Added");
sub(auth.currentUser.email);
};
const onRemove = (title) => {
console.log(title + " Deleted");
sub(auth.currentUser.email);
};
I'm developing a custom Form validation for my React project with Typescript.
I'm facing an issue with the useState that is not updating immediately the state containing the errors when I submit a form.
Let me provide you an example.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
When I'm checking for the errors in the handleSubmit, there are no errors, even if errors should be present there:
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
In general, I'm wondering what is the best practice in order to avoid these kinds of issues with the react state not updating immediately the state?
I already tried with useEffect, listening on the validationErrors changes but nothing changes actually, the behavior is pretty the same.
I'm sure I'm missing something.
useState is asynchronous, so state changes (setValidationErrors) are not applied immediately. Therefore, you cannot get the latest state of validationErrors in the next line.
We can do validation and set state separately. In that case, you can leverage the latest value (not the latest state) to check values validity.
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
//get all invalid values
const invalidValues = returnInvalidValues();
//update state
setValidationErrors(prev => [...prev, ...invalidValues])
if (invalidValues.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
};
const returnInvalidValues = () => {
return Object.entries(formValues).filter(([inputName, value]) => formValues[inputName] === ''). map(invalidValue => `${inputName} is not valid`);
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
You also can try useEffect
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
//introduce useEffect here
useEffect(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
}, [validationErrors]);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
If it does not work for your case, you can delay using the latest state with setTimeout. With this approach, it will put the task to get the latest state to the end of the call stack queue (you can check this document)
const initialFormState = {
email: '',
password: '',
}
const SignUpForm = () => {
const [formValues, setFormValues] = useState(initialFormState);
const [validationErrors, setValidationErrors] = useState<string>([]);
const handleChange = () => {
// handle the change implementation updating the formValues ...
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
doValidationOnSubmit();
setTimeout(() => {
if (validationErrors.length > 0) {
console.log('validation errors!!');
return;
}
doLogin();
})
};
const doValidationOnSubmit = () => {
Object.entries(formValues).forEach(([inputName, value]) => {
if (formValues[inputName] === '') {
setValidationErrors((oldValidationErrors) => [...oldValidationErrors, `${inputName} is not valid`]);
}
});
}
const doLogin = () => {
// do login logic
}
return (
<>
<form onSubmit={handleSubmit}>
<input type="email" name="email" onChange={handleChange} />
<input type="password" name="email" onChange={handleChange} />
<button type="submit">Login</button>
</form>
</>
);
}
export default SignUpForm;
I understand that we can pass props from a parent component to the child component. In my case, I am passing from App.js ( parent component) to child component - UserInput.js. However, I also want to pass the props back from UserInput.js to App.js so that I can reset the label values.
However, I am unable to do so. When I do props.reset(), it says reset is not a function. I get exception - typeError- "prop.reset is not a function". My ultimate goal is to reset the userName and age field to blank.
Here is my App.js and UserInput.js
import "./App.css";
import UserInput from "./Components/UserInput/UserInput";
import Validation from "./Components/Validation/Validation";
import { useState } from "react";
import Output from "./Components/Output/Output";
const App = (props) =>
{
const errorMessage = [
"",
"Please enter a valid name and age (non-empty values).",
"Please enter a valid age (>0)."
];
let usrList = [
];
const [errNumber, setErrNumber] = useState(0);
const [userList, setUsrList] = useState(usrList);
const validationOkHandler = () => {
//setting error back to 0.
setErrNumber(0);
}
const userInputHandler = (userData) => {
//console.log(userData);
if (userData.user.trim() === "" || userData.age === 0) {
console.log(
"Invalid Input - Please enter a valid name and age (non-empty values)."
);
setErrNumber(1);
}
else if (userData.age < 0) {
console.log("Invalid input Please enter a valid age (>0).");
setErrNumber(2);
}
else {
props.reset();
setErrNumber(0);
userData.id = Math.random();
setUsrList( (previousUSer) => {
return [userData, ...previousUSer];
});
}
};
return (
<div>
<UserInput
onUserInput={userInputHandler}
valdiationOk={validationOkHandler}
/>
{errNumber > 0 && (
<Validation valdiationOk={validationOkHandler}>
{errorMessage[errNumber]}
</Validation>
)}
{errNumber === 0 && <Output userList={userList} />}
</div>
);
}
export default App;
Here is my UserInput.js
import { useState } from "react";
import styles from "./UserInput.module.css";
import App from "../../App";
const UserInput = (props) => {
const [usrName, setUsrName] = useState("");
const [age, setAge] = useState("");
const userNameHandler = (event) => {
console.log(event.target.value);
setUsrName(event.target.value);
};
const ageHandler = (event) => {
console.log(event.target.value);
setAge(event.target.value);
};
const resetForm = () => {
setUsrName('');
};
const addUserHandler = (event) => {
const userInfo = {
user: usrName,
age: +age,
};
console.log(userInfo);
event.stopPropagation();
props.onUserInput(userInfo);
<App reset={resetForm} />
};
const ModalHandler = () => {
console.log("Modal handler clicked");
props.valdiationOk();
};
return (
<div className={`${styles["container"]}`} onClick={ModalHandler}>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
id="username"
onChange={userNameHandler}
></input>
</div>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="age">Age</label>
<input type="text" name="age" id="age" onChange={ageHandler}></input>
</div>
<div>
<button type="submit" onClick={addUserHandler}>
Add User
</button>
</div>
</div>
);
};
export default UserInput;
There's no need for props to invoke a function from another functional component in this case. You can plainly pass the resetForm as a callback argument to the onUserInput prop function along with userInfo, this argument function can then be directly invoked based on your checks and conditions. If you implement this you might still not see the form fields reset to empty strings "" when the form is valid, because there's no two-way binding established between the state variable and the input fields value. To get things work as expected, you should establish this binding by setting the fields value to the hook's state variable, so that the value in the field is the same as the value of the state variable.
Your updated code should be something like this:
App.js
import "./App.css";
import UserInput from "./Components/UserInput/UserInput";
import Validation from "./Components/Validation/Validation";
import { useState } from "react";
import Output from "./Components/Output/Output";
const App = (props) =>
{
const errorMessage = [
"",
"Please enter a valid name and age (non-empty values).",
"Please enter a valid age (>0)."
];
let usrList = [
];
const [errNumber, setErrNumber] = useState(0);
const [userList, setUsrList] = useState(usrList);
const validationOkHandler = () => {
//setting error back to 0.
setErrNumber(0);
}
const userInputHandler = (userData,reset) => { //reset is the callback function
//console.log(userData);
if (userData.user.trim() === "" || userData.age === 0) {
console.log(
"Invalid Input - Please enter a valid name and age (non-empty values)."
);
setErrNumber(1);
}
else if (userData.age < 0) {
console.log("Invalid input Please enter a valid age (>0).");
setErrNumber(2);
}
else {
reset(); //Call reset function from the function argument as a callback function
setErrNumber(0);
userData.id = Math.random();
setUsrList( (previousUSer) => {
return [userData, ...previousUSer];
});
}
};
return (
<div>
<UserInput
onUserInput={userInputHandler}
valdiationOk={validationOkHandler}
/>
{errNumber > 0 && (
<Validation valdiationOk={validationOkHandler}>
{errorMessage[errNumber]}
</Validation>
)}
{errNumber === 0 && <Output userList={userList} />}
</div>
);
}
export default App;
UserInput.js
import { useState } from "react";
import styles from "./UserInput.module.css";
const UserInput = (props) => {
const [usrName, setUsrName] = useState("");
const [age, setAge] = useState("");
const userNameHandler = (event) => {
console.log(event.target.value);
setUsrName(event.target.value);
};
const ageHandler = (event) => {
console.log(event.target.value);
setAge(event.target.value);
};
const resetForm = () => {
setUsrName('');
};
const addUserHandler = (event) => {
const userInfo = {
user: usrName,
age: +age,
};
console.log(userInfo);
event.stopPropagation();
props.onUserInput(userInfo,resetForm); //Pass resetfrom function as the callback function.
};
const ModalHandler = () => {
console.log("Modal handler clicked");
props.valdiationOk();
};
return (
<div className={`${styles["container"]}`} onClick={ModalHandler}>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="username">Username</label>
<input
type="text"
name="username"
id="username"
value = {usrName} //Two way binding using the value attribute.
onChange={userNameHandler}
></input>
</div>
<div className={`${styles["inputWrapper"]}`}>
<label htmlFor="age">Age</label>
<input type="text" value={age} name="age" id="age" onChange={ageHandler}></input>
</div>
<div>
<button type="submit" onClick={addUserHandler}>
Add User
</button>
</div>
</div>
);
};
export default UserInput;
My goal after clicking the register button is:
Make input fields blank
Do not show error tooltips
Here is the link on CodeSandbox
I've already tried using event.target.reset(); however the tooltips are still appearing on the screen.
export default function App() {
const [showSucessAlert, setshowSucessAlert] = useState(false);
const [validated, setValidated] = useState(false);
const [transitionAlert, setTransitionAlert] = useState(false);
const handleSubmit = (event) => {
const form = event.currentTarget;
event.preventDefault();
if (form.checkValidity() === false) {
event.stopPropagation();
} else {
handleClickTransitionAlert();
setshowSucessAlert(true);
}
setValidated(true);
};
const handleClickTransitionAlert = () => {
setTransitionAlert(true);
setTimeout(() => {
setTransitionAlert(false);
}, 1700);
};
return (
<Form noValidate validated={validated} onSubmit={handleSubmit}>
<Form.Group className="position-relative" controlId="validationPassword">
<Form.Label>Password</Form.Label>
<InputGroup hasValidation id="validationPassword" />
<Form.Control
type="password"
aria-describedby="validationPassword"
required
/>
<Form.Control.Feedback tooltip type="invalid">
Please enter your Password.
</Form.Control.Feedback>
</Form.Group>
<Alert
className={`mt-1 p-1 position-fixed ${
transitionAlert ? "alert-shown" : "alert-hidden"
}`}
show={showSucessAlert}
variant="success"
>
Registered user!
</Alert>
<Button className="mt-5" variant="primary" type="submit">
Register
</Button>
</Form>
);
}
Here is the link on CodeSandbox
Every help is welcome!
I don't commonly use uncontrolled components, but I think you could solve this by adding setValidated(false) and event.target.reset() to the handleClickTransitionAlert, like this:
const handleClickTransitionAlert = (event) => {
setTransitionAlert(true);
setTimeout(() => {
setTransitionAlert(false);
setValidated(false)
event.target.reset()
}, 1700);
};
Try reseting the validated attribute on Bootsrap Form.
it should look something like this (this is pseudo-code):
import React, { useRef, useState } from 'react';
const FormComponent = () => {
const [validated, setValidated] = useState(false);
const formRef = useRef(null);
const handleReset = () => {
formRef.current.reset();
setValidated(false);
};
const handleSubmit = () => {
// Do stuff here
// On success or error:
setValidated(true);
handleReset();
}
return(
<Form ref={formRef} validated={validated} onSubmit={handleSubmit}>
// your form inputs
</Form>
);
export default FormComponent;
}
**its a recipe app and i'm stuck on passing search input to the api.
this are two separate components**
this is the component that does the search:
class Search extends Component {
state = {
search: ""
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = (e) => {
e.preventDefault();
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="search"
value={this.state.search}
onChange={this.handleChange}
/>
<button type="submit">
Search
</button>
</form>
</div>
);
}
}
export default Search;
im trying that value in the state Search component will pass to the api:
currently im getting state is undefined and the same when use props
const key = "***";
const url = `https://www.food2fork.com/api/search?key=${key}&q=${this.state.search}&page=2`;
export const recipesData = () =>{
const recipesData = fetch(url).then(res => res.json());
return recipesData;
Add method to your class or pass state to receiptsData function;
export const recipesData = (search) =>{
const url = `https://www.food2fork.com/api/search?key=${key}&q=${search}&page=2`;
const recipesData = fetch(url).then(res => res.json());
return recipesData;
or
class Search extends Component {
state = {
search: ""
};
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = (e) => {
e.preventDefault();
const url = `https://www.food2fork.com/api/search?key=${key}&q=${this.state.search}&page=2`;
fetch(url)
.then(res => res.json())
.then(res => {
console.log(res);
})
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="search"
value={this.state.search}
onChange={this.handleChange}
/>
<button type="submit">
Search
</button>
</form>
</div>
);
}
}
export default Search;
Here is a working example using React Hook:
const Search = () => {
const [value, setValue] = React.useState('');
const fetchRecipesData = url => {
return fetch(url).then(res => res.json());
};
const handleChange = e => {
setValue(e.target.value);
};
const handleSubmit = e => {
const key = 'YOUR_API_KEY';
const url = `https://www.food2fork.com/api/search?key=${key}&q=${value}&page=2`;
console.log(fetchRecipesData(url));
e.preventDefault();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type='text'
value={value}
onChange={handleChange}
/>
<button type='submit'>Search</button>
</form>
</div>
);
};
ReactDOM.render(<Search />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>