From 52bda8379470a6cb212d456503a078ccb05c1335 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 23 Jan 2025 15:44:53 +0000 Subject: [PATCH] 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 --- frontend/docs/patterns.md | 81 ++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/frontend/docs/patterns.md b/frontend/docs/patterns.md index 0b9c3ee900..d34cb5a0d2 100644 --- a/frontend/docs/patterns.md +++ b/frontend/docs/patterns.md @@ -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 ``` ### 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 ``` + or + ```tsx { 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`) 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) => { evt.preventDefault(); @@ -268,7 +276,7 @@ const onFormSubmit = (evt: React.MouseEvent) => { [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 ``` - - ### 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"); -``` \ No newline at end of file +```