Cannot read property 'map' of undefined - Cant map data from axios request - javascript

In react, I am trying to fetch data from an API call which I have created. The console prints out the correct response which is list of names of users, but the mapping is not working for me. Any advice would be beneficial.
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import axios from "axios";
const CreateProject = () => {
const [loading, setLoading] = React.useState(true);
const { handleSubmit, register, errors } = useForm();
const [value, setValue] = React.useState("Choose option");
const [items, setItems] = React.useState([
{ label: "Loading", value: "Loading" }
]);
const onSubmit = values => {
console.log(values);
};
useEffect(() => {
// initialise as false
let unmounted = false;
async function getUsers() {
const res = await axios.get(
"http://localhost:3000/api/findUser/findUser"
// Api for finding user
);
const body = await res.data;
console.log(res.data);
// check state is still false beofre state is set
if (!unmounted) {
setItems(
body.res.map(({ name }) => ({
label: name,
value: name
}))
);
setLoading(false);
// setLoading allows change to option - data displayed
}
}
getUsers();
return () => {
unmounted = true;
};
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
type="text"
placeholder="ProjectName"
name="ProjectName"
ref={register({ required: true, maxLength: 20 })}
/>
<input
type="text"
placeholder="Project Details"
name="Project Detail"
ref={register({ required: true, maxLength: 50 })}
/>
<select
disabled={loading}
value={value}
onChange={e => setValue(e.currentTarget.value)}
>
{items.map(({ label }) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
{errors.search && <p>No user found</p>}
<input type="submit" />
</form>
);
};
export default CreateProject;
The error I receive is seems to be around the body.res.map at setItems - "Uncaught (in promise) TypeError: Cannot read property 'map' of undefined"

You are actually console logging other variable than what you're mapping over.
If what you're logging is the right thing, your setItems() should be:
setItems(
res.data.map(({ name }) => ({
label: name,
value: name
}))
)

const body = await res.data; There is no reason to write await because res.data is not a promise.
Here's how it should be const body = res.data
And in this block you need to map only body
setItems(
body.map(({ name }) => ({
label: name,
value: name
}))
);

Use promise chaining instead of await to make things smoother
async function getUsers() {
axios.get("http://localhost:3000/api/findUser/findUser")
.then((res) => {
console.log(res.data);
if (!unmounted) {
setItems(
res.data.map(({
name
}) => ({
label: name,
value: name
}))
);
setLoading(false);
// setLoading allows change to option - data displayed
}
})
.catch((error) => console.log(error));
}

Related

React : Empty values on PUT for axios

I have a simple list that I get from an API using axios.
Every element is a modifiable input, with it own update button.
After changing the data of an input, and while performing PUT request, console.log(test); returns empty values.
I checked console.log(newList); which is the array of the list, and the changing data are indeed happening in the list, but it seems they can't be sent to the server.
Note : The API is just for testing, the PUT method may not work, but atleast the values in the console should be sent.
Note2 : I don't know how to place the id of an item of the list in the url so you may encounter an error. / You can try with 1,2 or 3 instead for testing.
https://codesandbox.io/s/quizzical-snowflake-dw1xr?file=/src/App.js:1809-1834
import React, { useState, useEffect } from "react";
import axios from "axios";
export default () => {
const [list, setList] = React.useState([]);
const [name, setName] = React.useState("");
const [description, setDescription] = React.useState("");
const [city, setCity] = React.useState("");
// Getting initial list from API
useEffect(() => {
axios
.get("https://6092374385ff5100172122c8.mockapi.io/api/test/users")
.then((response) => {
setList(response.data);
console.log(response);
})
.catch((err) => console.log(err));
}, []);
// onUpdate to update the data in the API
const onUpdate = (e) => {
e.preventDefault();
const test = {
name: name,
description: description,
city: city
};
console.log(test);
// axios request PUT data on API
axios
.put(
"https://6092374385ff5100172122c8.mockapi.io/api/test/users" + id,
test
)
.then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
// axios request GET to get the new modified list from the database, after the update
axios
.get("https://6092374385ff5100172122c8.mockapi.io/api/test/users")
.then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
};
// Handler for changing values of each input
function handleChangeUpdate(id, event) {
const { name, value } = event.target;
const newList = list.map((item) => {
if (item.id === id) {
const updatedItem = {
...item,
[name]: value
};
return updatedItem;
}
return item;
});
setList(newList);
console.log(newList);
}
return (
<div>
<ul>
<div>
{list.map((item) => (
<li key={item.id}>
<input
className="form-control"
name="name"
onChange={(event) => handleChangeUpdate(item.id, event)}
defaultValue={item.name}
></input>
<input
className="form-control"
name="description"
onChange={(event) => handleChangeUpdate(item.id, event)}
defaultValue={item.description}
></input>
<input
className="form-control"
name="city"
onChange={(event) => handleChangeUpdate(item.id, event)}
defaultValue={item.city}
></input>
<button onClick={onUpdate}>Update</button>
</li>
))}
</div>
</ul>
</div>
);
};
It's because you never set the values of the props. That is why they never change from their initial values. You just update the list prop in handleChangeUpdate. There are two steps you need to take with the existing file structure:
Make handleChangeUpdate be able to differentiate between different props (city, description, etc.). For example, by passing the prop's name.
Update the prop's value in the handleChangeUpdate.
To realize the first step, you can change the input tag like the following:
{/* attention to the first argument of handleChangeUpdate */}
<input
className="form-control"
name="name"
onChange={(event) => handleChangeUpdate("name", item.id, event)}
defaultValue={item.name}
></input>
Then, you need to adjust the handleChangeUpdate:
if (name === "name") {
setName(value);
} else if (name === "description") {
setDescription(value);
} else if (name === "city") {
setCity(value);
}
By the way, list is not a good name for a variable.
Alternatively
Without creating new parameters, you can also use only the event to set the props
// Handler for changing values of each input
function handleChangeUpdate(id, event) {
const { name, value } = event.target;
const newList = list.map((item) => {
if (item.id === id) {
const updatedItem = {
...item,
[name]: value
};
return updatedItem;
}
return item;
});
setList(newList);
console.log(newList);
if (name === "name") {
setName(value);
} else if (name === "description") {
setDescription(value);
} else if (name === "city") {
setCity(value);
}
}
I think you have 3 errors in the onUpdate function.
You are not passing the id of the item from the onClick event
Your put method should be change
You should not perform get request as soon as after the put request, because sometimes the backend will not updated yet.
You can update your code as below,
1.Pass the id of the item when the button is clicked.
<button onClick={onUpdate(item.id)}>Update</button>
Modify the put method, passing the id
axios
.put(
`https://6092374385ff5100172122c8.mockapi.io/api/test/users/${e}`,
test
).then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
3.Perform the get request after the response of the put request
const onUpdate = (e) => {
const test = {
name: name,
description: description,
city: city
};
console.log(test);
// axios request PUT data on API
axios
.put(
`https://6092374385ff5100172122c8.mockapi.io/api/test/users/${e}`,
test
)
.then((res) => {
console.log(res);
// axios request GET to get the new modified list from the database, after the update
axios
.get("https://6092374385ff5100172122c8.mockapi.io/api/test/users")
.then((res) => {
alert("success");
console.log(res);
})
.catch((error) => {
console.log(error);
});
})
.catch((error) => {
console.log(error);
});
};

How to have changeable values in input React JS?

I was trying to set my value in the input value! but after that, I cannot write anything in the input field! I wanted to set values from the back end in value!
We are writing an admin channel to edit the article for that we need already existing article values to edit the article! What am I doing wrong! or Maybe you can suggest a better way to edit the article in the admin channel!
here is the code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { useParams } from 'react-router';
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
const [changedValues, setChangedValues] = useState('');
console.log('values', editValues);
console.log('changed', changedValues);
const params = useParams();
console.log(params);
const resultsId = params.id;
console.log('string', resultsId);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem('token') || ''
);
const setTokens = (data) => {
localStorage.setItem('token', JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
const res = await axios.get(
`${process.env.REACT_APP_API_URL}/article/${resultsId}`
);
setEditValues(res.data);
} catch (err) {}
};
fetchData();
}, [resultsId]);
const inputValue = editValues;
const userToken = props.token;
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ''}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<input
// ref={editValues.shortDesc}
value={editValues.shortDesc}
onChange={(input) => setChangedValues(input.target.value)}
type='text'
/>
<button type='submit'>send</button>
</form>
</div>
);
};
export default EditArticle;
your onChange handler is updating a different state property than what is being used as the value on the input (editValues vs changedValues).
Also you can pass a defaultValue to input that will get used as the default value only.
See more here https://reactjs.org/docs/uncontrolled-components.html
you can use just do it just using editValues. try this:
I just reproduced it without the api call to run the code.
import React, { useState, useEffect } from "react";
const EditArticle = (props) => {
const [editValues, setEditValues] = useState([]);
console.log("values", editValues);
const [authTokens, setAuthTokens] = useState(
localStorage.getItem("token") || ""
);
const setTokens = (data) => {
localStorage.setItem("token", JSON.stringify(data));
setAuthTokens(data);
// setToken(data['dataValues']['token']);
};
useEffect(() => {
const fetchData = async () => {
try {
//here get the data from api and setstate
setEditValues({ title: "title", shortDesc: "shortDesc" });
} catch (err) {}
};
fetchData();
}, []);
return (
<div>
<form value={{ authTokens, setAuthTokens: setTokens }}>
<input
value={editValues.title || ""}
onChange={(input) => setEditValues({title: input.target.value})}
type="text"
/>
<input
value={editValues.shortDesc}
onChange={(input) => setEditValues({shortDesc: input.target.value})}
type="text"
/>
<button type="submit">send</button>
</form>
</div>
);
};
export default EditArticle;

