display api errore messages pattern (#25721)
This is a quick doc update the frontend patterns for how we want to handle displaying API error messages. Also contains some markdown lint warnings
This commit is contained in:
+55
-26
@@ -137,22 +137,27 @@ We use functional components with React instead of class comonents. We do this
|
||||
as this allows us to use hooks to better share common logic between components.
|
||||
|
||||
### Passing props into components
|
||||
|
||||
We tend to use explicit assignment of prop values, instead of object spread syntax:
|
||||
```
|
||||
|
||||
```tsx
|
||||
<ExampleComponent prop1={pop1Val} prop2={prop2Val} prop3={prop3Val} />
|
||||
```
|
||||
|
||||
### Naming handlers
|
||||
|
||||
When defining component props for handlers, we prefer naming with a more general `onAction`. When
|
||||
naming the handler passed into that prop or used in the same component it's defined, we prefer
|
||||
either the same `onAction` or, if useful, a more specific `onMoreSpecifiedAction`. E.g.:
|
||||
|
||||
```tsx
|
||||
<BigSecretComponent
|
||||
onSubmit={onSubmit}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```tsx
|
||||
<BigSecretComponent
|
||||
onSubmit={onUpdateBigSecret}
|
||||
@@ -195,32 +200,35 @@ const PackComposerPage = ({ router }: IPackComposerPageProps): JSX.Element => {
|
||||
export default PackComposerPage;
|
||||
```
|
||||
|
||||
|
||||
## Forms
|
||||
|
||||
### Data validation
|
||||
|
||||
#### How to validate
|
||||
|
||||
Forms should make use of a pure `validate` function whose input(s) correspond to form data (may include
|
||||
new and possibly former form data) and whose output is an object of formFieldName:errorMessage
|
||||
key-value pairs (`Record<string,string>`) e.g.
|
||||
|
||||
```
|
||||
```tsx
|
||||
const validate = (newFormData: IFormData) => {
|
||||
const errors = {};
|
||||
...
|
||||
return errors;
|
||||
}
|
||||
```
|
||||
|
||||
The output of `validate` should be used by the calling handler to set a `formErrors`
|
||||
state.
|
||||
|
||||
#### When to validate
|
||||
|
||||
Form fields should *set only new errors* on blur and on save, and *set or remove* errors on change. This provides
|
||||
an "optimistic" user experience. The user is only told they have an error once they navigate
|
||||
away from a field or hit enter, actions which imply they are finished editing the field, while they are informed they have fixed
|
||||
an error as soon as possible, that is, as soon as they make the fixing change. e.g.
|
||||
```
|
||||
|
||||
```tsx
|
||||
const onInputChange = ({ name, value }: IFormField) => {
|
||||
const newFormData = { ...formData, [name]: value };
|
||||
setFormData(newFormData);
|
||||
@@ -240,15 +248,15 @@ const onInputChange = ({ name, value }: IFormField) => {
|
||||
|
||||
,
|
||||
|
||||
```
|
||||
```tsx
|
||||
const onInputBlur = () => {
|
||||
setFormErrors(validateFormData(formData));
|
||||
};
|
||||
```
|
||||
|
||||
, and
|
||||
, and
|
||||
|
||||
```
|
||||
```tsx
|
||||
const onFormSubmit = (evt: React.MouseEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
|
||||
@@ -268,7 +276,7 @@ const onFormSubmit = (evt: React.MouseEvent<HTMLFormElement>) => {
|
||||
|
||||
[Hooks](https://reactjs.org/docs/hooks-intro.html) are used to track state and use other features
|
||||
of React. Hooks are only allowed in functional components, which are created like so:
|
||||
|
||||
|
||||
```typescript
|
||||
import React, { useState, useEffect } from "React";
|
||||
|
||||
@@ -301,15 +309,18 @@ View currently working contexts in the [context directory](../context).
|
||||
|
||||
## Fleet API calls
|
||||
|
||||
### Making API calls
|
||||
|
||||
The [services](../services) directory stores all API calls and is to be used in two ways:
|
||||
|
||||
- A direct `async/await` assignment
|
||||
- Using `react-query` if requirements call for loading data right away or based on dependencies.
|
||||
|
||||
Examples below:
|
||||
|
||||
**Direct assignment**
|
||||
#### Direct assignment
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
// page
|
||||
import ...
|
||||
import queriesAPI from "services/entities/queries";
|
||||
@@ -331,12 +342,12 @@ const PageOrComponent = (props) => {
|
||||
};
|
||||
```
|
||||
|
||||
**React Query**
|
||||
#### React Query
|
||||
|
||||
[react-query](https://react-query.tanstack.com/overview) is a data-fetching library that
|
||||
gives us the ability to fetch, cache, sync and update data with a myriad of options and properties.
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
import ...
|
||||
import { useQuery, useMutation } from "react-query";
|
||||
import queriesAPI from "services/entities/queries";
|
||||
@@ -370,6 +381,35 @@ const PageOrComponent = (props) => {
|
||||
};
|
||||
```
|
||||
|
||||
### Handling API errors
|
||||
|
||||
We pull the logic for handling error message into a `getErrorMessage` handler that lives in a sibling
|
||||
`helpers.tsx` or `helpers.ts` file. This allow us to encapsulate the code for getting and formatting
|
||||
the API error message away from the component. This will keep put components cleaner and easier
|
||||
to read.
|
||||
|
||||
```tsx
|
||||
/* In the component making a request */
|
||||
|
||||
try {
|
||||
await softwareAPI.install()
|
||||
// successful messgae
|
||||
} catch (e) {
|
||||
renderFlash("error", getErrorMessage(e))
|
||||
}
|
||||
|
||||
/* in helpers.tsx */
|
||||
|
||||
// This function is used to abstract away the details of getting and formatting
|
||||
// the error message we recieve from the API
|
||||
export const getErrorMessage = (e: unknown) => {
|
||||
...
|
||||
|
||||
// return a string or a JSX.Element
|
||||
return "some error message"
|
||||
}
|
||||
```
|
||||
|
||||
## Page routing
|
||||
|
||||
We use React Router directly to navigate between pages. For page components,
|
||||
@@ -377,7 +417,7 @@ React Router (v3) supplies a `router` prop that can be easily accessed.
|
||||
When needed, the `router` object contains a `push` function that redirects
|
||||
a user to whatever page desired. For example:
|
||||
|
||||
```typescript
|
||||
```tsx
|
||||
// page
|
||||
import PATHS from "router/paths";
|
||||
import { InjectedRouter } from "react-router/lib/Router";
|
||||
@@ -403,19 +443,11 @@ const PageOrComponent = ({
|
||||
|
||||
Below are a few need-to-knows about what's available in Fleet's CSS:
|
||||
|
||||
|
||||
|
||||
### Modals
|
||||
|
||||
1) When creating a modal with a form inside, the action buttons (cancel, save, delete, etc.) should
|
||||
be wrapped in the `modal-cta-wrap` class to keep unified styles.
|
||||
|
||||
### Forms
|
||||
|
||||
1) When creating a form, **not** in a modal, use the class `${baseClass}__button-wrap` for the
|
||||
action buttons (cancel, save, delete, etc.) and proceed to style as needed.
|
||||
|
||||
|
||||
## Icons and images
|
||||
|
||||
### Adding icons
|
||||
@@ -438,14 +470,11 @@ The icon should now be available to use with the `Icon` component from the given
|
||||
<Icon name="chevron" />
|
||||
```
|
||||
|
||||
|
||||
|
||||
### File size
|
||||
|
||||
The recommend line limit per page/component is 500 lines. This is only a recommendation.
|
||||
Larger files are to be split into multiple files if possible.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
At a bare minimum, we make every effort to test that components that should render data are doing so
|
||||
@@ -492,4 +521,4 @@ the flash message may register the `push` and immediately hide itself.
|
||||
router.push(newPath);
|
||||
// then flash
|
||||
renderFlash("error", "Something went wrong");
|
||||
```
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user