Apologies in advance for not wording my question as well as it could have been.
I followed a MERN tutorial that was all done using class based React components. I am trying to convert it over to functional components with hooks.
I am having trouble saving the users/usernames from the initial GET request, and then subsequently mapping them out onto a html select form so that the new form submit can be attributed to one of the existing users.
Here is what the tutorial code looks like that functions as intended:
import React, { Component } from 'react';
import axios from 'axios';
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
export default class CreateRide extends Component {
constructor(props) {
super(props);
this.onChangeUsername = this.onChangeUsername.bind(this);
this.onChangeDescription = this.onChangeDescription.bind(this);
this.onChangeDuration = this.onChangeDuration.bind(this);
this.onChangeDistance = this.onChangeDistance.bind(this);
this.onChangeDate = this.onChangeDate.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
username: '',
description: '',
duration: 0,
distance: 0,
date: new Date(),
users: []
}
}
componentDidMount() {
axios.get('http://localhost:5000/users/')
.then(response => {
if (response.data.length > 0) {
this.setState({
users: response.data.map(user => user.username),
username: response.data[0].username
});
}
})
.catch((error) => {
console.log(error);
})
}
onChangeUsername(e) {
this.setState({
username: e.target.value
});
}
onChangeDescription(e) {
this.setState({
description: e.target.value
});
}
onChangeDuration(e) {
this.setState({
duration: e.target.value
});
}
onChangeDistance(e) {
this.setState({
distance: e.target.value
});
}
onChangeDate(date) {
this.setState({
date: date
});
}
onSubmit(e) {
e.preventDefault();
const ride = {
username: this.state.username,
description: this.state.description,
duration: this.state.duration,
distance: this.state.distance,
date: this.state.date
};
console.log(ride);
axios.post('http://localhost:5000/rides/add', ride)
.then(res => console.log(res.data));
window.location = '/';
}
render() {
return (
<div>
<h3>Create New Ride Log</h3>
<form onSubmit={this.onSubmit}>
<div className="form-group">
<label>Username: </label>
<select ref="userInput"
required
className="form-control"
value={this.state.username}
onChange={this.onChangeUsername}>
{
this.state.users.map(function (user) {
return <option
key={user}
value={user}>{user}
</option>;
})
}
</select>
</div>
<div className="form-group">
<label>Description: </label>
<input type="text"
required
className="form-control"
value={this.state.description}
onChange={this.onChangeDescription}
/>
</div>
<div className="form-group">
<label>Duration (in minutes): </label>
<input
type="text"
className="form-control"
value={this.state.duration}
onChange={this.onChangeDuration}
/>
</div>
<div className="form-group">
<label>Distance (in miles): </label>
<input
type="text"
className="form-control"
value={this.state.distance}
onChange={this.onChangeDistance}
/>
</div>
<div className="form-group">
<label>Date: </label>
<div>
<DatePicker
selected={this.state.date}
onChange={this.onChangeDate}
/>
</div>
</div>
<div className="form-group">
<input type="submit" value="Create Ride Log" className="btn btn-primary" />
</div>
</form>
</div>
)
}
}
The code I am targeting is in the ComponentDidMount() {...} mapping the user and usernames to state, and then again later down below in the html render.
What I have now as a functional component looks like this:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import DatePicker from 'react-datepicker';
import "react-datepicker/dist/react-datepicker.css";
const CreateRide = (props) => {
const [username, setUsername] = useState('');
const [description, setDescription] = useState('');
const [duration, setDuration] = useState(0);
const [distance, setDistance] = useState(0);
const [date, setDate] = useState(new Date());
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchData = async () => {
const response = await axios.get('http://localhost:5000/users/');
if (response.data.length > 0) {
setUsers({ users: response.data.map(user => user.username ) });
setUsername({ username: response.data[0].username });
}
}
fetchData();
}, []);
const onChangeUsername = (e) => {
setUsername(e.target.value);
}
const onChangeDescription = (e) => {
setDescription(e.target.value);
}
const onChangeDuration = (e) => {
setDuration(e.target.value);
}
const onChangeDistance = (e) => {
setDistance(e.target.value);
}
const onChangeDate = (date) => {
setDate(date);
}
const onSubmit = (e) => {
e.preventDefault();
const ride = {
username: username,
description: description,
duration: duration,
distance: distance,
date: date
};
console.log(ride);
axios.post('http://localhost:5000/rides/add', ride)
.then(res => console.log(res.data));
window.location = '/';
}
return (
<div>
<h3>Create New Ride Log</h3>
<form onSubmit={onSubmit}>
<div className="form-group">
<label>Username: </label>
<select
required
className="form-control"
value={username}
onChange={onChangeUsername}>
{
users.map(user => {
return <option key={user} value={user}>{user}</option>;
})
}
</select>
</div>
<div className="form-group">
<label>Description: </label>
<input type="text"
required
className="form-control"
value={description}
onChange={onChangeDescription}
/>
</div>
<div className="form-group">
<label>Duration (in minutes): </label>
<input
type="text"
className="form-control"
value={duration}
onChange={onChangeDuration}
/>
</div>
<div className="form-group">
<label>Distance (in miles): </label>
<input
type="text"
className="form-control"
value={distance}
onChange={onChangeDistance}
/>
</div>
<div className="form-group">
<label>Date: </label>
<div>
<DatePicker
selected={date}
onChange={onChangeDate}
/>
</div>
</div>
<div className="form-group">
<input type="submit" value="Create Ride Log" className="btn btn-primary" />
</div>
</form>
</div>
)
}
export default CreateRide;
Here is what a sample json response looks like when I run it through postman:
[
{
"_id": "5f39e38abfa99e273e299fd8",
"username": "Rudy",
"createdAt": "2020-08-17T01:55:22.797Z",
"updatedAt": "2020-08-17T01:55:22.797Z",
"__v": 0
},
{
"_id": "5f39e391bfa99e273e299fd9",
"username": "James",
"createdAt": "2020-08-17T01:55:29.727Z",
"updatedAt": "2020-08-17T01:55:29.727Z",
"__v": 0
},
{...}
]
Further on down in the HTML, I am struggling to conceptualize how the ref='userInput' is being used in the first form-group. Using a functional component creates an error of not allowing strings refs.
I know that this encompasses more than one question, but I'm pretty jammed up and anything would be appreciated.
Answering your questions in order:
1. User data: You're setting your user data in objects:
setUsers({ users: response.data.map(user => user.username ) });
setUsername({ username: response.data[0].username });
That should just be:
setUsers(response.data.map(user => user.username));
setUsername(response.data[0].username);
2. Refs: It doesn't look like you're using that ref in the original class-based code, you should be able to remove it. If you do need a ref to the select
element, you can use the useRef
hook, documentation here: https://reactjs.org/docs/hooks-reference.html#useref