I am wanting to use a Formik form to edit data records that exist in a MySQL database. The overall architecture I was planning was that a component would extract the data from the MySQL database and pass this data as array props to a child component which contains the Formik form. I was then aiming to deconstruct those props to populate a const variable called initialvalues, which Formik uses to pre-populate the fields in the form.
I have retrieved the data I want to display in the form as an array using MembersByID.jsx below:
MembersByID.jsx
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import MembersEdit from '../Members/MembersEdit'
function MembersByID(props) {
const [memberData, setMemberData] = useState([])
// console.log(memberData)
useEffect(() => {
axios.get( `http://localhost:3001/members/byId/${props.id}`)
.then((response) => {
setMemberData(response.data);
})
}, [props.id])
return (
//next line takes props from DataTable.jsx - where the prop = id, which in turn equals the row id clicked in the table
<>
<div>Member ID No from MembersByID.jsx = {props.id}</div>
<div></div>
<MembersEdit memberData={memberData}/></>
)
}
export default MembersByID
That component passes the member data as a prop called memberData to a child component called MembersEdit.jsx:
MembersEdit.jsx
import React, { useState } from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup'; //yup does form validation
import axios from 'axios';
import { useMutation } from '@tanstack/react-query';
import { PatternFormat } from 'react-number-format';
//react-query useMutation code
const useMembersCreateMutation = () => {
return useMutation((formPayload) => {
return axios.post('http://localhost:3001/members', formPayload);
});
};
//Variable to store Tailwind css for 'Field' elements of Formik function
const formikField =
'my-px block px-2.5 pb-2.5 pt-4 w-full text-sm text-gray-900 bg-transparent rounded-lg border border-gray-400 appearance-none focus:outline-none focus:ring-0 focus:border-blue-600 peer';
//Variable to store Tailwind css for 'Label' elements of Formik function
const formikLabel =
'absolute text-base text-gray-500 duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] bg-white dark:bg-gray-900 px-2 peer-focus:px-2 peer-focus:text-blue-600 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-6 peer-focus:top-1 peer-focus:scale-75 peer-focus:-translate-y-4 left-1';
//Function for date fields formatting, using react-number-format
function DateField({ field }) {
return (
<PatternFormat
{...field}
format="####/##/##"
mask={['Y', 'Y', 'Y', 'Y', 'M', 'M', 'D', 'D']}
className={formikField}
placeholder="YYYY/MM/DD"
/>
);
}
//Main function - creates Formik form
function MembersEdit(props) {
const { mutate } = useMembersCreateMutation();
console.log(props)
// //Formik initial values
const initialValues = {
forename: {props?.memberData?.forename},
surname: '',
date_of_birth: '',
email_address: '',
mobile_phone: '',
address_1: '',
address_2: '',
address_3: '',
address_4: '',
address_5: '',
postcode: '',
doctor_name: '',
medical_equipment: '',
medical_conditions: '',
next_of_kin_name: '',
next_of_kin_relationship: '',
next_of_kin_phone: '',
next_of_kin_local: '0',
key_safe_code: '',
directions: '',
deceased_date: '',
normally_escorted: '',
blue_badge_holder: '0',
};
// Yup field validation
const validationSchema = Yup.object().shape({
forename: Yup.string()
.required('*Forename is required')
.max(35, 'Forename can be a maximum of 35 characters'),
surname: Yup.string()
.required('*Surname is required')
.max(35, 'Surname can be a maximum of 35 characters'),
date_of_birth: Yup.string().required('*Date of Birth is required'),
email_address: Yup.string()
.email('*Invalid email address format')
.max(255, 'Email address can be a maximum of 255 characters'),
mobile_phone: Yup.string().max(
12,
'Mobile phone can be a maximum of 12 characters'
),
address_1: Yup.string()
.required('*Address Line 1 is required')
.max(35, 'Address Line 1 can be a maximum of 35 characters'),
address_2: Yup.string().max(
35,
'Address Line 2 can be a maximum of 35 characters'
),
address_3: Yup.string().max(
35,
'Address Line 3 can be a maximum of 35 characters'
),
address_4: Yup.string().max(
35,
'Address Line 4 can be a maximum of 35 characters'
),
address_5: Yup.string().max(
35,
'Address Line 5 can be a maximum of 35 characters'
),
postcode: Yup.string()
.required('*Postcode is required')
.max(12, 'Postcode can be a maximum of 12 characters'),
doctor_name: Yup.string().max(
35,
'Doctor can be a maximum of 35 characters'
),
medical_equipment: Yup.string().max(
255,
'Medical Equipment can be a maximum of 255 characters'
),
initial_medical_conditions: Yup.string(),
next_of_kin_name: Yup.string().max(
70,
'Next of Kin Name can be a maximum of 70 characters'
),
next_of_kin_relationship: Yup.string().max(
40,
'Next of Kin Relationship can be a maximum of 40 characters'
),
next_of_kin_phone: Yup.string().max(
12,
'Next of Kin Phonecan be a maximum of 12 characters'
),
key_safe_code: Yup.string().max(
8,
'Key Safe Code can be a maximum of 8 characters'
),
deceased: Yup.string(),
});
// State used to display success/error posting message
const [createMsg, setCreateMsg] = useState('');
return (
<>
{/* in the below, the two question marks mean it only attempts to get the prop value if it is not null */}
{props?.memberData?.forename}
<div className="createMemberPage px-5">
<Formik
enableReinitialize={true}
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, formik) => {
mutate(values, {
onSuccess: () => {
setCreateMsg('New Member Created!');
formik.resetForm();
},
one rror: (response) => {
setCreateMsg('Error: Member not created - Keep Calm and Call Jonathan');
console.log(response);
},
});
} }
>
<Form className="formContainer">
<h1 className="pb-3 text-xl font-semibold">General Information</h1>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="forename"
placeholder=" " />
<label className={formikLabel}>Forename</label>
<ErrorMessage
name="forename"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="surname"
placeholder=" " />
<label className={formikLabel}>Surname</label>
<ErrorMessage
name="surname"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="date_of_birth"
placeholder=" "
component={DateField} />
<label className={formikLabel}>Date Of Birth</label>
<ErrorMessage
name="date_of_birth"
component="span"
className="text-red-600" />
</div>
<h1 className="pb-3 pt-5 text-xl font-semibold">
Contact Information
</h1>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="email_address"
placeholder=" " />
<label className={formikLabel}>Email Address</label>
<ErrorMessage
name="email_address"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="mobile_phone"
placeholder=" " />
<label className={formikLabel}>Mobile Phone</label>
<ErrorMessage
name="mobile_phone"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="address_1"
placeholder=" " />
<label className={formikLabel}>Address Line 1</label>
<ErrorMessage
name="address_1"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="address_2"
placeholder=" " />
<label className={formikLabel}>Address Line 2</label>
<ErrorMessage
name="address_2"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="address_3"
placeholder=" " />
<label className={formikLabel}>Address Line 3</label>
<ErrorMessage
name="address_3"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="address_4"
placeholder=" " />
<label className={formikLabel}>Address Line 4</label>
<ErrorMessage
name="address_4"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="address_5"
placeholder=" " />
<label className={formikLabel}>Address Line 5</label>
<ErrorMessage
name="address_5"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="postcode"
placeholder=" " />
<label className={formikLabel}>Postcode</label>
<ErrorMessage
name="postcode"
component="span"
className="text-red-600" />
</div>
<h1 className="pb-3 pt-5 text-xl font-semibold">
Medical Information
</h1>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="doctor_name"
placeholder=" " />
<label className={formikLabel}>Doctor Name</label>
<ErrorMessage
name="doctor_name"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="medical_equipment"
placeholder=" " />
<label className={formikLabel}>Medical Equipment</label>
<ErrorMessage
name="Medical Equipment"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="initial_medical_conditions"
placeholder=" " />
<label className={formikLabel}>Medical Conditions</label>
<ErrorMessage
name="initial_medical_conditions"
component="span"
className="text-red-600" />
</div>
<h1 className="pb-3 pt-5 text-xl font-semibold">Next Of Kin</h1>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="next_of_kin_name"
placeholder=" " />
<label className={formikLabel}>Next of Kin Name</label>
<ErrorMessage
name="next_of_kin_name"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="next_of_kin_relationship"
placeholder=" " />
<label className={formikLabel}>Next of Kin Relationship</label>
<ErrorMessage
name="next_of_kin_relationship"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="next_of_kin_phone"
placeholder=" " />
<label className={formikLabel}>Next of Kin Phone</label>
<ErrorMessage
name="next_of_kin_phone"
component="span"
className="text-red-600" />
</div>
<div className="pb-2">
<label className="text-gray-500 px-2.5">Next Of Kin Local</label>
<ErrorMessage
name="next_of_kin_local"
component="span"
className="text-red-600" />
<Field
type="checkbox"
value="1"
className="border border-gray-400 rounded-lg outline-0 text-gray-500"
autoComplete="off"
id="inputCreateMember"
name="next_of_kin_local"
placeholder=" " />
</div>
<h1 className="pb-3 pt-5 text-xl font-semibold">Other Information</h1>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="key_safe_code"
placeholder=" " />
<label className={formikLabel}>Key Safe Code</label>
<ErrorMessage
name="key_safe_code"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="directions"
placeholder=" " />
<label className={formikLabel}>Directions to Home</label>
<ErrorMessage
name="directions"
component="span"
className="text-red-600" />
</div>
<div className="pb-2 relative">
<Field
className={formikField}
autoComplete="off"
id="inputCreateMember"
name="deceased"
placeholder=" "
component={DateField} />
<label className={formikLabel}>Deceased Date</label>
<ErrorMessage
name="deceased"
component="span"
className="text-red-600" />
</div>
<div className="pb-2">
<label className="text-gray-500 px-2.5">Normally Escorted</label>
<ErrorMessage
name="normally_escorted"
component="span"
className="text-red-600" />
<Field
type="checkbox"
value="1"
className="border border-gray-400 rounded-lg outline-0 text-gray-500"
autoComplete="off"
id="inputCreateMember"
name="normally_escorted"
placeholder=" " />
</div>
<div className="pb-2">
<label className="text-gray-500 px-2.5">Blue Badge Holder</label>
<ErrorMessage
name="blue_badge_holder"
component="span"
className="text-red-600" />
<Field
type="checkbox"
value="1"
className="border border-gray-400 rounded-lg outline-0 text-gray-500"
autoComplete="off"
id="inputCreateMember"
name="blue_badge_holder"
placeholder=" " />
</div>
<div className="flex flex-col items-center">
<button
className="text-base text-white bg-blue-500 border hover:bg-blue-600 hover:text-gray-100 p-2 px-20 rounded-lg mt-5"
type="submit"
>
Create Member
</button>
</div>
<br></br>
<h1 className={(createMsg === "") ? "" :
((createMsg === "New Member Created!") ? "text-xl text-blue-600 font-bold p-2 border border-blue-600 text-center" : "text-xl text-red-600 font-bold p-2 border border-red-600 text-center")}> {/* This code only formats the class, hence shows the border, when a message is being displayed */}
{createMsg}
</h1>
</Form>
</Formik>
</div></>
);
}
export default MembersEdit;
The props data arriving in MembersEdit is as follows, when logged in console.log:
As a test, I rendered one part of that props array in the return section of the MembersEdit function, using the line {props?.memberData?.forename}. This renders on the screen OK.
Next, I tried adding the same line of code in the forename part of the initialValues const array, so in the part:
const initialValues = {
forename: {props?.memberData?.forename},
But I then get a parsing error stating that a comma is expected:
I'm not sure why the line of code: {props?.memberData?.forename} works when rendering the forename in the return section of the MemberEdit component, but not in the const array section. Am I just making a simple syntax error of some description, or is my entire approach here incorrect?
CodePudding user response:
What you have here
props?.memberData?.forename
is a plain value - probably either a string or undefined
.
When in the context of JSX, putting {}
brackets around something indicates to interpret what's inside as a value to interpolate into the HTML. So doing
return (
<>
{props?.memberData?.forename}
works.
But that's only in the context of JSX. When outside of JSX, {
indicates either the start of a block, or the start of an object literal. When in an expression context, it indicates the start of an object literal. Here, you're in an expression context:
const initialValues = {
forename: // expression goes here
You don't need to interpolate anything, or create a nested object - you just need the plain value from props.
const initialValues = {
forename: props?.memberData?.forename,
Doing
const initialValues = {
forename: {props?.memberData?.forename},
fails because the {
above indicates the start of an object literal, but objects require key-value pairs, and props?.memberData?.forename
is only a value (an expression). For it to be valid syntax, it would need a key as well, such as { someKey: props?.memberData?.forename }
.
Another issue to consider is that optional chaining evaluates to undefined
when the chain fails. You might want to account for that so that forename
is set to the empty string if the chain fails, instead of undefined
.
const initialValues = {
forename: props?.memberData?.forename ?? '',