Ask about how to get react data from firebase

import React, { useEffect, useState } from "react";
import { dbService } from "./myFirebase";
function Editor({ userObj }) {
const [myContent, setMyContent] = useState("");
const [myContents, setMyContents] = useState([]);
const getValue = (event) => {
const { name, value } = event.target;
setMyContent({ ...myContent, [name]: value });
};
useEffect(() => {
console.log("1", myContents);
dbService.collection("contents").onSnapshot((snapshot) => {
const communityArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMyContents(communityArray);
console.log("2", myContents);
});
console.log("3", myContents);
}, []);
console.log("4", myContents);
const addContent = async (event) => {
event.preventDefault();
await dbService.collection("contents").add({
content: myContent,
createdAt: Date.now(),
});
setMyContent("");
};
return (
<div>
<button type="submit" onClick={addContent}>
{" "}
input{" "}
</button>
</div>
);
}
export default Editor;
When data is entered in this way, I coded it to be saved and received in Firebase.
If I print the log, it comes out as an empty array and then the value is entered.
So when I get it, if I say console.log("10", myContents[0].id);, it is properly received, but when I refresh it, TypeError: Cannot read property'id' of undefined appears.
I think it's because of that empty array, how do I fix it?
Are you using async await for useEffect? I tried, but it was the same, was it wrong?

