Reputation: 3919
I have a React app which uses the following Model:
export interface DataModel {
itemId: string,
itemName: string,
}
I define an initial value for the model like this:
export const INIT_ITEM:DataModel = {
itemId: "",
itemName: "",
}
This is used in a Dashbaord Component, which shows a form allowing the user to set a name, minimumValue and maximumValue. Name is required; minimumValue and maximumValue are not, but if they are both present then maximumValue needs to be greater than minimumValue. This validation is done when the form is submitted. This is the Dashboard Component:
interface ErrorHolder { // Holds error strings
selectedItems: string,
selection1: string,
selection2: string,
valid: boolean
}
const INIT_VALIDATION:ErrorHolder = {
selectedItems: "",
selection1: "",
selection2: "",
valid: false
}
// availableItems is an array of <DataModel>, prepopulated with available options
// selectedItems is an array of <DataModel>, and is initially empty
// selection1 and selection2 are both <DataModel> objects, which are initialised as above
//
// The items from availableItems are displayed with checkboxes. The user must select at least one item to // display on the Dashboard; the list of selected items is stored as selectedItems
// The user must also select an item to be 'selection1' and a different item to be 'selection2'
//
const Dashboard = ( {availableItems, selectedItems, setSelectedItems, selection1, setSelection1, selection2, setSelection2} ) => {
const [valid, setValid] = useState<ErrorHolder>(INIT_VALIDATION); // Initialise error strings
const handleChangeItemSelected = (event:React.ChangeEvent<HTMLInputElement>, checked:boolean) => {
const itemId:number = event.target.value;
if(checked) {
// Checkbox is checked, so this item should be shown
const newSelectedItems = [...selectedItems];
const fullItem = availableItems.filter(item => item.itemId === itemId);
newSelectedItems .push(fullItem[0]);
setSelectedItems(newSelectedItems );
}else {
// Checkbox is not checked, so this item should not be shown
const newSelectedItems = selectedItems.filter(item=> item.itemId !== itemId);
setSelectedItems(newSelectedItems );
}
}
const handleSelectItem = (event: { target: HTMLSelectElement }) => {
let thisItem:DataModel|undefined = undefined;
let selectedId = parseInt(event.target.value);
if(selectedId !== 0) {
const findItem = availableItems.filter(item => item.itemId === selectedId);
thisItem = findItem[0];
}
// Check which item is being changed
switch(event.target.id) {
case 'selection1':
setSelection1(thisItem);
break;
case 'metric2Select':
setSelection2(thisItem);
break;
}
}
const handleSubmit = (event: SyntheticEvent) => {
event.preventDefault();
// Validate
let tempValid:ErrorHolder = INIT_VALIDATION;
tempValid.valid = true;
// Validate everything - set error if found
if(selectedItems.length === 0) { // There must be at least 1 selected item
tempValid.valid = false;
tempValid.selectedItems = "Please select at least one item";
}
// Make sure 2 different items are selected for selection 1 and selection 2
if(!selection1) {
tempValid.valid = false;
tempValid.selection1 = "Please select two items to show on the chart";
}else if(!selection2) {
tempValid.valid = false;
tempValid.selection2 = "Please select two items to show on the chart";
}else if(selection1.itemId === selection2.itemId) {
tempValid.valid = false;
tempValid.selection2= "Please select two different items to show on the chart";
}
}
setValid(tempValid);
}
return (
<form onSubmit = { handleSubmit }>
// List available options
<div>{valid.selectedItems}</div> // Display error message for selected items
<table>
<thead>
<tr>
<td>Name</td>
<td>Show</td>
</tr>
</thead>
<tbody>
{
availableItems.map((item) => { // List all available items with checkboxes
<tr key={item.itemId}>
<td>{item.itemName}</td>
<td><Checkbox value={item.itemId}
onChange={handleChangeItemSelected}
checked={selectedItems.includes(item)} /></td>
</tr>
})
}
</tbody>
</table>
<select id = "selection1" onChange = { event => handleSelectItem(event) }
value = { selection1?.itemId}>
<option key="sel1" value="0">Please Select</option>
{
availableItems.map((item) => <option key={item.itemId} value={item.itemId}>{item.itemName}</option>;
}
</select>
<div>{valid.selection1}</div>
<select id = "selection2" onChange = { event => handleSelectItem(event) }
value = { selection2?.itemId}>
<option key="sel2" value="0">Please Select</option>
{
availableItems.map((item) => <option key={item.itemId} value={item.itemId}>{item.itemName}</option>;
}
</select>
<div>{valid.selection2}</div>
</form>
);
}
export default Dashboard;
The problem I'm having is that the errors don't show if I enter invalid data (eg don't select anything, select same thing for selection1 and selection2 etc) and click 'Submit'. If I do a console log I can see that the valid
variable has been set correctly, but it seems that the component is not re-rendering to show it.
What am I doing wrong?
Upvotes: 2
Views: 47
Reputation: 15116
In every call to handleSubmit
you're mutating the same object INIT_VALIDATION
, and passing the same object to a setState
function won't trigger a rerender.
If you make a copy by replacing
let tempValid:ErrorHolder = INIT_VALIDATION
with
const tempValid:ErrorHolder = {...INIT_VALIDATION}
I would expect it to work. (The let
wasn't part of the problem, but if you're not changing the variable itself you can use a const
instead.)
Upvotes: 1