Files
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

8.0 KiB

React

Most of the recommendations here are based on Alex Kondov's Tao of React.

Prefer functional components

React initially introduced class components to leverage JavaScript class syntax for managing state and component lifecycles. However, class components have significant drawbacks:

  • they tend to be verbose;
  • they often involve a misuse of the inheritance mechanism through extends and super.

Hooks were introduced to provide an alternative approach to declaring state and effects. They maintain the core concept of the component's render function without the need for classes, streamlining the development process.

Declare one component per file

While colocation is a commendable concept, it's not consistently followed for React components within a single file. The main reason is that we've noticed people misusing this approach before. It might start with something as straightforward as adding a basic modal component alongside a page component, but it can quickly lead to a confusing jumble of components that becomes difficult to manage.

Name components

Failing to name a component is a common mistake that can lead to prolonged debugging efforts. It results in less informative error stacks and challenges while navigating components in React Dev Tools. There are two approaches to properly name a component:

  1. By assigning the function straight to a binding. The arrow function is itself anonymous, but assigning it to a const makes JavaScript infer the name from the variable:
const Foo = () => {
	return <div>Foo</div>;
};

console.log('The component name is:', Foo.name);
  1. By setting the displayName property. Once a wrapper call sits between the binding and the function, there is nothing for inference to attach to — the arrow is an argument to memo, and memo returns an object rather than a function:
const Foo = memo(() => {
	return <div>Foo</div>;
});

Foo.displayName = 'Foo'; // `Foo.name` is `undefined`

console.log('The component name is:', Foo.displayName);

Use default export at the end of file

While named exports are often preferred, using default export enhances code readability, especially when dealing with Higher Order Components (HOCs) like memo and forwardRef, and it aligns neatly with code splitting using lazy.

// Component.tsx
import { memo } from 'react';

type ComponentProps = {
	name: string;
};

// Anonymous arrow, but the const assignment infers the name "Component"
const Component = (props: ComponentProps) => {
	return <div>Hello, {props.name}</div>;
};

export default memo(Component); // the component name is preserved
// index.ts
import { lazy } from 'react';

const Component = lazy(() => import('./Component'));

The same example based on named exports is less readable:

// Component.tsx
import { memo } from 'react';

type ComponentProps = {
	name: string;
};

// Anonymous arrow in an argument position — no name is inferred
export const Component = memo((props: ComponentProps) => {
	return <div>Hello, {props.name}</div>;
});

Component.displayName = 'Component'; // needed for React Dev Tools
// index.ts
import { lazy } from 'react';

const Component = lazy(async () => {
	const { Component } = await import('./Component');
	return { default: Component }; // you need to reconstruct the default export
});

Extract helper functions

A drawback associated with the adoption of React Hooks is the tendency for individuals to define helper functions within the component. This is facilitated by the fact that there is no requirement to pass arguments; instead, variables from the encompassing scope can be directly accessed and used.

const Component = () => {
	const value = useMyHook();
	const isValueOK = () => value === 'OK';
	return isValueOK() ? <>OK</> : null;
};

Typically, effective helper functions adhere to the principle of being pure, which makes them simpler to debug. However, when you bind variables into the helper's scope, you're making it impure. Furthermore, with each rendering cycle, the function value gets redefined. While this process is efficient in terms of CPU and memory usage, it can lead to scenarios where you must rely on techniques like useCallback to prevent unnecessary re-renders of child components that receive your helper function as a prop.

Here's the ideal case:

const isValueOK = (value) => value === 'OK';

const Component = () => {
	const value = useMyHook();
	return isValueOK(value) ? <>OK</> : null;
};

Do not refer to the children prop explicitly

Children should always be actual children, not passed in as a prop. Use the eslint-plugin-react rule react/no-children-prop as reference.

❌ Incorrect:

export const MyComponent = ({ children }) => <div children={children} />;

✅ Correct:

export const MyComponent = ({ children }) => <div>{children}</div>;

Declare props type explicitly

Each component named, for instance, Component must have an exported type declaration for ComponentProps.

export type MyComponentProps = {
	// ...
};

const MyComponent = (props: MyComponentProps) => /* ... */;

export default MyComponent;

Use generic types when appropriate

Generic types are a great way to ensure type safety once they enable us to not fall into the unsoundness of type assertions.

❌ Incorrect:

// MyComponent.tsx
export type MyComponentProps = {
	value: unknown;
	onChange: (newValue: unknown) => void;
};

const MyComponent = ({ value, onChange }: MyComponentProps) => /* ... */;

export default MyComponent;

// MyApp.tsx
import MyComponent from './MyComponent';

const MyApp = () => {
	const [value, setValue] = useState('');
	const handleChange = (value: unknown) => {
		setValue(value as string); // unsound type assertion, as unknown can be anything
	};
	return <MyComponent value={value} onChange={handleChange} />;
};

export default MyApp;

✅ Correct:

// MyComponent.tsx
export type MyComponentProps<TValue> = {
	value: TValue;
	onChange: (newValue: TValue) => void;
};

const MyComponent = <TValue,>({ value, onChange }: MyComponentProps<TValue>) => /* ... */;

export default MyComponent;

// MyApp.tsx
import MyComponent from './MyComponent';

const MyApp = () => {
	const [value, setValue] = useState('');
	const handleChange = (value: string) => {
		setValue(value);
	};
	// since `value` is string, `onChange` type will be inferred
	// as `(value: string) => void`
	return <MyComponent value={value} onChange={handleChange} />;
};

export default MyApp;

Avoid naming identifiers with their value type/kind

This is a common programming convention, but it seems easier to violate when working with React concepts. In general, identifiers should not include information about the data structure they refer to.

// Examples of bad identifiers
const amountConst = 123; // instead of "amount"
let countNumber = 0; // instead of "count"
function logFunction(lineString: string) {
	// instead of "log" and "line"
	console.log(lineString);
}

Identifiers like this mimic the old-fashioned Hungarian notation. In React, this happens quite often:

// Examples of bad identifiers in React constructs
function useThingHook() {
	// ...
} // instead of "useThing"

const useThingContext = () => useContext(ThingContext);
// "ThingContext" already exposes itself as a context that provides a "Thing".
// "useThing" is good enough, since it doesn't need to expose its context dependency.

const MyComponent = () => <div />;
// Fine for code examples and components passed by props*, but in general
// capital cased function names used in JSX already communicate they are of
// "Component" kind

// * when "Component" is okay as a suffix
type MyPanelProps = {
	panelComponent: ElementType<{}>;
};

function MyPanel({ panelComponent: PanelComponent }: MyPanelProps) {
	// "PanelComponent" is fine here
	return (
		<div className='panel'>
			<PanelComponent />
		</div>
	);
}