Couldn't correctly initialize state in parent component from children states

I have two React components, namely, Form and SimpleCheckbox.
SimpleCheckbox uses some of the Material UI components but I believe they are irrelevant to my question.
In the Form, useEffect calls api.getCategoryNames() which resolves to an array of categories, e.g, ['Information', 'Investigation', 'Transaction', 'Pain'].
My goal is to access checkboxes' states(checked or not) in the parent component(Form). I have taken the approach suggested in this question.(See the verified answer)
Interestingly, when I log the checks it gives(after api call resolves):
{Pain: false}
What I expect is:
{
Information: false,
Investigation: false,
Transaction: false,
Pain: false,
}
Further More, checks state updates correctly when I click into checkboxes. For example, let's say I have checked Information and Investigation boxes, check becomes the following:
{
Pain: false,
Information: true,
Investigation: true,
}
Here is the components:
const Form = () => {
const [checks, setChecks] = useState({});
const [categories, setCategories] = useState([]);
const handleCheckChange = (isChecked, category) => {
setChecks({ ...checks, [category]: isChecked });
}
useEffect(() => {
api
.getCategoryNames()
.then((_categories) => {
setCategories(_categories);
})
.catch((error) => {
console.log(error);
});
}, []);
return (
{categories.map(category => {
<SimpleCheckbox
label={category}
onCheck={handleCheckChange}
key={category}
id={category}
/>
}
)
}
const SimpleCheckbox = ({ onCheck, label, id }) => {
const [check, setCheck] = useState(false);
const handleChange = (event) => {
setCheck(event.target.checked);
};
useEffect(() => {
onCheck(check, id);
}, [check]);
return (
<FormControl>
<FormControlLabel
control={
<Checkbox checked={check} onChange={handleChange} color="primary" />
}
label={label}
/>
</FormControl>
);
}
What I was missing was using functional updates in setChecks. Hooks API Reference says that: If the new state is computed using the previous state, you can pass a function to setState.
So after changing:
const handleCheckChange = (isChecked, category) => {
setChecks({ ...checks, [category]: isChecked });
}
to
const handleCheckChange = (isChecked, category) => {
setChecks(prevChecks => { ...prevChecks, [category]: isChecked });
}
It has started to work as I expected.
It looks like you're controlling state twice, at the form level and at the checkbox component level.
I eliminated one of those states and change handlers. In addition, I set checks to have an initialState so that you don't get an uncontrolled to controlled input warning
import React, { useState, useEffect } from "react";
import { FormControl, FormControlLabel, Checkbox } from "#material-ui/core";
import "./styles.css";
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<Form />
</div>
);
}
const Form = () => {
const [checks, setChecks] = useState({
Information: false,
Investigation: false,
Transaction: false,
Pain: false
});
const [categories, setCategories] = useState([]);
console.log("checks", checks);
console.log("categories", categories);
const handleCheckChange = (isChecked, category) => {
setChecks({ ...checks, [category]: isChecked });
};
useEffect(() => {
// api
// .getCategoryNames()
// .then(_categories => {
// setCategories(_categories);
// })
// .catch(error => {
// console.log(error);
// });
setCategories(["Information", "Investigation", "Transaction", "Pain"]);
}, []);
return (
<>
{categories.map(category => (
<SimpleCheckbox
label={category}
onCheck={handleCheckChange}
key={category}
id={category}
check={checks[category]}
/>
))}
</>
);
};
const SimpleCheckbox = ({ onCheck, label, check }) => {
return (
<FormControl>
<FormControlLabel
control={
<Checkbox
checked={check}
onChange={() => onCheck(!check, label)}
color="primary"
/>
}
label={label}
/>
</FormControl>
);
};
If you expect checks to by dynamically served by an api you can write a fetchHandler that awaits the results of the api and updates both slices of state
const fetchChecks = async () => {
let categoriesFromAPI = ["Information", "Investigation", "Transaction", "Pain"] // api result needs await
setCategories(categoriesFromAPI);
let initialChecks = categoriesFromAPI.reduce((acc, cur) => {
acc[cur] = false
return acc
}, {})
setChecks(initialChecks)
}
useEffect(() => {
fetchChecks()
}, []);
I hardcoded the categoriesFromApi variable, make sure you add await in front of your api call statement.
let categoriesFromApi = await axios.get(url)
Lastly, set your initial slice of state to an empty object
const [checks, setChecks] = useState({});

