Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,3 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Remote Content
/docs/**/examples/**/*.md
/docs/**/examples/**/*.png
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
description: Automatically calculates a read-only field in forms
title: Computed Form Fields
id: computed-form-fields
---

# Computed Form Fields Example

This example shows you how to build a form with computed form fields. Computed form fields automatically calculate a value based on a separate read-only field. For example, if a user inputs a quantity, a computed form field can multiply that number by a price to give a total cost.

Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples.

## Preview
This example will render as shown in this screenshot:

![Computed Form Fields](./src/assets/computed-form-fields.png)

## Steps

### 1. Define Constants for Input Fields

Before building out your form, define constants for each input field. Using constants helps reduce errors and makes your logic more maintainable.

```jsx
const SPECIES = "species";
const NUMBER_OF_FISH = "numberOfFish";
const COMPUTED_PRICE = "computedPrice";
```

In this example, we will build a form with three inputs. The values from the first two inputs will compute the value of `computedPrice`.

### 2. Initialize the Form Component with State

Next, we'll define the main form component. To manage the data entered in the form, we initialize it with a `formData` state variable, which is an empty object. This state will dynamically store the values of the form fields as users interact with the form.

The `setFormData` function allows us to update the state whenever an input changes. This ensures the form data is kept in sync.

```jsx
const ComputedForm = () => {
const [formData, setFormData] = useState({});

return (
// form JSX will go here
);
};
```

### 3. Structuring the Form with Inputs

In the `return` statement of the component, use the Trussworks `Form` component to structure your form. Within the form, include input components such as `TextInput`, referencing the variables defined in Step 1 (e.g. `NUMBER_OF_FISH`) instead of hardcoding strings. This practice helps avoid typos and ensures consistency when accessing the `formData` state. For better organization and styling, wrap your inputs within the `FormGroup` component provided by Trussworks.

```jsx
return (
<Form
onSubmit={handleSubmit}
className="maxw-full margin-205 padding-205 bg-white radius-8px shadow-2"
>
<FormGroup>
<Label className="text-bold" htmlFor={NUMBER_OF_FISH}>
Number of Fish
</Label>
<TextInput
className="text-bold"
id={NUMBER_OF_FISH}
name={NUMBER_OF_FISH}
type="number"
placeholder="0"
value={formData[NUMBER_OF_FISH] || ""}
/>
```

### 4. Adding Input Handlers for Form Fields

Use `handleNumberFishChange` and `handleSelectChange` to manage the state for the **Number of Fish** and **Species** fields. These functions update the `formData` state whenever the user interacts with the inputs.

For the **Number of Fish** input, the `handleNumberFishChange` function captures the input value and updates the corresponding field in the `formData` state. Similarly, the `handleSelectChange` function updates the state when the user selects a species.

This function handles changes in the **Number of Fish** input field:
```jsx
const handleNumberFishChange = (event, formData) => {
const { value } = event.target;
setFormData({
...formData, // Preserve existing form data
[NUMBER_OF_FISH]: value, // Update the "Number of Fish" field
});
};
```

Use the `handleNumberFishChange` function to manage the **Number of Fish** input:
```jsx
<TextInput
className="text-bold"
id={NUMBER_OF_FISH}
name={NUMBER_OF_FISH}
type="number"
placeholder="0"
value={formData[NUMBER_OF_FISH] || ""} // Display the current state value
onChange={(event) => handleNumberFishChange(event, formData)} // Trigger the handler on change
/>
```

### 5. Calculating Computed Price

In addition to updating the input value of `numberOfFish`, we also want to update the value of `computedPrice`. We can do that by adding a helper function to run some logic to compute that value. We then call that helper function in the input's `onChange` handler:

This maps species to their respective prices:
```jsx
const speciesPriceMap = {
grouper: 25.0,
salmon: 58.0,
marlin: 100.0,
mahimahi: 44.0,
};
```

This function is kept outside the component since it doesn't rely on React state or lifecycle:
```jsx
const computeFieldValue = (numberOfFish, species) => {
let computedPrice = parseInt(numberOfFish || 0) * parseInt(speciesPriceMap[species] || 0);
return computedPrice.toString();
};
```

