I have built a travel journal however I ran into two big bugs, one of them where the id which is set to Math.random()*10000 and is expected to change on submit however it does not, another issue I have is where once I remove one journal entry, I am not able to add any more entries via submit.
I have tried adding the math.random in different places however it doesn't change, I have run out of ideas on how to tackle this issue, if you have any suggestions ,any help is appreciated.
import React, { useState } from "react";
import Card from "./Card";
import data from "./data";
function Entry(props) {
const [entry, setEntry] = useState([
{
title: "",
location: "",
googleMapsUrl: "",
startDate: "",
endDate: "",
description: "",
imageUrl: "",
id: Math.random() * 100000000,
},
]);
function handleChange(e) {
setEntry((prevState) => {
return {
...prevState,
[e.target.name]: e.target.value,
};
});
}
// const newData = [...data];
function handleSubmit(e) {
e.preventDefault();
setEntry((prevState) => {
return {
...prevState,
};
});
data.unshift(entry);
}
return (
<div>
<form className="entry-form">
<h1 className="entry-title">Add another Travel Memory</h1>
<div className="journal-entry">
<input
className="entry-input"
type="text"
value={entry.location}
name="location"
placeholder="LOCATION"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="text"
name="title"
value={entry.title}
placeholder="LANDMARK"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="text"
name="googleMapsUrl"
value={entry.googleMapsUrl}
placeholder="GOOGLE MAPS LINK"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="date"
value={entry.startDate}
name="startDate"
onChange={handleChange}
required
/>
<input
className="entry-input"
type="date"
value={entry.endDate}
name="endDate"
onChange={handleChange}
required
/>
<textarea
className="entry-input"
placeholder="ADD YOUR STORY OR A FUN FACT FROM YOUR JOURNEY"
name="description"
value={entry.description}
onChange={handleChange}
required
/>
<input
className="entry-input"
type="text"
name="imageUrl"
value={entry.imageUrl}
placeholder="ADD A IMAGE LINK TO REMIND YOU OF YOUR TRAVEL"
onChange={handleChange}
/>
<button type="submit" onClick={handleSubmit} className="entry-btn">
add your travel memory
</button>
</div>
</form>
<Card data={data} />
</div>
);
}
export default Entry;
Math.random()*10000 and is expected to change on submit however it does not
Because no code was written to change it. Take a look at the state update in the submit handler:
setEntry((prevState) => {
return {
...prevState,
};
});
No values are changed. The new state is an exact copy of the previous state. Contrast this with the state update in the change handler for the input fields:
setEntry((prevState) => {
return {
...prevState,
[e.target.name]: e.target.value,
};
});
Notice how the new state is constructed from the previous state, and a given field is updated.
If you want to update the id field in the submit handler, update the id field in the submit handler:
setEntry((prevState) => {
return {
...prevState,
id: Math.random() * 100000000
};
});
Related
I have setup basic the GraphQL Playground and am able to query or add objects which in my case are cars. I am even able to query cars and display them on front end but I am having issues in terms of adding or updating the database via GraphQL by entering data in to a form. I am using the use state hook and as soon as I add a use state hook the page stops loading and I come up with a blank page as in it stops loading:
The code is below:
export default function CarForm() {
const [formState, setFormState] = useState({
name: '',
mileage: '',
dailyPrice: '',
monthlyPrice: '',
gas: '',
gearType: '',
thumbnailUrl: ''
})
const [carForm] = useMutation(ADD_NEW_CAR, {
variables: {
name: formState.name,
mileage: formState.mileage,
dailyPrice: formState.dailyPrice,
monthlyPrice: formState.monthlyPrice,
gas: formState.gas,
gearType: formState.gearType
}
});
return (
<div>
<h1>
Submit your car
</h1>
<Form>
<h5 >make and model of car :
<input className="field" type="text" value={formState.name}
onChange={(e) =>
setFormState({
...formState,
name: e.target.value
})
} />
</h5>
<h5>
Mileage
<input className="field" type="text" value={formState.mileage}
onChange={(e) =>
setFormState({
...formState,
mileage: e.target.value
})
} />
</h5>
<h5>gearType
<input className="field" type="text" value={formState.gearType}
onChange={(e) =>
setFormState({
...formState,
gearType: e.target.value
})
} />
</h5>
<h5>gas
<input className="field" type="text" value={formState.gas}
onChange={(e) =>
setFormState({
...formState,
gas: e.target.value
})
} />
</h5>
<h5>dailyPrice
<input className="field" type="text" value={formState.dailyPrice}
onChange={(e) =>
setFormState({
...formState,
dailyPrice: e.target.value
})
} />
</h5>
<h5>monthlyPrice
<input className="field" type="text" value={formState.monthlyPrice}
onChange={(e) =>
setFormState({
...formState,
monthlyPrice: e.target.value
})
} />
</h5>
<h5>thumbnailUrl
<input className="field " type="text" value={formState.thumbnailUrl}
onChange={(e) =>
setFormState({
...formState,
thumbnailUrl: e.target.value
})
} />
</h5>
</Form>
<Button onSubmit={(e) => {
e.preventDefault();
}}>
submit
</Button>
</div >
);
}```
I am able to use the mutation or query via the GraphQL playground but am not able to update the database via adding data to the form. it just returns a blank page. What am I doing wrong here? Is there an easier way to input data?
I wanna add the data of the cars via the form but it returns a blank page.
In the onSubmit callback you need to actually call the carForm function, passing the variables there. Just leave the useMutation with one param, and add the variables in the submit handler. Check out this pseudo-code:
const [carForm] = useMutation(ADD_NEW_CAR)
const submitHandler = async (e) => {
e.preventDefault();
await carForm({
variables: {...} // useState variables.
});
}
return <Form>
...
<Button onSubmit={submitHandler}>Submit</Button>
</Form>
I am creating an invoice generator for my personal project so I can learn and understand the MERN stack better. I currently have this as part of my state. I currently have my className linked to my CSS, as I learned that could be manipulated to use create the handleChange function, so I wasn't sure if I needed to completely change that. I have tried working with index, by mapping each object to it's index, but it's just been confusing.
state = {
...
itemValues: [{
description: "",
rate: "",
quantity: "",
amount: "",
tax: ""
}],
...
}
and this as my mapped value
itemValues.map((item, i) => {
let descId = `description-${i}`;
let rateId = `rate-${i}`;
let quanId = `quantity-${i}`;
let amonId = `amount-${i}`;
let taxId = `tax-${i}`;
return(
<div key={i} className="item-row">
<input
className="item item-description"
type="text"
id={descId}
name={descId}
placeholder="Item description"
data-id={descId}
onChange={this.handleChange('desc')}
/>
{/* */}
<input className="item item-rate"
type="text"
id={rateId}
name={rateId}
placeholder="Item rate"
data-id={rateId}
/>
{/* */}
<input className="item item-quantity"
id="item-quantity"
type="text"
placeholder="item quantity"
data-id={quanId}
/>
{/* */}
<input className="item item-amount"
id="item-amount"
type="text"
placeholder="item amount"
data-id={amonId}
/>
{/* */}
{this.state.taxation === "None" ? "" : <input class="item item-tax"
id="item-tax"
type="text"
placeholder="tax(%)"
data-id={taxId}
/>}
<input type='button' value='remove' onClick={this.removeClick}
/>
</div>
)
})
}
I'm not sure how to set my handleChange function, to allow dynamic inputs to be saved.
Since is a mapping into a global component like itemValuesComponent for example, you need more than one function or an index as well to set items values into the state.
You can try the follow:
const handleChange = (location, property) => (e) => {
this.setState(prevState => ({
...prevState,
itemValues: [...prevState.itemValues.slice(0, location),
{
...prevState.itemValues[location],
[property]: e.target.value,
}
, ...prevState.itemValues.slice(location)]
}))
}
and when you need it:
<input
...
onChange={this.handleChange(i,'description')}
/>
Please, let me know if this work.
Try to use React Hooks, I'm pretty sure that it will simplify your code.
I am using the Context API to add user details from a form to a global state. When I submit the form, the state is always "one step behind" - essentially, a double click is required to get the desired result.
Basic recreation of the code (have removed irrelevant bits and some imports):
import { UserProvider, UserContext } from "../../StateContext"
export default function SignUp() {
const user = useContext(UserContext)
const history = useHistory()
const handleSubmit = (e) => {
e.preventDefault()
user.setName(userDetails.name)
//logging out the user object here will result in the previous values shown
}
const [userDetails, setUserDetails] = useState({
name: null,
age: null,
})
return (
<>
<form onSubmit={handleSubmit}>
<div className="form-vertical-batch">
<FormControl>
<Input
type="text"
placeholder="Your Name"
required={true}
onChange={(e) =>
setUserDetails({ ...userDetails, name: e.target.value })
}
></Input>
<FormHelperText>
Put the name you're best known by online - either a nickname,
brand name, or your real name.
</FormHelperText>
</FormControl>
<FormControl>
<TextField
type="number"
inputProps={{ min: 18, max: 99 }}
onChange={(e) =>
setUserDetails({ ...userDetails, age: e.target.value })
}
/>
<FormHelperText>
You currently must be at least 18 years old to use the platform.
</FormHelperText>
</FormControl>
</div>
</div>
<input
id="formButton"
className="btn sign-up-button"
type="submit"
placeholder="Send message"
/>
</form>
</>
)
}
To clarify the issue here - if I submit with a name as "Reikon" and log our the user object, the first time it will return as null, and then the second time it will return "Reikon" as expected.
I am unable to type any input into my input field. I am using React, and have already set a handleChange and a handleSubmit function. The first two input fields, for 'name' and 'email', take input just fine. But for 'favoriteCity', it doesn't seem to work.
I am wondering if it is due to a MongoDB error that I am getting.
class UserPage extends Component {
state = {
user: [],
newUser: {
name: '',
email: '',
favoriteCity: ''
}
}
getAllUsers = () => {
axios.get('/api/users')
.then(res => {
this.setState({ user: res.data })
})
}
componentDidMount() {
this.getAllUsers()
}
handleChange = event => {
const newUser = { ...this.state.newUser };
newUser[event.target.name] = event.target.value;
this.setState({ newUser: newUser});
}
handleSubmit = event => {
event.preventDefault()
axios.post('/api/users', this.state.newUser)
.then(res => {
this.props.history.push(`/users/${res.data._id}`)
})
}
render() {
return (
<div>
{ /* This shows a list of All Users */ }
{this.state.user.map(user => (
<div key={user._id}>
<Link to={`/users/${user._id}`}>{user.name}</Link>
</div>
))}
<h1>New User Page</h1>
<form onSubmit={this.handleSubmit}>
<label>Name: </label>
<input
type="text"
name="name"
placeholder="Name?"
value={this.state.newUser.name}
onChange={this.handleChange}
/>
<label>Email: </label>
<input
type="text"
name="email"
placeholder="Email?"
value={this.state.newUser.email}
onChange={this.handleChange}
/>
<label>Favorite City: </label>
<input
type="text"
name="city"
placeholder="Favorite City?"
value={this.state.newUser.favoriteCity}
onChange={this.handleChange}
/>
<Button
type="submit"
value="Submit"
variant="contained"
color="primary"
>
Create User
</Button>
</form>
</div>
);
}
}
export default UserPage;
Please help.
Weird that email works fine, from what you posted your handleChange function is only updating the name on the newUser.
What you should see is what you type in all the inputs appear in the name input.
To fix this, you should probably have separate change handlers for each input:
handleNameChange
handleEmailChange
...
You should also consider storing name, email etc.. at the root of your state instead of nesting them in an object, that'll simplify the handler functions code.
Problem
I have a list of people. I want to:
Select a user to edit by clicking on their name.
Edit that user's information, so I can click the submit button and update the list.
If I click on a different name, I want to switch to that person's information without having to deliberately close the form first.
Everything works until #3. When I click on another person, the form, itself, does NOT update.
My Code
Update Component for the update form:
const UpdateForm = ({ updatePerson, personToUpdate, handleInputChange }) => {
let _name, _city, _age, _id;
const submit = (e) => {
e.preventDefault();
updatePerson({
name: _name.value,
city: _city.value,
age: _age.value,
_id: _id.value
});
};
return (
<div>
<form onSubmit={submit}>
<h3>Update Person</h3>
<label htmlFor="_id">Some Unique ID: </label>
<input type="text" name="_id" ref={input => _id = input} id="_id" defaultValue={personToUpdate._id} onChange={input => handleInputChange(personToUpdate)} required />
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" ref={input => _name = input} id="name" defaultValue={personToUpdate.name} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" ref={input => _city = input} id="city" defaultValue={personToUpdate.city} onChange={input => handleInputChange(personToUpdate)} />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" ref={input => _age = input} id="age" defaultValue={personToUpdate.age} onChange={input => handleInputChange(personToUpdate)} />
<br />
<input type="submit" value="Submit" />
</form>
</div>
);
};
export default UpdateForm;
Relevant parts of Person Component:
class Person extends Component {
nameClick() {
if (this.props.person._id !== this.props.personToUpdate._id) {
this.props.setForUpdate(this.props.person);
this.forceUpdate();
}
else {
this.props.toggleUpdatePersonPanel();
}
}
render() {
return (
<div>
<span onClick={this.nameClick}>
{this.props.person.name} ({this.props.person.age})
</span>
</div>
);
}
}
export default Person;
Relevant parts of PeopleList, which holds Persons:
class PeopleList extends Component {
render() {
return(
<div>
{this.props.people.map((person) => {
return <Person
key={person._id}
person={person}
updatePersonPanel={this.props.updatePersonPanel}
setForUpdate={this.props.setForUpdate}
personToUpdate={this.props.personToUpdate}
/>;
})}
</div>
);
}
} // end class
export default PeopleList;
Form Reducer, with just the relevant actions:
export default function formReducer(state = initialState.form, action) {
let filteredPeople;
switch (action.type) {
case TOGGLE_UPDATE_PANEL:
return Object.assign({}, state, { updatePersonPanel: false }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}});
case SET_FOR_UPDATE:
return Object.assign({}, state, { personToUpdate: action.person }, { updatePersonPanel: true });
case UPDATE_RECORD:
filteredPeople = state.people.filter((person) => {
return person._id === action.person._id ? false : true;
}); // end filter
return Object.assign({}, state, { people: [ ...filteredPeople, action.person] }, { personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
}}, { updatePersonPanel: false });
case HANDLE_INPUT_CHANGE:
return Object.assign({}, state, { personToUpdate: action.person });
default:
return state;
}
}
The relevant parts of my Initial State file:
form: {
people: [
{
_id: "adfpnu64",
name: "Johnny",
city: "Bobville",
age: 22
},
{
_id: "adf2pnu6",
name: "Renee",
city: "Juro",
age: 21
},
{
_id: "ad3fpnu",
name: "Lipstasch",
city: "Bobville",
age: 45
}
],
updatePersonPanel: false,
personToUpdate: {
_id: "",
name: "",
city: "",
age: ""
},
}
Attempts at a Solution( so far)
I have attempted to make the component a completely controlled component, by switching the form attribute to value instead of defaultValue. When I do this, the names switch just fine, but the form becomes unchangeable and useless.
My Questions
Almost all of the solutions to these kind of issues either recommend using redux-form or supply two-way binding solutions that work fine in React without reduce. I want to know how to do this with Redux without using redux-form or anything extra if possible. Is there a way to resolve this without touching lifecycle methods?
Conclusion (For now)
Well, for now, I settled for making my form uncontrolled and used some classic Js DOM methods and a lifecycle method to control the form. For whatever reason, once I employed some of the answer suggestions my browser ate up my CPU and crashed, presumably because there was some kind of infinite loop. If anyone has some further recommendations, I'd really appreciate it. For now I settle for this:
class UpdateForm extends Component {
constructor(props){
super(props);
this.submit = this.submit.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.personToUpdate._id !== this.props.personToUpdate._id) {
document.getElementById("name").value = nextProps.personToUpdate.name;
document.getElementById("age").value = nextProps.personToUpdate.age;
document.getElementById("city").value = nextProps.personToUpdate.city;
}
}
submit(e) {
e.preventDefault();
this.props.updatePerson({
name: document.getElementById("name").value,
city: document.getElementById("city").value,
age: document.getElementById("age").value,
_id: this.props.personToUpdate._id
});
}
render() {
return (
<div>
<form onSubmit={this.submit}>
<h3>Update Person</h3>
Unique ID: {this.props.personToUpdate._id}
<br />
<label htmlFor="name">Name: </label>
<input type="text" name="name" id="name" ref="name" defaultValue={this.props.personToUpdate.name} required />
<br />
<label htmlFor="city">City: </label>
<input type="text" name="city" id="city" ref="city" defaultValue={this.props.personToUpdate.city} required />
<br />
<label htmlFor="age">Age: </label>
<input type="text" name="age" id="age" ref="age" defaultValue={this.props.personToUpdate.age} required />
<br />
<input type="submit" value="Update" />
</form>
</div>
);
}
} // end class
export default UpdateForm;
I'll be soon exploring redux-form because it is evident that forms as inputs and outputs are a wonky business. For now, my little app works.
Yes there is and you are on the right path. The way is to use value instead of defaultValue but you have to read the value from a state and then use the onChange handler to modify the state.
Something like
this.state = {inputText:''}
Then in the input field
<input value={this.state.inputText} onChange={this.handleChange}/>
And the handleChange function will be
handleChange(event){
this.setState({inputText:event.target.value})
}
Remember to bind the handleChange event in the constructor so you can pass it as this.handleChange in the input field's onChange prop.
Something like this
this.handleChange = this.handleChange.bind(this)
https://facebook.github.io/react/docs/forms.html - Here are the official docs regarding it
Also if you want to do it in redux the same sort of logic applies where this will be the input field
<input value={this.props.inputText} onChange={this.props.handleChange}/>
where inputText and handleChange are redux state and action respectively passed to the component via props
For your case I guess it has to be something like where you are 'reading' values from the people array and the action bound to the onChange modifies that value in the people array in the state.
<--EDIT-->
How it can be done for the case in point. Pass the people in the redux state as a people prop to the component. Pass an action changePeople(newPeople) to the component as a prop which takes an argument newPeople and changes the people in the redux state to have the value newPeople. Since people is nested in form you'll have to do some Object.assign etc to modify the state.
Now in the component using the people props populate the checkboxes using a map function. The map function takes a second parameter index so for each checkbox have a function which sets the local state variable currentPerson to the value of the index
this.props.people.map((person,index) =>
return <Checkbox onClick={()=>this.setState(currentPerson:index)}/>
)
So everytime you click on a checkbox the currentPerson points to the corresponding index.
Now the input fields can be
<input value={this.props.people[this.state.currentPerson].name} onChange={this.handleChange.bind(this,'name')}/>
This is for the 'name' input field. It reads from the currentPerson index of the people array which has been passed down as a prop.
This is how the handleChange will be like
handleChange(property,event){
const newPeople = [
...this.props.people.slice(0, this.state.currentPerson),
Object.assign({}, this.props.people[this.state.currentPerson], {
[property]: event.target.value
}),
...this.props.people.slice(this.state.currentPerson + 1)
]
this.props.changePeople(newPeople)
}
The handleChange takes a property (so you don't have to write separate handlers for each input field). The newPeople basically modifies the element at current index this.state.currentPerson in the people passed from props (ES6 syntax being used here. If you have doubts do ask). Then it is dispatched using the changePeople action which was also passed as props.