Skip to main content

React

General

  • Prefer Function Components over Class Components.
  • Create one file per "logical component." Multiple Function Components per file are allowed for structuring, styling, etc.
  • Always use JSX (use React.createElement only for app initialization).
  • Use React.PropsWithChildren instead of defining children manually.
  • Inject dependencies to services/APIs via Context.
  • How to structure folders? "Move files around until it feels right" Recommendation: Separate by modules instead of by type (File Structure – React).

Naming

  • Use PascalCase for React components and camelCase for instances
Bad
import carCard from "./CarCard";
const CarItem = <CarCard />;
Good
import CarCard from "./CarCard";
const carItem = <CarCard />;
  • Use filename as component name
Bad
import Footer from "./Nav";
Good
import Footer from "./Footer";
  • Speaking component filename
    Include the component name in the filename.
Bad
clients / Table.tsx;

clients / Table.sc.ts;

clients / Table.gql.ts;
Good
clients / ClientsTable.tsx;

clients / ClientsTable.sc.ts;

clients / ClientsTable.gql.ts;

Do not use DOM props as component props unless they are intended for that purpose.

Bad
<MyComponent className="fancy" />
Good
<MyComponent variant="fancy" />
  • Always use camelCase for prop names.
Bad
<Foo UserName="hello" phone_number={12345678} />
Good
<Foo userName="hello" phoneNumber={12345678} />
  • Always name React.useState like this: const [variable, setVariable] = React.useState()

Conventions

  • If the property is true, then do not pass the value
Bad
<Foo hidden={true} />
Good
<Foo hidden />
  • Boolean props should always be optional
Bad
type Props = {
hidden: boolean;
};

<Foo hidden={false} />;
Good
type Props = {
visible?: boolean;
};

<Foo />;

Use parameter?: type instead of parameter: type | undefined, otherwise parameter = undefined must be explicitly set.

Recommendations

  • If a component becomes too complex:
    • Move GraphQL and styled components into separate files with .gql.ts and .sc.ts extensions.
      caution

      These files should be treated as private and must not be imported from other files.

    • Split the component into smaller sub-components.

Working with SVGs

If possible, use SVGs inline with <use>.

It is important that the SVG file contains an id, which is then referenced via the hash parameter in the path.

You can use it like this:

Good
<svg>
<use xlinkHref="/icon.svg#custom-id"></use>
</svg>

SVG File:

Good
<svg viewBox="0 0 24 24" id="custom-id" version="1.1" xmlns="http://www.w3.org/2000/svg">
<title>icon</title>
<polygon
fill="currentColor"
points="12.661 11.954 17.904 6.707 17.198 6 11.954 11.247 6.707 6.003 6 6.71 11.247 11.954 6.003 17.2 6.71 17.907 11.954 12.661 17.2 17.904 17.907 17.198"
></polygon>
</svg>
Bad
import Icon from "../assets/icon.svg"

...

<Icon />
Bad
<img src="/icon.svg" />

Background: SVGs imported as modules end up in the JS bundle, which increases download and compile time. Additionally, when used as <img> tags, they cannot be manipulated (e.g., changing path color).

Common Bugs

info

These are common pitfalls, not strict rules that must always be followed.

Bad
{
todos.map((todo, index) => <Todo {...todo} key={index} />);
}
Good
{
todos.map((todo) => <Todo {...todo} key={todo.id} />);
}
  • Use && with 0 or ""
Bad
export default function PostList({ posts }) {
return (
<div>
<ul>{posts.length && posts.map((post) => <PostItem key={post.id} post={post} />)}</ul>
</div>
);
}
  • Renders 0 instead of null for an empty list!
Good
export default function PostList({ posts }) {
return (
<div>
<ul>
{posts.length ? posts.map((post) => <PostItem key={post.id} post={post} />) : null}
</ul>
</div>
);
}

Common Anti-Patterns

Constant components or functions that have no dependencies are created on every render.

Bad
const Foo: React:FC = (jobs) => {
const sortJobs = (a: Job, b: Job) => {
return a.createdAt - b.createdAt;
}

const Wrapper = styled.div`...`;

return <Wrapper>...</Wrapper>;
}

Explanation: Created on every render and can lead to performance issues. (See also Hooks API Reference – React)

Good
const sortJobs = (a: Job, b: Job) => {
return a.createdAt - b.createdAt;
}

const Wrapper = styled.div`...`;

const Foo: React:FC = (jobs) => {
return <Wrapper>...</Wrapper>;
}

GraphQL

Use fragment to define required data of a component (see Colocating Fragments)

Bad
// DisplayName.tsx

function DisplayName({
user: { firstName, lastName }: GQLUserDetailQuery
}): JSX.Element {
return <>{firstName} {lastName}</>
}



// UserDetail.tsx

const userDetailQuery = gql`
query UserDetail($id: ID!) {
user(id: $id) {
firstName
lastName
}
}
`

function UserDetail({ id }: { id: string}): JSX.Element {
const user = useQuery(userDetailQuery);

...

return (
<>
<DisplayName user={user} />
...
</>
)
}
Good
// DisplayName.tsx

export const displayNameFragment = gql`
fragment DisplayName on User {
firstName
lastName
}
`;

function DisplayName({
user: { firstName, lastName }: { user: GQLDisplayNameFragment }
}): JSX.Element {
return <>{firstName} {lastName}</>
}



// UserDetail.tsx

const userDetailQuery = gql`
query UserDetail($id: ID!) {
user(id: $id) {
...DisplayName
}
}

${displayNameFragment}
`

function UserDetail({ id }: { id: string}): JSX.Element {
const user = useQuery(userDetailQuery);

...

return (
<>
<DisplayName user={user} />
...
</>
)
}

Explanation: The child component should define for itself which fields of a GraphQL object it needs (and not rely on the parent component’s query!). This way, the child component is not directly affected by changes in the parent query, for example if a field is no longer queried.

Further reading / sources: