diff --git a/README.md b/README.md index 4db8da2..a8f048f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This is a living repository — nothing is set in stone! If you're member of Met - [Engineering Principles](./docs/engineering-principles.md) - [JavaScript Guidelines](./docs/javascript.md) - [Pull Requests Guide](./docs/pull-requests.md) +- [React Guidelines](./docs/react.md) - [Secure Coding Guidelines](./docs/secure-coding-guidelines.md) - [Redux Guidelines](./docs/redux.md) - [Secure Development Lifecycle Policy](./docs/sdlc.md) diff --git a/docs/react.md b/docs/react.md new file mode 100644 index 0000000..d24231a --- /dev/null +++ b/docs/react.md @@ -0,0 +1,147 @@ +# React Guidelines + +## Performance + +Note that the below are purely optimizations so any code should still function without them. + +### Use `memo` to skip re-rendering child components when the props are unchanged + +React normally re-renders a component whenever its parent re-renders. + +Using [memo](https://react.dev/reference/react/memo) will create a "memoized" component that React will not re-render when its parent re-renders, assuming the props are unchanged. + +If a component has array or object properties, consider using `useMemo` in the parent component as detailed below. + +```typescript +const Greeting = memo(function Greeting({ name }) { + return

Hello, {name}!

; +}); + +export default Greeting; +``` + +### Use `useMemo` to cache values between re-renders + +The [useMemo](https://react.dev/reference/react/useMemo) hook caches a calculation result between re-renders until its dependencies change. + +The most common use case is ensuring object or array properties can retain the same reference and therefore prevent unnecessary re-renders since React does a shallow comparison on component and hook properties. + +While the scenario is rare, expensive recalculation such as iterating large arrays or very complex math can also be avoided, by ensuring it is only executed when the dependencies change. + +This can also be achieved via `useEffect` and `useState` however this is not advised since: + +- More code is required. +- Readability is reduced since the intent is less explicit. +- An additional render is required to generate the first value since `useEffect` callbacks are evaluated after the render rather than during it. + +🚫 + +```typescript +export function TodoList({ todos }) { + const visibleTodos = filterTodos(todos); + + return ( +
+ +
+ ); +} +``` + +🚫 + +```typescript +export function TodoList({ todos }) { + const [visibleTodos, setVisibleTodos] = useState(); + + useEffect(() => setVisibleTodos(filterTodos(todos)), [todos]); + + return ( +
+ +
+ ); +} +``` + +✅ + +```typescript +export function TodoList({ todos }) { + const visibleTodos = useMemo(() => filterTodos(todos), [todos]); + + return ( +
+ +
+ ); +} +``` + +### Use `useCallback` to cache functions between re-renders + +The [useCallback](https://react.dev/reference/react/useCallback) hook serves exactly the same purpose as `useMemo` but is specifically for functions. + +It can also prevent unnecessary re-renders by ensuring a reference to a function prop, such as `onClick`, is only changed when the dependencies change. + +🚫 + +```typescript +function ProductPage({ productId, referrer }) { + const handleSubmit = (orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }; + + return ( +
+ +
+ ); +} +``` + +🚫 + +```typescript +function ProductPage({ productId, referrer }) { + const handleSubmit = useMemo(() => { + return (orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }; + }, [productId, referrer]); + + return ( +
+ +
+ ); +} +``` + +✅ + +```typescript +function ProductPage({ productId, referrer }) { + const handleSubmit = useCallback( + (orderDetails) => { + post('/product/' + productId + '/buy', { + referrer, + orderDetails, + }); + }, + [productId, referrer], + ); + + return ( +
+ +
+ ); +} +```