I’m learning React and have created a two page application. The first presents the user with a list of customers from a database, with Add, Edit, Delete, View buttons, my ‘App.js’. The second presents a "dialog box" using a CustomerForm component, which is visible when one of four variables is set to true (isAdding, isEditing, isDeleting, isViewing).
CustomerForm.js presents fields from the Customer recor, as input boxes, and should allow the user to perform the appropriate operation on them. I’ve got as far as displaying the data when the user clicks edit, but the fields appear uneditable. The issue relates to the value
attribute below:
value={customer != null ? customer[field.name] : ""}
When I omit this, the text is editable, however the customer record is not shown. The full listing of App.css, CustomerForm.css and CustomerForm.js follow:
App.css:
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
button {
border: none; /* Remove the border */
border-radius: 0; /* Make the corners square (0 radius) */
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); /* Shadow to the bottom-right */
padding: 5px 10px; /* vert, horiz. Add some padding */
background-color: darkgray; /* Button background color */
color: #FFFFFF; /* Button text color */
font-size: small; /* Font size */
cursor: pointer; /* Show a pointer cursor on hover */
outline: none; /* Remove the default outline on focus */
margin: 4px 4px; /* very, horiz. Add horizontal space between buttons */
}
h1 {
color: black;
font-weight: bold;
font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
font-size: x-large;
}
th {
background-color: darkgray;
color: white;
font-weight: normal;
font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
font-size: small; /* Font size */
margin: 0 4px; /* Add horizontal space between buttons */
text-align: left;
}
td {
/* background-color: lightgray;*/
font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
font-size: small;
text-align: left;
}
.note {
color: black;
font-style: italic;
font-family: 'Verdana', 'Arial', 'Helvetica', sans-serif;
font-size: x-small;
}
.selectedCustomer {
background-color: blue; /* Color for the highlighted row */
color: white;
cursor: pointer;
}
.customer {
background-color: lightgray;
color: black;
cursor: pointer;
}
CustomerForm.css:
.modal-overlay { /* this is for a window-sized <div> that stops interaction with the items below it */
position: fixed; /* the overlay stays in place regardless of scrolling */
top: 0; /* top-left of overlay */
left: 0;
width: 100%; /* from top-left, this covers the entire window, no matter what is done to the window */
height: 100%;
background: rgba(0, 0, 0, 0.5); /* drop the visibility of everything behind by 50% */
display: flex; /* These flexbox properties center the modal dialog horizontally and vertically within the overlay. The combination of justify-content: center; and align-items: center; ensures that the .modal-content (the dialog) is always in the middle of the screen, providing a focused area for user interaction. */
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
width: auto;
height: auto;
max-width: 500px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.buttonBar {
text-align: right;
}
.editableInput {
background-color: white;
}
.nonEditableInput {
background-color: lightBlue;
}
td {
padding-right: 10px; /* Adds padding to the right side of the cell */
}
CustomerForm.js:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import FormModes from './FormModes';
import './CustomerForm.css'; // Include CSS for modal styling
const CustomerForm = ({ customer, mode, onClose }) =>
{
const [formData, setFormData] = useState((customer != null) ?
customer :
{
CustomerID: '',
CustomerName: '',
Address1: '',
Address2: '',
Address3: '',
Town: '',
Postcode: '',
Country: '',
PhoneNumber: '',
CustomerID: '',
});
const [formMode, setFormMode] = useState(mode);
const [error, setError] = useState(null);
const formFields = // array of fields to show
[
{label:'Customer Name', type:'text', name:'CustomerName'},
{label:'Address 1', type:'text', name:'Address1'},
{label:'Address 2', type:'text', name:'Address2'},
{label:'Address 3', type:'text', name:'Address3'},
{label:'Town', type:'text', name:'Town'},
{label:'Postcode', type:'text', name:'Postcode'},
{label:'Country', type:'text', name:'Country'},
{label:'Phone Number', type:'text', name:'PhoneNumber'},
];
const buttonText = () =>
{
switch (formMode)
{
case FormModes.Add: return "Add";
case FormModes.Edit: return "Update";
case FormModes.Delete: return "DELETE";
case FormModes.View: return "Close";
default: throw new Error("Unknown formMode: ${formMode}")
}
};
const isReadOnly = () =>
{
return ((formMode==FormModes.Delete) || (formMode==FormModes.View));
};
const handleSubmit = async (event) =>
{
try
{
event.preventDefault(); // this stops the default submit action
switch (formMode)
{
case FormModes.Add:
break;
case FormModes.Edit:
const response = await axios.post('/api/customers', formData);
break;
case FormModes.Delete:
break;
case FormModes.View:
break;
default:
throw new Error('Unknown form Mode ${formMode}.')
}
onClose();
} catch (err) {
setError(err.message);
}
};
return (
<div className="modal-overlay">
<div className="modal-content">
<h2>{formMode} Customer</h2>
<form onSubmit={handleSubmit}>
<table border="0">
{formFields.map((field) =>
<tr key={field.CustomerID}>
<td padding-right="10px"><label>{field.label}</label></td>
<td><input type={field.type}
name={field.name}
value={(customer != null) ? customer[field.name] : ""}
placeholder={field.label}
readOnly={isReadOnly()}
className={isReadOnly() ? "nonEditableInput" : "editableInput"}
/>
</td>
</tr>
)}
</table>
<div className="buttonBar">
<button type="submit">{buttonText()}</button>
<button type="button" onClick={onClose}>Cancel</button>
{error && <p>Error: {error}</p>}
</div>
</form>
</div>
</div>
);
};
export default CustomerForm;
My question is, why does including this line:
value={customer != null ? customer[field.name] : ""}
lock the input field to that value? I simply want to set it when the field is shown and be editable.
2
Answers
You have created a "controlled component" by setting the
value
programatically. This prevents the value from being changed by the browser. You either need to add anonChange
prop with a handler that updates the value being set on thevalue
prop, or you need to remove thevalue
prop.See "My text input doesn’t update when I type into it" in the React docs for
input
.In React, directly setting the value prop of an input field can unintentionally lock the field to that value because React’s virtual DOM doesn’t update the input value when the component re-renders. To have the input field be initially populated but editable, you should manage the input value using the useState hook and update the component state accordingly.
Instead of setting the value to customer[field.name], you should use a state variable like formData to keep track of the form data. Set the value of the input field to formData[field.name] and attach an onChange handler to update formData when the input value changes.
For example:
This approach ensures that the input field is both initially populated with the customer data and remains editable as the user interacts with it.