This function updates the **Number of Fish** and dynamically computes the "Computed Price":
```jsx
const handleNumberFishChange = (event, formData) => {
const { value } = event.target;
setFormData({
...formData,
[NUMBER_OF_FISH]: value,
[COMPUTED_PRICE]: computeFieldValue(value, formData?.species || ""), // Compute and update the "Computed Price"
});
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
description: Dynamically show/hide fields based on other inputs
title: Conditional Form Fields
id: conditional-form-fields
---

# Conditional Form Fields Example

This example shows you how to create a dynamic form with conditional fields. The visibility of certain input fields depends on the values of other fields. Specifically, when the `fullName` field is filled out, the `nickname` field becomes visible. If the `fullName` field is empty, the `nickname` field is hidden and removed from the `formState`.

Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples.

## Preview
This example will render as shown in this screenshot:

![Conditional Form Fields](./src/assets/conditional-form-fields.png)


## Steps

### 1. Define Constants for Input Fields

Before building out your form, define constants for each input field. Using constants helps reduce errors and makes your logic more maintainable.

```jsx
const FULL_NAME = "fullName";
const NICKNAME = "nickname";
```

In this example, we will build a form with two inputs. The value from `FULL_NAME` inputs will control the visiblity of `NICKNAME`.

### 2. Initialize the Form Component with State

Next, we'll define the main form component. To manage the data entered in the form, we initialize it with a `formData` state variable, which is an empty object. This state will dynamically store the values of the form fields as users interact with the form.

The `setFormData` function allows us to update the state whenever an input changes. This ensures the form data is kept in sync.

```jsx
const ConditionalForm = () => {
const [formData, setFormData] = useState({});

return (
// form JSX will go here
);
};
```

### 3. Structuring the Form with Inputs

In the `return` statement of the component, use the Trussworks `Form` component to structure your form. Within the form, include input components such as `TextInput`, referencing the variables defined in Step 1 (e.g., `FULL_NAME`) instead of hardcoding strings. This practice helps avoid typos and ensures consistency when accessing the `formData` state. For better organization and styling, wrap your inputs within the `FormGroup` component provided by Trussworks.

```jsx
return (
<Form
onSubmit={handleSubmit}
className="maxw-full margin-205 padding-205 bg-white radius-8px shadow-2"
>
<FormGroup>
<Label className="text-bold" htmlFor={FULL_NAME}>
Full Name
</Label>
<TextInput
id={FULL_NAME}
name={FULL_NAME}
type="text"
placeholder="Full Name"
value={formData[FULL_NAME] || ""}
/>
```

### 4. Adding Input Handlers for Form Fields

Now we can create an `onChange` handler for each input. This captures the input's value as it is being typed. We'll use `handleFullNameChange` to update state with a copy of the existing `formState`, and update only the field value that we want to update. In this case, the value we want to update is `fullName`. The value of this input will then re-render with the updated value from `formState`.

This function handles changes in the **Full Name** input field:
```jsx
const handleFullNameChange = (event, formData) => {
const { value } = event.target;
setFormData({
...formData, // Preserve existing form data
[FULL_NAME]: value, // Update the "Full Name" field
});
};
```

Use the `handleFullNameChange` function to manage the **Full Name** input:
```jsx
<TextInput
id={FULL_NAME}
name={FULL_NAME}
type="text"
placeholder="Full Name"
value={formData[FULL_NAME] || ""}
onChange={(event) => handleFullNameChange(event, formData)} // Call the handleFullNameChange function to update the "Full Name" field
/>
```

### 5. Conditionally Render Nickname Field

Next, we want to conditionally render the `nickname` component. The condition depends on whether or not the `fullName` value is set in `formState`. This input field will only render if `formData[FULL_NAME]` evaluates to `true`.

```jsx
{
formData[FULL_NAME] && (
<>
<Label htmlFor={NICKNAME}>Nickname</Label>
<TextInput
id={NICKNAME}
name={NICKNAME}
type="text"
placeholder="Nickname"
value={formData[NICKNAME] || ""}
onChange={(event) => handleNickNameChange(event, formData)}
/>
</>
)
}
```

### 6. Managing Dependent Field Values

In this step, we ensure that the value of the **Nickname** field is cleared whenever the **Full Name** field is empty. Do this by adding logic to the `onChange` handler which calls the `handleFullNameChange` function.

```jsx
const handleFullNameChange = (event, formData) => {
const { value } = event.target;
setFormData({
...formData,
[FULL_NAME]: value,
[NICKNAME]: value === "" ? "" : formData[NICKNAME], // Clear the "Nickname" field if "Full Name" is empty
});
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
description: Enforce validation logic, like disallowing numbers
title: Field Validators
id: field-validators
---

# Form Field Validators Example

This example shows you how to enforce field validation logic on certain fields within your form. The validator ensures that no numbers are allowed within the **Full Name** input field.

Learn more about RADFish examples at the official [documentation](https://nmfs-radfish.github.io/radfish/developer-documentation/examples-and-templates#examples). Refer to the [RADFish GitHub repo](https://nmfs-radfish.github.io/radfish/) for more information and code samples.

## Preview
This example will render as shown in this screenshot:

![Field Validators](./src/assets/field-validators.png)

## Steps

### 1. Adding Validation with `onBlur` Handlers
In this step, we add validation logic to our form inputs using an `onBlur` handler. This ensures the data is validated when the user navigates away from the input field. It also provides immediate feedback on any validation errors.

This function validates the input value. It uses the validation rules and updates the state with any validation errors:
```jsx
const handleBlur = (event, validators) => {
const { name, value } = event.target;
setValidationErrors((prev) => ({
...prev,
...handleInputValidationLogic(name, value, validators),
}));
};
```

This helper function loops through the provided validators and checks if the input value passes the validation criteria. If not, it returns the appropriate error message:
```jsx
const handleInputValidationLogic = (name, value, validators) => {
if (validators && validators.length > 0) {
for (let validator of validators) {
if (!validator.test(value)) {
return { [name]: validator.message };
}
}
}
return { [name]: null }; // Clear error if value is valid
};
```

Here’s how to use the onBlur handler in the TextInput component. The input dynamically sets its validation status and ARIA attributes based on validation errors:
```jsx
<TextInput
id={FULL_NAME}
name={FULL_NAME}
type="text"
placeholder="Full Name"
value={formData[FULL_NAME] || ""}
aria-invalid={validationErrors[FULL_NAME] ? "true" : "false"}
validationStatus={validationErrors[FULL_NAME] ? "error" : undefined}
onChange={handleChange}
onBlur={(e) => handleBlur(e, fullNameValidators)}
/>
```

### 2. Testing with Validator
We include the validator needed to check the contents of the field input. In this case `fullNameValidators` has a single test that checks if the input contains a number. This array can include several tests for more complex validation logic. They are each included as separate objects within each validator array.

```jsx
// utilities/fieldValidators.js
const fullNameValidators = [
{
test: (value) => !/\d/.test(value) || value === "",
message: "Full Name should not contain numbers.",
},
];

export { fullNameValidators };

// in Form.js
import { fullNameValidators } from "../utilities/fieldValidators";
```

### 3. Trigger Validation Logic and Render Error Message
We can update the properties on our input so the validation logic gets triggered when a user navigates away from the form field. We also conditionally render an `ErrorMessage` when there is an error present.

```jsx
<TextInput
id={FULL_NAME}
name={FULL_NAME}
type="text"
placeholder="Full Name"
value={formData[FULL_NAME] || ""}
aria-invalid={validationErrors[FULL_NAME] ? "true" : "false"}
validationStatus={validationErrors[FULL_NAME] ? "error" : undefined}
onChange={handleChange}
onBlur={(e) => handleBlur(e, fullNameValidators)}
/>
{
validationErrors[FULL_NAME] && <ErrorMessage>{validationErrors[FULL_NAME]}</ErrorMessage>
}
```
Loading