How to update state value of variable that uses custom Hook

My Component has form input fields. These made use of a useState hook with their value and setValue for each input field. I want to optimize my component so the input fields made use of the same custom Hook which I called useFormInput
Inspired by Dan Abramov https://youtu.be/dpw9EHDh2bM see at 49:42
This works perfectly. However now I want to update the username after a new exercise is created. This is in the onSubmit method. But I'm not sure how to do this. Before I refactored I could use setUserName(), but now username is set by the generic custom hook function useFormInput
the username has an onChange method, so I thought I can maybe use this. However this uses the e.target.value because it is used for an input field.
Component:
I commented out the setUserName(''), here I want to update the username
const CreateExercise = () => {
const inputEl = useRef(null)
const username = useFormInput('')
const description = useFormInput('')
const duration = useFormInput(0)
const date = useFormInput(new Date())
const [users, setUsers] = useState([])
useEffect(() => {
axios
.get('http://localhost:5000/users/')
.then(res => {
if (res.data.length > 0) {
setUsers(res.data.map(user => user.username))
}
})
.catch(err => console.log(err))
}, [])
const onSubmit = e => {
e.preventDefault()
const exercise = {
username: username.value,
description: description.value,
duration: duration.value,
date: date.value
}
axios
.post('http://localhost:5000/exercises/add', exercise)
.then(res => console.log(res.data))
debugger
// setUsername('')
window.location = '/'
}
custom Hook useFormInput:
const useFormInput = initialValue => {
const [value, setValue] = useState(initialValue)
const handleChange = e => {
const newValue = e.target ? e.target.value : e
setValue(newValue)
}
return {
value,
onChange: handleChange
}
}
I expect the value in the state of username is updated to an empty string ' '
Complete code is on my repo on https://github.com/jeltehomminga/mern-tracker
Instead of trying to maintain more than 1 state, I'd recommend combining all state into one object. Then you can move everything into your custom hook. In addition, always make sure you handle and communicate any errors to the user.
Working example:
State as an object
hooks/useFormHandler (the API defined below is an object with functions to mimic API calls -- you'll replace this with real API calls. Also, if you wanted to make this hook reusable for other form components, then you'll need to remove the useEffect and handleSubmit functions from the custom hook and place them inside the specified functional component instead)
import { useCallback, useEffect, useState } from "react";
import API from "../../API";
// create a custom useFormHandler hook that returns initial values,
// a handleChange function to update the field values and a handleSubmit
// function to handle form submissions.
const useFormHandler = initialState => {
const [values, setValues] = useState(initialState);
// on initial load this will attempt to fetch users and set them to state
// otherwise, if it fails, it'll set an error to state.
useEffect(() => {
API.get("http://localhost:5000/users/")
.then(res => {
if (res.data.length > 0) {
setValues(prevState => ({
...prevState,
users: res.data.map(({ username }) => username)
}));
} else {
setValues(prevState => ({
...prevState,
error: "Unable to locate users."
}));
}
})
.catch(err =>
setValues(prevState => ({ ...prevState, error: err.toString() }))
);
}, []);
// the handleChange function will first deconstruct e.target.name and
// e.target.value, then in the setValues callback function, it'll
// spread out any previous state before updating the changed field via
// [name] (e.target.name) and updating it with "value" (e.target.value)
const handleChange = useCallback(
({ target: { name, value } }) =>
setValues(prevState => ({ ...prevState, error: "", [name]: value })),
[]
);
// the handleSubmit function will send a request to the API, if it
// succeeds, it'll print a message and reset the form values, otherwise,
// if it fails, it'll set an error to state.
const handleSubmit = useCallback(
e => {
e.preventDefault();
const exercise = {
username: values.username,
description: values.description,
duration: values.duration,
date: values.date
};
// if any fields are empty, display an error
const emptyFields = Object.keys(exercise).some(field => !values[field]);
if (emptyFields) {
setValues(prevState => ({
...prevState,
error: "Please fill out all fields!"
}));
return;
}
API.post("http://localhost:5000/exercises/add", exercise)
.then(res => {
alert(JSON.stringify(res.message, null, 4));
setValues(prevState => ({ ...prevState, ...initialState }));
})
.catch(err =>
setValues(prevState => ({ ...prevState, error: err.toString() }))
);
},
[initialState, setValues, values]
);
return {
handleChange,
handleSubmit,
values
};
};
export default useFormHandler;
components/CreateExerciseForm
import isEmpty from "lodash/isEmpty";
import React, { Fragment } from "react";
import { FaCalendarPlus } from "react-icons/fa";
import Spinner from "react-spinkit";
import Button from "../Button";
import Input from "../Input";
import Select from "../Select";
import useFormHandler from "../../hooks/useFormHandler";
const fields = [
{ type: "text", name: "description", placeholder: "Exercise Description" },
{ type: "number", name: "duration", placeholder: "Duration (in minutes)" },
{
type: "date",
name: "date",
placeholder: "Date"
}
];
// utilize the custom useFormHandler hook within a functional component and
// pass it an object with some initial state.
const CreateExerciseForm = () => {
const { values, handleChange, handleSubmit } = useFormHandler({
username: "",
description: "",
duration: "",
date: "",
error: ""
});
// the below will show a spinner if "values.users" hasn't been fulfilled yet
// else, it'll show the form fields. in addition, if there's ever a
// "values.error", it'll be displayed to the user.
return (
<form
style={{ width: 500, margin: "0 auto", textAlign: "center" }}
onSubmit={handleSubmit}
>
{isEmpty(values.users) ? (
<Spinner name="line-scale" />
) : (
<Fragment>
<Select
name="username"
placeholder="Select a user..."
handleChange={handleChange}
value={values.username}
selectOptions={values.users}
style={{ width: "100%" }}
/>
{fields.map(({ name, type, placeholder }) => (
<Input
key={name}
type={type}
name={name}
placeholder={placeholder}
onChange={handleChange}
value={values[name]}
/>
))}
<Button type="submit">
<FaCalendarPlus style={{ position: "relative", top: 2 }} />
Create Exercise
</Button>
</Fragment>
)}
{values.error && <p>{values.error}</p>}
</form>
);
};
export default CreateExerciseForm;
State as independent data types
Or, if you insist on using separated states, then create a resetValue function in the useFormInput hook:
const useFormInput = initialValue => {
// initialize state from "initialValue"
const [value, setValue] = useState(initialValue)
// handle changes to the "value" state via updating it
// with e.target.value
const handleChange = useCallback(({ target: { value } => {
setValue(value)
}, []);
// reset the value back to initialValue
const resetValue = useCallback(() => {
setValue(initialValue);
}, []);
return {
value,
handleChange,
resetValue
}
}
Then, destructure properties for the username (and other states, if needed):
const CreateExercise = () => {
// use ES6 destructure and aliasing to extract and rename the
// "value" (as username), "handleChange" function (as
// handleUsernameChange) and "resetValue" function (as resetUsername)
const {
value: username,
handleChange: handleUsernameChange,
resetValue: resetUsername
} = useFormInput('')
...other form state
...useEffect(() => {}, [])
const handleSubmit = useCallback(e => {
e.preventDefault();
const exercise = {
username: username,
description: description,
duration: duration,
date: date
};
axios
.post('http://localhost:5000/exercises/add', exercise)
.then(res => {
console.log(res.data)
// only reset the username if the exercise was successfully
// created
resetUsername();
})
.catch(err => console.log(err.toString());
}, [date, description, duration, resetUsername, username]);
return ( ...form )
}
I took a look and did a PR - Formik implementation w/validation.
Here is the PR - https://github.com/jeltehomminga/mern-tracker/pull/1
UI View
<>
<h3>Create New Exercise Log</h3>
<pre>{JSON.stringify({ formData }, null, 2)}</pre>
<ExerciseForm {...{ users }} onChange={data => setFormData(data)} />
</>
CreateExercise Form
import React from "react";
import * as Yup from "yup";
import { Formik, Form, Field } from "formik";
import DatePicker from "react-datepicker";
import cx from "classnames";
const requiredMessage = "Required";
const exerciseFormSchema = Yup.object().shape({
username: Yup.string().required(requiredMessage),
description: Yup.string()
.min(2, "Too Short!")
.required(requiredMessage),
duration: Yup.number()
.integer()
.min(1, "Min minutes!")
.max(60, "Max minutes!")
.required(requiredMessage),
date: Yup.string().required(requiredMessage)
});
const ExerciseForm = ({ users = [], onChange }) => {
return (
<Formik
initialValues={{
username: "",
description: "",
duration: "",
date: ""
}}
validationSchema={exerciseFormSchema}
onSubmit={values => onChange(values)}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
isSubmitting,
setFieldValue
}) => {
const getProps = name => ({
name,
value: values[name],
onChange: handleChange,
onBlur: handleBlur,
className: cx("form-control", {
"is-invalid": errors[name]
})
});
return isSubmitting ? (
// Replace this with whatever you want...
<p>Thanks for the Exercise!</p>
) : (
<Form>
<FormControl label="Username">
<>
<select {...getProps("username")}>
<>
<option value="default">Select user...</option>
{users.map(person => (
<option key={person} value={person.toLowerCase()}>
{person}
</option>
))}
</>
</select>
<FormErrorMessage {...{ errors }} name="username" />
</>
</FormControl>
<FormControl label="Description">
<>
<Field {...getProps("description")} />
<FormErrorMessage {...{ errors }} name="description" />
</>
</FormControl>
<FormControl label="Duration in minutes">
<>
<Field {...getProps("duration")} type="number" />
<FormErrorMessage {...{ errors }} name="duration" />
</>
</FormControl>
<FormControl label="Date">
<>
{/* Was present before refactor */}
<div>
<DatePicker
{...getProps("date")}
selected={values.date}
minDate={new Date()}
onChange={date => setFieldValue("date", date)}
/>
<FormErrorMessage {...{ errors }} name="date" />
</div>
</>
</FormControl>
<button type="submit" className="btn btn-primary">
Create Exercise log
</button>
</Form>
);
}}
</Formik>
);
};
export default ExerciseForm;
// Created to manage label and parent className
const FormControl = ({ label, children }) => (
<div className="form-group">
<label>{label}:</label>
{children}
</div>
);
const FormErrorMessage = ({ name, errors }) => {
const error = errors && errors[name];
return error ? (
<div
class="invalid-feedback"
// Add inline style override as error message cannot sit as sibling to datePicker (bootstrap css)
style={{ display: "block" }}
>
{error}
</div>
) : null;
};

Categories