Components

This section is about how we build React components at Vetspire. It includes some practices to follow and tips to ensure we are all building consistent components.

General considerations for building components

  • New components MUST be functional and not class components. This allows us to build modern components that use the latest technologies such as hooks.

  • Components MUST be named exported, ideally at the declaration level. This makes for a more consistent & readable codebase. It also enforces better naming standards.

  • Components MUST be named imported.

  • We SHOULD follow all the standards defined in the React docs when building components.

Component File Structure

  • All new shared components MUST be built in the /web/shared-components directory.

The recommended approach to building components is as follows:

ExampleComponent
โ”œโ”€โ”€ ExampleSubComponent.tsx
โ”œโ”€โ”€ LargerExampleSubComponent
โ”‚   โ”œโ”€โ”€ SubSubComponent.tsx
โ”‚   โ””โ”€โ”€ index.tsx
โ”œโ”€โ”€ __tests__
โ”œโ”€โ”€ index.tsx
โ””โ”€โ”€ utils
    โ””โ”€โ”€ sharedUtilityFunction.ts

In the above example index.tsx would contain the main export export const ExampleComponent....

For modules with import { ExampleComponent} from "/path/to/ExampleComponent", we favour building focused, small components to contain all related types, styles, and non-shared utility functions within the same file. Utility modules and functions are placed in a utils folder.

Component Structure

  • As noted in the "Styling Components" section, styles SHOULD be extracted into const variables/styled components.

  • Component files that contain multiple components MUST contain the main component at the top of the file.

  • Components MUST have minimal logic in the return/render block. Any logic must be moved outside the component or into the component body

Example of the above:

Correct (as logic is minimal) โœ…

export const MyComponent = ({ listOfProviders }) => {
  return (
    <div className={aSharedClassName}>
      {listOfProviders.map((provider) => (
        <div>{provider.name}</div>
      ))}
    </div>
  );
};

Correct (as logic is complicated) โœ…

export const MyComponent = ({ listOfProviders }) => {
  const renderedProviderContent = useMemo(
    () =>
      listOfProviders.map((provider) => {
        if (provider.name === "something") {
          return <div> something </div>;
        } else {
          // lots more logic in here
        }
      }),
    [listOfProviders]
  );

  return <div className={aSharedClassName}>{renderedProviderContent}</div>;
};

Incorrect (as logic is complicated and so should be pulled out of the return block)โŒ

export const MyComponent = ({ listOfProviders }) => {
  return (
    <div className={aSharedClassName}>
      {listOfProviders.map((provider) => {
        if (provider.name === "something") {
          return <div> something </div>;
        } else {
          // lots more logic in here
        }
      })}
    </div>
  );
};
  • We MUST pull complicated logic out of render prop block.

IncorrectโŒ

<MyComponent
  loading={loading}
  myFunction={() => {
    if (someVariable) {
      return someValue;
    } else {
      if (someOtherValue) {
        return someOtherValue;
      }
      return defaultValue;
    }
  }}
/>

Correct โœ…

const myFunction = () => {
  if (someVariable) {
    return someValue;
  } else {
    if (someOtherValue) {
      return someOtherValue;
    }
    return defaultValue;
  }
};

<MyComponent loading={loading} myFunction={myFunction} />;

Component sections MUST follow this order:

  • Imports

  • Type definition for main component

  • Main component itself

  • Helper functions for main component (or those shared across components in the file)

  • Extracted styles for main component (or those shared across components in the file)

  • Subcomponent type

  • Subcomponent itself

  • Helper functions for subcomponent

  • Extracted styles for subcomponent

Non-comprehensive example below to demonstrate:

import React from "react"
import { css } from "emotion"

import { theme } from "shared-components/theme"

type MyComponentProps = {
  booleanProp: boolean
}

export const MyComponent = ({ booleanProp }: MyComponentProps) => {
  // const thingThatReliesOnPropsUsedWithHooks = useSomeHook() ...
  // ...
  return (
    <div className={aSharedClassName}>
      {shouldShowTruthyThing(booleanProp) && (
        <div>Veracity represents the very essence of truth.</div>
      )}
      <MySubComponent />
    </div>
  )
}

const shouldShowTruthyThing = (booleanValue: boolean): boolean => {
  return booleanValue ? true : false
}

const aSharedClassName = css`
  align-items: center;
  border-top: 1px solid ${theme.defaultUnit * 4}px;
  padding: ${theme.defaultUnit}px ${theme.defaultUnit * 4}px;
`

const MySubComponent = () => {
  <div
    className={`css
        ${aSharedClassName}
        ${mySubComponentClassName}
    `}
  >
    Some Content
  </div>
}

const mySubComponentClassName = css`
  backface-visibility: hidden
  display: flex
  margin: ${theme.defaultUnit * 6}px;
`

Imports

  • We MUST use absolute imports.

Correct โœ…

import something from "path/to/something"

Incorrect โŒ

import something from "../../something"

  • We MUST use named imports/exports (unless in the rare case where it isn't impossible)

Correct โœ…

import { something } from "path/to/something"

Incorrect โŒ

import something from "path/to/something"

  • We MUST alphabetise imports in the following order external, internal, current_directory.

Correct โœ…

import React, { useCallback, useState } from "react" // external
import dayjs from "dayjs" // external

import { ModelType, mutation } from "generated/graphql" // internal

import { Something } from "./Something" // current_directory
  • We MUST avoid circular imports when possible.

Styling Components

  • We MUST use composable styles using emotion.

  • We MUST avoid using the legacy styled-component package.

  • Any inlined style that consist of three or more rules MUST be defined in a const variable for readability and reusability.

Correct โœ…

const styles = css`
  font-weight: bold;
  font-size: 12px;
  color: red
`
<div className={styles} />

Incorrect โŒ

<div className={css`
  font-weight: bold;
  font-size: 12px;
  color: red
`} />
  • We MUST use the defaultValue value for styling.

  • We MUST use colors from theme file.

  • CSS rules MUST appear in alphabetical order.

Last updated