I am trying to update the context through the "state" variable here a then render it in another component. The rendering works fine, but not the first time of submitting the form here, only the second time, third time etc. Any idea how to solve this? I tried to move dispatchDataState({ type: "ADD_DATA", payload: state }) into useEffect but then I cannot remove the entered value in the inputs after submitting it. Any idea?
import React, { useContext, useState, useEffect } from "react";
import DataContext from "../store/data-context";
function Form() {
const [name, setName] = useState("");
const [secName, setSecName] = useState("");
const [tel, setTel] = useState("");
const [note, setNote] = useState("");
const [state, setState] = useState({
name: "",
secName: "",
tel: "",
note: "",
});
const { dispatchDataState } = useContext(DataContext);
const handleSubmit = (e) => {
e.preventDefault();
console.log(name);
setState({
name: name,
secName: secName,
tel: tel,
note: note,
});
dispatchDataState({ type: "ADD_DATA", payload: state });
console.log(state);
};
return (
<div>
<form onSubmit={handleSubmit}>
<label>
Jméno
<input
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<label>
Příjmení
<input
type="text"
required
value={secName}
onChange={(e) => setSecName(e.target.value)}
/>
</label>
<label>
Telefonní číslo
<input
type="text"
required
value={tel}
onChange={(e) => setTel(e.target.value)}
/>
</label>
<label>
Poznámka
<input
type="text"
value={note}
onChange={(e) => setNote(e.target.value)}
/>
</label>
<input type="submit" value="Odeslat" />
</form>
</div>
);
}
export default Form;
Part of the problem is: useState set method not reflecting change immediately And you are highly encouraged to read and understand that.
But the root of the problem here seems to be that you are also duplicating state. Storing the same values in multiple state variables and trying to keep them synchronized manually. Why?
Just keep the state values you want:
const [name, setName] = useState("");
const [secName, setSecName] = useState("");
const [tel, setTel] = useState("");
const [note, setNote] = useState("");
And then dispatch the context using those values:
const handleSubmit = (e) => {
e.preventDefault();
const state = {
name,
secName,
tel,
note,
};
dispatchDataState({ type: "ADD_DATA", payload: state });
console.log(state);
};
Or, if you want the "state" to be an object:
const [state, setState] = useState({
name: "",
secName: "",
tel: "",
note: ""
});
And dispatch that:
const handleSubmit = (e) => {
e.preventDefault();
dispatchDataState({ type: "ADD_DATA", payload: state });
console.log(state);
};
Of course in that case you'd also have to update your change handlers. For example:
<input
type="text"
required
value={state.name}
onChange={(e) => setState({ ... state, name: e.target.value })}
/>
And so on for your other input fields.
But overall the point is that "state" doesn't replace all possible variables in React. It's just there for, well, storing state. You can create new variables like anywhere else in JavaScript in order to re-structure the data from state for other purposes.
Related
Here's an example of a component I have in my react project:
import {useState, useEffect, useRef} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { createWorker } from '../../actions/worker';
import '../components.scss';
const New = () => {
const [worker, setWorker] = useState(
{
name: "",
email: "",
confirmation_email: "",
admin: false
}
);
const [checked, setChecked] = useState(false); //Determines if admin checkbox is checked or not
const [successMessage, setSuccessMessage] = useState(null); //If there's a success in the uploading process, we simply will put a message at the bottom.
const dispatch = useDispatch();
const errors = useSelector((state) => state.errors.error);
const selectedWorker = useSelector((state) => state.workers.current_worker); //We will be using this to determine if the user has a right to access this page
const renderedAlreadyRef = useRef(false); //Let's us know if we've rendered it already or not
useEffect(() => {
//We'll be using this to see if allWorkers.workers has been updated. We also use the ref renderedAlreadyRef to ensure it only runs after rendering
if (renderedAlreadyRef.current === true && Object.keys(errors).length === 0){
setSuccessMessage("Worker created successfully");
}
}, [errors])
const handleSubmit = (e) => {
//Handles submitting the form
e.preventDefault();
dispatch(createWorker(worker));
renderedAlreadyRef.current = true;
}
const handleChange = (e) => {
const newKey = e.target.id;
const newValue = e.target.value
if (newKey === "admin"){
setChecked(!checked)
setWorker(oldState => ({...oldState, "admin": !checked}))
}
else{
setWorker(oldState => ({ ...oldState, [newKey]: newValue}));
}
}
if (Object.keys(selectedWorker).length !== 0){
if (selectedWorker.admin === 1){
return(
<>
<form id="worker_form" onSubmit={e => handleSubmit(e)}>
<label>
Worker Name:
<input type="text" defaultValue={worker.name} id="name" onChange={e => handleChange(e)}></input>
</label>
<label>
Worker Email:
<input type="text" defaultValue={worker.email} id="email" onChange={e => handleChange(e)}></input>
</label>
<label>
Confirmation Email:
<input type="text" defaultValue={worker.confirmation_email} id="confirmation_email" onChange={e => handleChange(e)}></input>
</label>
<label>
Are they an admin?: <input type="checkbox" checked={checked} id="admin" onChange={e => handleChange(e)} />
</label>
<button type="submit" onClick={e => handleSubmit(e)} className="submit_new_button">Submit</button>
</form>
<h3 className='new_messages'>{successMessage}</h3>
</>
)
}
else{
return(
<div id="Forbidden">
<h1>Error 403 - Forbidden</h1>
<h2>You do not have access to this page</h2>
</div>
)
}
}
else{
return(<h1>Loading...</h1>)
}
}
export default New;
Up until yesterday, this was working flawlessly, but as of yesterday, suddenly, the things that are conditionally rendered no longer work. They just load forever... That is unless I change something in the file (or even just add a bit of whitespace) and then save that file. Suddenly, the thing that wasn't rendering does (in the case of this component, it's the successMessage that doesn't change). I have no idea what could be causing this as I changed a bunch of things yesterday. If you have any idea please let me know as this is confounding me.
I figured it out. Turns out that using refs was the reason this was happening. I'm assuming that based on the way refs work, it didn't re-render the page after they changed (which I guess makes sense since they don't trigger useEffects either).
I'm new to react and I've tried to fetch data using "setData" and then using the state variable "data" to set the values of title , desc and author but I'm getting them as undefined. After setting the initial states to empty I've set the values of the input fields to what I had got from the "data" state and when I submit the form only the fields in which the changes are made were updated the rest of the fields had "undefined" values i.e when I submitted the form to update it looked like this {title:undefined,desc:undefined,author:"person1"}, I'm unable to set the initial state values to input field values.
EditBlog.js
import React, { useState, useEffect } from "react"
import { useParams, useHistory } from "react-router-dom"
const EditBlog = () => {
const { id } = useParams()
useEffect(() => {
fetch('http://localhost:8000/values/' + id)
.then((res) => {
return res.json()
})
.then((data) => {
setData(data)
})
}, [id])
const [data, setData] = useState({})
const [title, setEditTitle] = useState()
const [desc, setEditDesc] = useState()
const [author, setEditAuthor] = useState()
const history = useHistory()
const updateHandler = (e) => {
e.preventDefault()
const updatedBlog = { title, desc, author }
fetch('http://localhost:8000/values/' + id, {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify(updatedBlog)
})
.then(() => {
console.log(updatedBlog)
history.push("/")
})
}
return (
<React.Fragment>
<div>
<h1>Edit Blog</h1>
<form onSubmit={updateHandler} className="new-blog">
<label>Blog Title</label>
<div className="input-box">
<input
onChange={(e) => setEditTitle(e.target.value)}
type="text"
defaultValue={data.title}
className="inputField"
/>
</div>
<label>Blog Body</label>
<div className="input-box">
<textarea
onChange={(e) => setEditDesc(e.target.value)}
cols="64"
defaultValue={data.desc}
/>
</div>
<label>Blog Author</label>
<div className="input-box">
<input
onChange={(e) => setEditAuthor(e.target.value)}
className="inputField"
type="text"
defaultValue={data.author}
/>
</div>
<button>Save</button>
</form>
</div>
</React.Fragment>
)
}
export default EditBlog
You're confusing yourself by trying to keep two copies of the same data in state. Reduce the state to just the one object you're editing (and give it initial values for the inputs, so nothing is undefined):
const [data, setData] = useState({
title: '',
desc: '',
author: ''
});
Have your inputs use and update the values in that state object:
<input
onChange={(e) => setData({...data, title: e.target.value})}
type="text"
value={data.title}
className="inputField"
/>
Note the use of value={data.title} so the input always shows the current value, and the new onChange which updates the same state being used and just changes the one value in that state object.
Then when you post the form, simply post the current state instead of building a new object from separately tracked values:
body: JSON.stringify(data)
In short, you don't need or want these:
const [title, setEditTitle] = useState()
const [desc, setEditDesc] = useState()
const [author, setEditAuthor] = useState()
Because you already have your data in data.
In useEffect add this code in .then block after setData(data):
setEditTitle(data.title)
setEditDesc(data.description)
setEditAuthor(data.author)
And replace defaultValue={data.title} with value={title} and the other two in the same way.
Note: I would say that The best approach is to avoid using states for your fields as you are already saving the state in useData. You should modify your code to avoid having three different state hooks.
I am rebuilding this component.
https://github.com/ayush221b/MarioPlan-react-redux-firebase-app/blob/master/src/Components/projects/CreateProject.js
https://github.com/ayush221b/MarioPlan-react-redux-firebase-app/blob/master/src/store/actions/projectActions.js
however, I don't know how to rewrite mapStateToProps and mapDispatchToProps
The error says
FirebaseError: Function addDoc() called with invalid data. Document fields must not be empty (found in field `` in document projects/5to35LFKROA5aKMXpjqy)
The project seems not be dispatched??
import {Component ,useState} from 'react'
import {connect} from 'react-redux'
import {createProject} from '../../store/actions/projectActions'
import { useForm } from "react-hook-form";
import { Redirect } from 'react-router-dom'
const CreateProject = (props) => {
const [state, setState] = useState({
title: "",
content: ""
});
const handleChange = event => {
setState({ ...state, [event.target.name]: event.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(state);
props.createProject(state);
props.history.push('/');
}
const { auth } = props;
return (
<div className="container">
<form className="white" onSubmit={handleSubmit}>
<h5 className="grey-text text-darken-3">Create a New Project</h5>
<div className="input-field">
<input type="text" id='title' onChange={handleChange} />
<label htmlFor="title">Project Title</label>
</div>
<div className="input-field">
<textarea id="content" className="materialize-textarea" onChange={handleChange}></textarea>
<label htmlFor="content">Project Content</label>
</div>
<div className="input-field">
<button className="btn pink lighten-1">Create</button>
</div>
</form>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
console.log("a"+dispatch);
return {
createProject: (project) => dispatch(createProject(project))
}
}
const mapStateToProps = (state) =>{
return{
auth: state.firebase.auth
}
}
export default connect(mapStateToProps,mapDispatchToProps)(CreateProject)
in the functional component, you can use hooks like "useSelector" to get the store states
const firebase = useSelector(state => state.firebase)
and "useDispatch" to trigger an action
const dispatch = useDispatch()
<button onClick={() => dispatch({ type: 'GET_DATA' })} >Click me</button>
don't forget to import
import { useSelector, useDispatch } from 'react-redux'
Problem: Missing name Property on Inputs
FirebaseError: Function addDoc() called with invalid data. Document fields must not be empty (found in field `` in document projects/5to35LFKROA5aKMXpjqy)
This error doesn't have anything to do with mapStateToProps. You are failing this test by passing a an object with an empty key.
{
title: "Some Title",
content: "some content",
'': "some value"
}
So where does that empty key come from? Well you are setting values in the state with a dynamic key based on the event.target.name.
const handleChange = (event) => {
setState({
...state,
[event.target.name]: event.target.value
});
};
When you change the input or the textarea, what is event.target.name? Take a look at your code.
<input type="text" id="title" onChange={handleChange} />
There is no name property!
You must either:
A) Add a name to each of the inputs that corresponds to the property which you want to update in the state.
<input type="text" id="title" name="title" onChange={handleChange} />
or B) Change your setState to use event.target.id, which is already set.
const handleChange = (event) => {
setState({
...state,
[event.target.id]: event.target.value
});
};
I recommend B) as it looks like that's what you were doing before.
Redux Hooks
Integrating with the redux hooks is very simple. Easier than dealing with connect, in my opinion.
Access auth from a selector.
const auth = useSelector((state) => state.firebase.auth);
Call useDispatch add the top-level of your component to access dispatch.
const dispatch = useDispatch();
In your handleSubmit, call dispatch with the results of your action creator.
dispatch(createProject(state));
Complete Code
const CreateProject = (props) => {
const auth = useSelector((state) => state.firebase.auth);
const dispatch = useDispatch();
const [state, setState] = useState({
title: "",
content: ""
});
const handleChange = (event) => {
setState({ ...state, [event.target.id]: event.target.value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(state);
dispatch(createProject(state));
props.history.push("/");
};
return (
<div className="container">
<form className="white" onSubmit={handleSubmit}>
<h5 className="grey-text text-darken-3">Create a New Project</h5>
<div className="input-field">
<input type="text" id="title" onChange={handleChange} />
<label htmlFor="title">Project Title</label>
</div>
<div className="input-field">
<textarea
id="content"
className="materialize-textarea"
onChange={handleChange}
/>
<label htmlFor="content">Project Content</label>
</div>
<div className="input-field">
<button className="btn pink lighten-1">Create</button>
</div>
</form>
</div>
);
};
I'm creating a register form in react with validation. The values i'm asking is Username, Email, password + (controll password). Everything about the form works, validations, Errors and if you click sign up you go to a new page. Now i want to extract the values to a my MySql database. I succeed in putting stuff in my database so the link works but i can't get the values of what i typed in the form.
I have tried
onChange={(e) => {
setUsernameReg(e.target.value);
}}
(see commented item)
But when i tried this I couldn't fill anything in Username. The code for the other inputs (email, password) is the same apart from the names.
So in short I want to get the value what you typed in a textbox to my database.
Code: FormSignup.js
import React, { useEffect, useState } from 'react';
import Axios from 'axios';
import validate from './validateInfo';
import useForm from './useForm';
import './Form.css';
const FormSignup = ({ submitForm }) => {
const { handleChange, handleSubmit, values, errors } = useForm(
submitForm,
validate
);
const [usernameReg, setUsernameReg] = useState("");
const [emailReg, setEmailReg] = useState("");
const [passwordReg, setPasswordReg] = useState("");
Axios.defaults.withCredentials = true;
const register = () => {
Axios.post("http://localhost:3001/register", {
username: usernameReg,
password: passwordReg,
email: emailReg,
}).then((response) => {
console.log(response);
});
};
return (
<div className='form-content-right'>
<form onSubmit={handleSubmit} className='form' noValidate>
<h1>
Get started with us today! Create your account by filling out the
information below.
</h1>
<div className='form-inputs'>
<label className='form-label'>Username</label>
<input
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
value={values.username}
onChange={handleChange}
/*
//onChange={(e) => {
// setUsernameReg(e.target.value);
//}}
*/
/>
Code UseForm.js
import { useState, useEffect } from 'react';
import Axios from 'axios';
const useForm = (callback, validate) => {
const [values, setValues] = useState({
username: '',
email: '',
password: '',
password2: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [usernameReg, setUsernameReg] = useState("");
const [emailReg, setEmailReg] = useState("");
const [passwordReg, setPasswordReg] = useState("");
Axios.defaults.withCredentials = true;
const register = () => {
Axios.post("http://localhost:3001/register", {
username: usernameReg,
password: passwordReg,
email: emailReg,
}).then((response) => {
console.log(response);
});
};
const handleChange = e => {
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
};
const handleSubmit = e => {
e.preventDefault();
setErrors(validate(values));
setIsSubmitting(true);
};
useEffect(
() => {
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
}
},
[errors]
);
return { handleChange, handleSubmit, values, errors };
};
export default useForm;
The code is from https://www.youtube.com/watch?v=KGFG-yQD7Dw&t and https://www.youtube.com/watch?v=W-sZo6Gtx_E&t
By using the value prop of the input, you turn it into a controled input element and thus need to update its value via a state variable. So this:
<input
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
value={values.username}
onChange={handleChange}
/*
//onChange={(e) => {
// setUsernameReg(e.target.value);
//}}
*/
/>
Should just be this:
<input
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
value={usernameReg}
onChange={e => setUsernameReg(e.target.value)}
/>
Note that this only answers this part:
I succeed in putting stuff in my database so the link works but i can't get the values of what i typed in the form
So this is how you can access those values. I can't guide you on how to get those values all the way to your DB as there is a longer distance they have to travel and I don't know what else could be in the way.
You should also look into useRef(), which will give you access to those input fields without updating your state on every change of the input and thus re-rendering your form over and over.
You can do something like this:
...
const regInput = React.useRef();
...
...
<input
ref={regInput}
className='form-input'
type='text'
name='username'
placeholder='Enter your username'
Then when you're ready to submit, just access the value of the username input like so:
...
const v = regInput.current.value;
...
I'm btrying to save an array of objects in local storage, each time a user clicks a button, i add the username and email fron input fields
but it keeps updating the local storage instead of appending new object to the array
Below is my code
const app = () => {
const [allusers,setAllusers] = useState([JSON.parse(localStorage.getItem('users')) || '']);
const [id,setId] = useState(0);
const [newuser,setNewuser] = useState({
'id':id
'name':'David',
'email':'david#gmail.com'
})
const handleChange = () =>{
setNewuser({...newuser,[e.target.name] : e.target.value});
}
const add = ()=>{
setAllusers([newuser])
localStorage.setItem('users',JSON.stringify(allusers))
setID(id+1); // increase id by 1
}
return(
<div>
<form>
<input type="text" name="user" onChange={handleChange}>
<input type="text" name="email" onChange={handleChange}>
<button onclick={()=>save}>Save</button>
</form>
</div>
)
}
export default app;
There were a lot of syntactical errors and use of functions like save which was never declared and still used.
I rewrote the whole example and made it a bit modular so that you can comprehend it better.
Here is the working example:
Final Output:
Full Source code:
import React, { useState, useEffect } from "react";
import "./style.css";
const App = () => {
const [allusers, setAllusers] = useState([]);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const handleName = e => {
setName(e.target.value);
};
const handleEmail = e => {
setEmail(e.target.value);
};
const save = e => {
e.preventDefault();
let newUsers = {
id: Math.floor(Math.random() * 100000),
name: name,
email: email
};
localStorage.setItem("users", JSON.stringify([...allusers, newUsers]));
setAllusers(allusers.concat(newUsers));
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
};
useEffect(() => {
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
if (localStorage.getItem("users")) {
setAllusers(JSON.parse(localStorage.getItem("users")));
}
}, []);
return (
<div>
<form>
<input type="text" name="user" onChange={handleName} />
<input type="text" name="email" onChange={handleEmail} />
<button onClick={save}>Save</button>
<p>{JSON.stringify(allusers)}</p>
</form>
</div>
);
};
export default App;
As You inquired in the comment section, here is how you can implement the Update functionality:
Final Output:
Full source code:
import React, { useState, useEffect } from "react";
import "./style.css";
const App = () => {
const [allusers, setAllusers] = useState([]);
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [id, setId] = useState(null);
const handleName = e => {
setName(e.target.value);
};
const handleEmail = e => {
setEmail(e.target.value);
};
const save = e => {
e.preventDefault();
let newUsers = {
id: Math.floor(Math.random() * 100000),
name: name,
email: email
};
localStorage.setItem("users", JSON.stringify([...allusers, newUsers]));
setAllusers(allusers.concat(newUsers));
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
};
const setForUpdate = user => {
setName(user.name);
setEmail(user.email);
setId(user.id);
};
const update = e => {
e.preventDefault();
let modifiedData = allusers.map(user => {
if (user.id === id) {
return { ...user, name: name, email: email };
}
return user;
});
setAllusers(modifiedData);
localStorage.setItem("users", JSON.stringify(modifiedData));
setId(null);
};
useEffect(() => {
console.log("Localstorage:", JSON.parse(localStorage.getItem("users")));
if (localStorage.getItem("users")) {
setAllusers(JSON.parse(localStorage.getItem("users")));
}
}, []);
return (
<div>
<form>
<input value={name} type="text" name="user" onChange={handleName} />
<input value={email} type="text" name="email" onChange={handleEmail} />
<button disabled={!(id == null)} onClick={save}>
Save
</button>
<button disabled={id == null} onClick={update}>
Update
</button>
</form>
{allusers &&
allusers.map(user => (
<div className="userInfo">
<p>{user.name}</p>
<p>{user.email}</p>
<button onClick={() => setForUpdate(user)}>
select for update
</button>
</div>
))}
</div>
);
};
export default App;
You can find the working example here: Stackblitz
You are trying to save allusers to the localStorage right after setAllUsers() but setState is asynchronous. The value does not have to be updated on the next line. You can read more about it at reactjs.org, Why is setState giving me the wrong value?.
I would recommend to use useEffect.
const add=()=> {
setAllusers([... allusers ,newuser])
}
useEffect(()=>{
// this is called only if the variable `allusers` changes
// because I've specified it in second argument of useEffect
localStorage.setItem('users',JSON.stringify(allusers))
}, [allusers]);
()=>handleChange is a function that takes no arguments and returns the handleChange function. You probably want () => handleChange(), which would take no arguments and INVOKE handleChange.
you are adding only one new user while clicking on add button. You need to copy previous data also when setting all users.
Second thing setting state is async and hence your localStorage and allusers may have different value and to avoid this one you need to use useEffect to set the value.
const add = ()=>{
setAllusers([...allusers ,newuser])
setID(id+1); // increase id by 1
}
useEffect(() => {
localStorage.setItem('users',JSON.stringify(allusers))
},[allusers])