I have been working professionally with React for the past +5 years.
In this article, I share the 101 best tips & tricks I learned over the years.
Ready? Let's dive in 💪!
🎁 You can download an e-book version of this article for FREE here 👉 101 React Tips & Tricks
ℹ️ Notes:
- This guide assumes a basic familiarity with React and an understanding of the terms
props
,state
,context
, etc.- I tried to use Vanilla JS for most of the examples to keep things simple. If you're using TypeScript, you can easily adapt the code.
- The code is not production-ready. Use at your own discretion.
Table of contents (Click to expand) ↕️
-
Category #1: Components organization 🧹
- 1. Use self-closing tags to keep your code compact
- 2. Prefer
fragments
over DOM nodes (e.g., div, span, etc.) to group elements
- 3. Use React fragment shorthand
<></>
(except if you need to set a key)
- 4. Prefer spreading props over accessing each one individually
- 5. When setting default values for props, do it while destructuring them
- 6. Drop curly braces when passing
string
type props.
- 7. Ensure that
value
is a boolean before using value && <Component {...props}/>
to prevent displaying unexpected values on the screen.
- 8. Use functions (inline or not) to avoid polluting your scope with intermediate variables
- 9. Use curried functions to reuse logic (and properly memoize callback functions)
- 10. Move data that doesn't rely on the component props/state outside of it for cleaner (and more efficient) code
- 11. When storing the selected item from a list, store the item ID rather than the entire item
- 12. If you're frequently checking a prop's value before doing something, introduce a new component
- 13. Use the CSS
:empty
pseudo-class to hide elements with no children
- 14. Group all the state and context at the top of the component
-
Category #2: Effective Design Patterns & Techniques 🛠 ️
- 15. Leverage the
children
props for cleaner code (and performance benefits)
- 16. Build composable code with
compound components
- 17. Make your code more extensible with
render functions
or component functions
props
- 18. When dealing with different cases, use
value === case && <Component />
to avoid holding onto old state
- 19. Always use error boundaries
-
Category #3: Keys & Refs 🗝️
- 20. Use
crypto.randomUUID
or Math.random
to generate keys
- 21. Make sure your list items IDs are stable (i.e., they don't change between renders)
- 22. Strategically use the
key
attribute to trigger component re-renders
- 23. Use a
ref callback function
for tasks such as monitoring size changes and managing multiple node elements.
-
Category #4: Organizing React code 🧩
-
Category #5: Efficient State Management 🚦
- 28. Never create a state for a value that can be derived from other state or props
- 29. Keep the state at the lowest level necessary to minimize re-renders
- 30. Clarify the distinction between the initial state and the current state
- 31. Update state based on the previous state, especially when memoizing with
useCallback
- 32. Use functions in
useState
for lazy initialization and performance gains, as they are invoked only once.
- 33. Use react context for broadly needed, static state to prevent prop drilling
- 34. React Context: Split your context into parts that change frequently and those that change infrequently to enhance app performance
- 35. React Context: Introduce a
Provider
component when the value computation is not straightforward
- 36. Consider using the
useReducer
hook as a lightweight state management solution
- 37. Simplify state updates with
useImmer
or useImmerReducer
- 38. Use Redux (or another state management solution) for complex client-side state accessed across multiple components
- 39. Redux: Use Redux DevTools to debug your state
-
Category #6: React Code Optimization 🚀
- 40. Prevent unnecessary re-renders with
memo
- 41. Specify an equality function with
memo
to instruct React on how to compare the props.
- 42. Prefer named functions over arrow functions when declaring a memoized component
- 43. Cache expensive computations or preserve references with
useMemo
- 44. Use
useCallback
to memoize functions
- 45. Memoize callbacks or values returned from utility hooks to avoid performance issues
- 46. Leverage lazy loading and
Suspense
to make your apps load faster
- 47. Throttle your network to simulate a slow network
- 48. Use
react-window
or react-virtuoso
to efficiently render lists
-
Category #7: Debugging React code 🐞
-
-
Category #8: Testing React code 🧪
-
Category #9: React hooks 🎣
- 59. Make sure you perform any required cleanup in your
useEffect
hooks
- 60. Use
refs
for accessing DOM elements
- 61. Use
refs
to preserve values across re-renders
- 62. Prefer named functions over arrow functions within hooks such as
useEffect
to easily find them in React Dev Tools
- 63. Encapsulate logic with custom hooks
- 64. Prefer functions over custom hooks
- 65. Prevent visual UI glitches by using the
useLayoutEffect
hook
- 66. Generate unique IDs for accessibility attributes with the
useId
hook
- 67. Use the
useSyncExternalStore
to subscribe to an external store
- 68. Use the
useDeferredValue
hook to display the previous query results until the new results become available
-
Category #10: Must-known React Libraries/Tools 🧰
- 69. Incorporate routing into your app with
react-router
- 70. Implement first-class data fetching in your app with
swr
or React Query
- 71. Simplify form state management with libraries like
formik
, React Hook Form
, or TanStack Form
- 72. Internationalize your app using
Format.js,
Lingui,
or react-i18next.
- 73. Effortlessly create impressive animations with
framer-motion
- 74. Tired of re-inventing the wheel with custom hooks? Check out https://usehooks.com/
- 75. Streamline app development by leveraging UI libraries like Shadcdn or Headless UI
- 76. Check your website's accessibility with the
axe-core-npm
library
- 77. Refactor React code effortlessly with
react-codemod
- 78. Transform your app into a Progressive Web Application (PWA) using vite-pwa
-
Category #11: React & Visual Studio Code 🛠️
-
Category #12: React & TypeScript 🚀
- 82. Use
ReactNode
instead of JSX.Element | null | undefined | ...
to keep your code more compact
- 83. Simplify the typing of components expecting children props with
PropsWithChildren
- 84. Access element props efficiently with
ComponentProps
, ComponentPropsWithoutRef
,…
- 85. Leverage types like
MouseEventHandler
, FocusEventHandler
and others for concise typings
- 86. Specify types explicitly in useState, useRef, etc., when the type can't be or shouldn't be inferred from the initial value
- 87. Leverage the
Record
type for cleaner and more extensible code
- 88. Use the
as const
trick to accurately type your hook return values
- 89. Redux: Ensure proper typing by referring to https://react-redux.js.org/using-react-redux/usage-with-typescript to correctly type your Redux state and helpers
- 90. Simplify your types with
ComponentType
- 91. Make your code more reusable with TypeScript generics
- 92. Ensure precise typing with the
NoInfer
utility type
- 93. Effortlessly type refs with the
ElementRef
type helper
-
Category #13: Miscellaneous Tips 🎉
- 94. Boost your code's quality and safety with
eslint-plugin-react
and Prettier.
- 95. Log and monitor your app with tools like Sentry or Grafana Cloud Frontend Observability.
- 96. Start coding quickly with online IDEs like Code Sandbox or Stackblitz
- 97. Looking for advanced react skills? Check out these books 👇
- 98. Prepping React interviews? Check reactjs-interview-questions
- 99. Learn React best practices from experts like Nadia, Dan, Josh, Kent, etc.
- 100. Stay updated with the React ecosystem by subscribing to newsletters like This Week In React or ui.dev
-
101. Engage with the React community on platforms like r/reactjs
Category #1: Components organization 🧹
1. Use self-closing tags to keep your code compact
Table of contents (Click to expand) ↕️
-
Category #1: Components organization 🧹
- 1. Use self-closing tags to keep your code compact
- 2. Prefer
fragments
over DOM nodes (e.g., div, span, etc.) to group elements - 3. Use React fragment shorthand
<></>
(except if you need to set a key) - 4. Prefer spreading props over accessing each one individually
- 5. When setting default values for props, do it while destructuring them
- 6. Drop curly braces when passing
string
type props. - 7. Ensure that
value
is a boolean before usingvalue && <Component {...props}/>
to prevent displaying unexpected values on the screen. - 8. Use functions (inline or not) to avoid polluting your scope with intermediate variables
- 9. Use curried functions to reuse logic (and properly memoize callback functions)
- 10. Move data that doesn't rely on the component props/state outside of it for cleaner (and more efficient) code
- 11. When storing the selected item from a list, store the item ID rather than the entire item
- 12. If you're frequently checking a prop's value before doing something, introduce a new component
- 13. Use the CSS
:empty
pseudo-class to hide elements with no children - 14. Group all the state and context at the top of the component
-
Category #2: Effective Design Patterns & Techniques 🛠 ️
- 15. Leverage the
children
props for cleaner code (and performance benefits) - 16. Build composable code with
compound components
- 17. Make your code more extensible with
render functions
orcomponent functions
props - 18. When dealing with different cases, use
value === case && <Component />
to avoid holding onto old state - 19. Always use error boundaries
- 15. Leverage the
-
Category #3: Keys & Refs 🗝️
- 20. Use
crypto.randomUUID
orMath.random
to generate keys - 21. Make sure your list items IDs are stable (i.e., they don't change between renders)
- 22. Strategically use the
key
attribute to trigger component re-renders - 23. Use a
ref callback function
for tasks such as monitoring size changes and managing multiple node elements.
- 20. Use
- Category #4: Organizing React code 🧩
-
Category #5: Efficient State Management 🚦
- 28. Never create a state for a value that can be derived from other state or props
- 29. Keep the state at the lowest level necessary to minimize re-renders
- 30. Clarify the distinction between the initial state and the current state
- 31. Update state based on the previous state, especially when memoizing with
useCallback
- 32. Use functions in
useState
for lazy initialization and performance gains, as they are invoked only once. - 33. Use react context for broadly needed, static state to prevent prop drilling
- 34. React Context: Split your context into parts that change frequently and those that change infrequently to enhance app performance
- 35. React Context: Introduce a
Provider
component when the value computation is not straightforward - 36. Consider using the
useReducer
hook as a lightweight state management solution - 37. Simplify state updates with
useImmer
oruseImmerReducer
- 38. Use Redux (or another state management solution) for complex client-side state accessed across multiple components
- 39. Redux: Use Redux DevTools to debug your state
-
Category #6: React Code Optimization 🚀
- 40. Prevent unnecessary re-renders with
memo
- 41. Specify an equality function with
memo
to instruct React on how to compare the props. - 42. Prefer named functions over arrow functions when declaring a memoized component
- 43. Cache expensive computations or preserve references with
useMemo
- 44. Use
useCallback
to memoize functions - 45. Memoize callbacks or values returned from utility hooks to avoid performance issues
- 46. Leverage lazy loading and
Suspense
to make your apps load faster - 47. Throttle your network to simulate a slow network
- 48. Use
react-window
orreact-virtuoso
to efficiently render lists
- 40. Prevent unnecessary re-renders with
- Category #7: Debugging React code 🐞
- Category #8: Testing React code 🧪
-
Category #9: React hooks 🎣
- 59. Make sure you perform any required cleanup in your
useEffect
hooks - 60. Use
refs
for accessing DOM elements - 61. Use
refs
to preserve values across re-renders - 62. Prefer named functions over arrow functions within hooks such as
useEffect
to easily find them in React Dev Tools - 63. Encapsulate logic with custom hooks
- 64. Prefer functions over custom hooks
- 65. Prevent visual UI glitches by using the
useLayoutEffect
hook - 66. Generate unique IDs for accessibility attributes with the
useId
hook - 67. Use the
useSyncExternalStore
to subscribe to an external store - 68. Use the
useDeferredValue
hook to display the previous query results until the new results become available
- 59. Make sure you perform any required cleanup in your
-
Category #10: Must-known React Libraries/Tools 🧰
- 69. Incorporate routing into your app with
react-router
- 70. Implement first-class data fetching in your app with
swr
orReact Query
- 71. Simplify form state management with libraries like
formik
,React Hook Form
, orTanStack Form
- 72. Internationalize your app using
Format.js,
Lingui,
orreact-i18next.
- 73. Effortlessly create impressive animations with
framer-motion
- 74. Tired of re-inventing the wheel with custom hooks? Check out https://usehooks.com/
- 75. Streamline app development by leveraging UI libraries like Shadcdn or Headless UI
- 76. Check your website's accessibility with the
axe-core-npm
library - 77. Refactor React code effortlessly with
react-codemod
- 78. Transform your app into a Progressive Web Application (PWA) using vite-pwa
- 69. Incorporate routing into your app with
- Category #11: React & Visual Studio Code 🛠️
-
Category #12: React & TypeScript 🚀
- 82. Use
ReactNode
instead ofJSX.Element | null | undefined | ...
to keep your code more compact - 83. Simplify the typing of components expecting children props with
PropsWithChildren
- 84. Access element props efficiently with
ComponentProps
,ComponentPropsWithoutRef
,… - 85. Leverage types like
MouseEventHandler
,FocusEventHandler
and others for concise typings - 86. Specify types explicitly in useState, useRef, etc., when the type can't be or shouldn't be inferred from the initial value
- 87. Leverage the
Record
type for cleaner and more extensible code - 88. Use the
as const
trick to accurately type your hook return values - 89. Redux: Ensure proper typing by referring to https://react-redux.js.org/using-react-redux/usage-with-typescript to correctly type your Redux state and helpers
- 90. Simplify your types with
ComponentType
- 91. Make your code more reusable with TypeScript generics
- 92. Ensure precise typing with the
NoInfer
utility type - 93. Effortlessly type refs with the
ElementRef
type helper
- 82. Use
-
Category #13: Miscellaneous Tips 🎉
- 94. Boost your code's quality and safety with
eslint-plugin-react
and Prettier. - 95. Log and monitor your app with tools like Sentry or Grafana Cloud Frontend Observability.
- 96. Start coding quickly with online IDEs like Code Sandbox or Stackblitz
- 97. Looking for advanced react skills? Check out these books 👇
- 98. Prepping React interviews? Check reactjs-interview-questions
- 99. Learn React best practices from experts like Nadia, Dan, Josh, Kent, etc.
- 100. Stay updated with the React ecosystem by subscribing to newsletters like This Week In React or ui.dev
- 101. Engage with the React community on platforms like r/reactjs
- 94. Boost your code's quality and safety with
// ❌ Bad: too verbose
<MyComponent></MyComponent>
// ✅ Good
<MyComponent/>
Back to top ⬆️
2. Prefer fragments
over DOM nodes (e.g., div, span, etc.) to group elements
In React, each component must return a single element. Instead of wrapping multiple elements in a <div>
or <span>
, use <Fragment>
to keep your DOM neat and tidy.
❌ Bad: Using div
clutters your DOM and may require more CSS code.
function Dashboard() {
return (
<div>
<Header />
<Main />
</div>
);
}
✅ Good: <Fragment>
wraps elements without affecting the DOM structure.
function Dashboard() {
return (
<Fragment>
<Header />
<Main />
</Fragment>
);
}
Back to top ⬆️
3. Use React fragment shorthand <></>
(except if you need to set a key)
❌ Bad: The code below is unnecessarily verbose.
<Fragment>
<FirstChild />
<SecondChild />
</Fragment>
✅ Good: Unless, you need a key
, <></>
is more concise.
<>
<FirstChild />
<SecondChild />
</>
// Using a `Fragment` here is required because of the key.
function List({ users }) {
return (
<div>
{users.map((user) => (
<Fragment key={user.id}>
<span>{user.name}</span>
<span>{user.occupation}</span>
</Fragment>
))}
</div>
);
}
Back to top ⬆️
4. Prefer spreading props over accessing each one individually
❌ Bad: The code below is harder to read (especially at scale).
// We do `props…` all over the code.
function TodoList(props) {
return (
<div>
{props.todos.map((todo) => (
<div key={todo}>
<button
onClick={() => props.onSelectTodo(todo)}
style={{
backgroundColor: todo === props.selectedTodo ? "gold" : undefined,
}}
>
<span>{todo}</span>
</button>
</div>
))}
</div>
);
}
✅ Good: The code below is more concise.
function TodoList({ todos, selectedTodo, onSelectTodo }) {
return (
<div>
{todos.map((todo) => (
<div key={todo}>
<button
onClick={() => onSelectTodo(todo)}
style={{
backgroundColor: todo === selectedTodo ? "gold" : undefined,
}}
>
<span>{todo}</span>
</button>
</div>
))}
</div>
);
}
Back to top ⬆️
5. When setting default values for props, do it while destructuring them
❌ Bad: You may need to define the defaults in multiple places and introduce new variables.
function Button({ onClick, text, small, colorScheme }) {
let scheme = colorScheme || "light";
let isSmall = small || false;
return (
<button
onClick={onClick}
style={{
color: scheme === "dark" ? "white" : "black",
fontSize: isSmall ? "12px" : "16px",
}}
>
{text ?? "Click here"}
</button>
);
}
✅ Good: You can set all your defaults in one place at the top. This makes it easy for someone to locate them.
function Button({
onClick,
text = "Click here",
small = false,
colorScheme = "light",
}) {
return (
<button
onClick={onClick}
style={{
color: colorScheme === "dark" ? "white" : "black",
fontSize: small ? "12px" : "16px",
}}
>
{text}
</button>
);
}
Back to top ⬆️
6. Drop curly braces when passing string
type props.
// ❌ Bad: curly braces are not needed
<Button text={"Click me"} colorScheme={"dark"} />
// ✅ Good
<Button text="Click me" colorScheme="dark" />
Back to top ⬆️
7. Ensure that value
is a boolean before using value && <Component {...props}/>
to prevent displaying unexpected values on the screen.
❌ Bad: When the list is empty, 0
will be printed on the screen.
export function ListWrapper({ items, selectedItem, setSelectedItem }) {
return (
<div className="list">
{items.length && ( // `0` if the list is empty
<List
items={items}
onSelectItem={setSelectedItem}
selectedItem={selectedItem}
/>
)}
</div>
);
}
✅ Good: Nothing will be printed on the screen when there are no items.
export function ListWrapper({ items, selectedItem, setSelectedItem }) {
return (
<div className="list">
{items.length > 0 && (
<List
items={items}
onSelectItem={setSelectedItem}
selectedItem={selectedItem}
/>
)}
</div>
);
}
Back to top ⬆️
8. Use functions (inline or not) to avoid polluting your scope with intermediate variables
❌ Bad: The variables gradeSum
and gradeCount
are cluttering the component's scope.
function Grade({ grades }) {
if (grades.length === 0) {
return <>No grades available.</>;
}
let gradeSum = 0;
let gradeCount = 0;
grades.forEach((grade) => {
gradeCount++;
gradeSum += grade;
});
const averageGrade = gradeSum / gradeCount;
return <>Average Grade: {averageGrade}</>;
}
✅ Good: The variables gradeSum
and gradeCount
are scoped within computeAverageGrade
function.
function Grade({ grades }) {
if (grades.length === 0) {
return <>No grades available.</>;
}
const computeAverageGrade = () => {
let gradeSum = 0;
let gradeCount = 0;
grades.forEach((grade) => {
gradeCount++;
gradeSum += grade;
});
return gradeSum / gradeCount;
};
return <>Average Grade: {computeAverageGrade()}</>;
}
💡 Note: you can also define a function
computeAverageGrade
outside the component and call it inside it.
Back to top ⬆️
9. Use curried functions to reuse logic (and properly memoize callback functions)
❌ Bad: The logic for updating a field is very repetitive.
function Form() {
const [{ name, email }, setFormState] = useState({
name: "",
email: "",
});
return (
<>
<h1>Class Registration Form</h1>
<form>
<label>
Name:{" "}
<input
type="text"
value={name}
onChange={(evt) =>
setFormState((formState) => ({
...formState,
name: evt.target.value,
}))
}
/>
</label>
<label>
Email:{" "}
<input
type="email"
value={email}
onChange={(evt) =>
setFormState((formState) => ({
...formState,
email: evt.target.value,
}))
}
/>
</label>
</form>
</>
);
}
✅ Good: Introduce createFormValueChangeHandler
that returns the correct handler for each field.
Note: This trick is especially nice if you have the ESLint rule jsx-no-bind turned on. You can just wrap the curried function inside
useCallback
and "Voilà!".
function Form() {
const [{ name, email }, setFormState] = useState({
name: "",
email: "",
});
const createFormValueChangeHandler = (field) => {
return (event) => {
setFormState((formState) => ({
...formState,
[field]: event.target.value,
}));
};
};
return (
<>
<h1>Class Registration Form</h1>
<form>
<label>
Name:{" "}
<input
type="text"
value={name}
onChange={createFormValueChangeHandler("name")}
/>
</label>
<label>
Email:{" "}
<input
type="email"
value={email}
onChange={createFormValueChangeHandler("email")}
/>
</label>
</form>
</>
);
}
Back to top ⬆️
10. Move data that doesn't rely on the component props/state outside of it for cleaner (and more efficient) code
❌ Bad: OPTIONS
and renderOption
don't need to be inside the component because they don't depend on any props or state.
Also, keeping them inside means we get new object references every time the component renders. If we were to pass renderOption
to a child component wrapped in memo
, it would break the memoization.
function CoursesSelector() {
const OPTIONS = ["Maths", "Literature", "History"];
const renderOption = (option: string) => {
return <option>{option}</option>;
};
return (
<select>
{OPTIONS.map((opt) => (
<Fragment key={opt}>{renderOption(opt)}</Fragment>
))}
</select>
);
}
✅ Good: Move them out of the component to keep the component clean and references stable.
const OPTIONS = ["Maths", "Literature", "History"];
const renderOption = (option: string) => {
return <option>{option}</option>;
};
function CoursesSelector() {
return (
<select>
{OPTIONS.map((opt) => (
<Fragment key={opt}>{renderOption(opt)}</Fragment>
))}
</select>
);
}
💡 Note: In this example, you can simplify further by using the option element inline.
const OPTIONS = ["Maths", "Literature", "History"];
function CoursesSelector() {
return (
<select>
{OPTIONS.map((opt) => (
<option key={opt}>{opt}</option>
))}
</select>
);
}
Back to top ⬆️
11. When storing the selected item from a list, store the item ID rather than the entire item
❌ Bad: If an item is selected but then it changes (i.e., we receive a completely new object reference for the same ID), or if the item is no longer present in the list, selectedItem
will either retain an outdated value or become incorrect.
function ListWrapper({ items }) {
// We are referencing the entire item
const [selectedItem, setSelectedItem] = useState<Item | undefined>();
return (
<>
{selectedItem != null && <div>{selectedItem.name}</div>}
<List
items={items}
selectedItem={selectedItem}
onSelectItem={setSelectedItem}
/>
</>
);
}
✅ Good: We store the selected item by its ID (which should be stable). This ensures that even if the item is removed from the list or one of its properties changed, the UI should be correct.
function ListWrapper({ items }) {
const [selectedItemId, setSelectedItemId] = useState<number | undefined>();
// We derive the selected item from the list
const selectedItem = items.find((item) => item.id === selectedItemId);
return (
<>
{selectedItem != null && <div>{selectedItem.name}</div>}
<List
items={items}
selectedItemId={selectedItemId}
onSelectItem={setSelectedItemId}
/>
</>
);
}
Back to top ⬆️
12. If you're frequently checking a prop's value before doing something, introduce a new component
❌ Bad: The code is cluttered because of all the user == null
checks.
Here, we can't return early because of the rules of hooks.
function Posts({ user }) {
// Due to the rules of hooks, `posts` and `handlePostSelect` must be declared before the `if` statement.
const posts = useMemo(() => {
if (user == null) {
return [];
}
return getUserPosts(user.id);
}, [user]);
const handlePostSelect = useCallback(
(postId) => {
if (user == null) {
return;
}
// TODO: Do something
},
[user]
);
if (user == null) {
return null;
}
return (
<div>
{posts.map((post) => (
<button key={post.id} onClick={() => handlePostSelect(post.id)}>
{post.title}
</button>
))}
</div>
);
}
✅ Good: We introduce a new component, UserPosts
, that takes a defined user and is much cleaner.
function Posts({ user }) {
if (user == null) {
return null;
}
return <UserPosts user={user} />;
}
function UserPosts({ user }) {
const posts = useMemo(() => getUserPosts(user.id), [user.id]);
const handlePostSelect = useCallback(
(postId) => {
// TODO: Do something
},
[user]
);
return (
<div>
{posts.map((post) => (
<button key={post.id} onClick={() => handlePostSelect(post.id)}>
{post.title}
</button>
))}
</div>
);
}
Back to top ⬆️
13. Use the CSS :empty
pseudo-class to hide elements with no children
In the example below 👇, a wrapper takes children and adds a red border around them.
function PostWrapper({ children }) {
return <div className="posts-wrapper">{children}</div>;
}
.posts-wrapper {
border: solid 1px red;
}
❌ Problem: The border remains visible on the screen even if the children are empty (i.e., equal to null
, undefined
, etc.).
✅ Solution: Use the :empty
CSS pseudo-class to ensure the wrapper is not displayed when it's empty.
.posts-wrapper:empty {
display: none;
}
Back to top ⬆️
14. Group all the state and context at the top of the component
When all the state and context are located at the top, it is easy to spot what can trigger a component re-render.
❌ Bad: State and context are scattered, making it hard to track.
function App() {
const [email, setEmail] = useState("");
const onEmailChange = (event) => {
setEmail(event.target.value);
};
const [password, setPassword] = useState("");
const onPasswordChange = (event) => {
setPassword(event.target.value);
};
const theme = useContext(ThemeContext);
return (
<div className={`App ${theme}`}>
<h1>Welcome</h1>
<p>
Email: <input type="email" value={email} onChange={onEmailChange} />
</p>
<p>
Password:{" "}
<input type="password" value={password} onChange={onPasswordChange} />
</p>
</div>
);
}
✅ Good: All state and context are grouped at the top, making it easy to spot.
function App() {
const theme = useContext(ThemeContext);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const onEmailChange = (event) => {
setEmail(event.target.value);
};
const onPasswordChange = (event) => {
setPassword(event.target.value);
};
return (
<div className={`App ${theme}`}>
<h1>Welcome</h1>
<p>
Email: <input type="email" value={email} onChange={onEmailChange} />
</p>
<p>
Password:{" "}
<input type="password" value={password} onChange={onPasswordChange} />
</p>
</div>
);
}
Back to top ⬆️
Category #2: Effective Design Patterns & Techniques 🛠 ️
15. Leverage the children
props for cleaner code (and performance benefits)
Using the children
props has several benefits:
- Benefit #1: You can avoid prop drilling by passing props directly to children components instead of routing them through the parent.
- Benefit #2: Your code is more extensible since you can easily modify children without changing the parent component.
- Benefit #3: You can use this trick to avoid re-rendering "slow" components (see in the example below 👇).
❌ Bad: MyVerySlowComponent
renders whenever Dashboard
renders, which happens every time the current time updates. You can see it in the next image, where I use React Developer Tool's profiler.
function App() {
// Some other logic…
return (
<Dashboard />
);
}
function Dashboard() {
const [currentTime, setCurrentTime] = useState(new Date());
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentTime(new Date());
}, 1_000);
return () => clearInterval(intervalId);
}, []);
return (
<>
<h1>{currentTime.toTimeString()}</h1>
<MyVerySlowComponent /> {/* Renders whenever `Dashboard` renders */}
</>
);
}
✅ Good: MyVerySlowComponent
doesn't render when Dashboard
renders.
function App() {
return (
<Dashboard >
<MyVerySlowComponent />
</Dashboard>
);
}
function Dashboard({ children }) {
const [currentTime, setCurrentTime] = useState(new Date());
useEffect(() => {
const intervalId = setInterval(() => {
setCurrentTime(new Date());
}, 1_000);
return () => clearInterval(intervalId);
}, []);
return (
<>
<h1>{currentTime.toTimeString()}</h1>
{children}
</>
);
}
Back to top ⬆️
16. Build composable code with compound components
Think of compound components as Lego blocks.
You piece them together to create a customized UI. These components work exceptionally well when creating libraries, resulting in both expressive and highly extendable code.
You can explore this pattern further here 👉 Compound Pattern
Example from reach.ui (Menu, MenuButton, MenuList, MenuLink are compound components)
<Menu>
<MenuButton>
Actions <span aria-hidden>▾</span>
</MenuButton>
<MenuList>
<MenuItem onSelect={() => alert("Download")}>Download</MenuItem>
<MenuItem onSelect={() => alert("Copy")}>Create a Copy</MenuItem>
<MenuLink as="a" href="https://reacttraining.com/workshops/">
Attend a Workshop
</MenuLink>
</MenuList>
</Menu>
Back to top ⬆️
17. Make your code more extensible with render functions
or component functions
props
Let's say we want to display various lists, such as messages, profiles, or posts, and each one should be sortable.
To achieve this, we introduce a List
component for reuse. There are two ways we can go around this:
❌ Bad: Option 1
List
handles rendering each item and how they are sorted. This is problematic as it violates the Open Closed Principle. Whenever a new item type is added, this code will be modified.
✅ Good: Option 2
List
takes render functions or component functions, invoking them only when needed.
You can find an example in this sandbox below 👇:
Back to top ⬆️
18. When dealing with different cases, use value === case && <Component />
to avoid holding onto old state
❌ Problem: In the sandbox below, the counter doesn't reset when switching between Posts
and Snippets
. This happens because when rendering the same component, its state persists across type changes.
✅ Solution: Render a component based on the selectedType
or use a key to force a reset when the type changes.
function App() {
const [selectedType, setSelectedType] = useState<ResourceType>("posts");
return (
<>
<Navbar selectedType={selectedType} onSelectType={setSelectedType} />
{selectedType === "posts" && <Resource type="posts" />}
{selectedType === "snippets" && <Resource type="snippets" />}
</>
);
}
// We use the `selectedType` as a key
function App() {
const [selectedType, setSelectedType] = useState<ResourceType>("posts");
return (
<>
<Navbar selectedType={selectedType} onSelectType={setSelectedType} />
<Resource type={selectedType} key={selectedType} />
</>
);
}
Back to top ⬆️
19. Always use error boundaries
By default, if your application encounters an error during rendering, the entire UI crashes 💥.
To prevent this, use error boundaries to:
- Keep parts of your app functional even if an error occurs.
- Display user-friendly error messages and optionally track errors.
💡 Tip: you can use the react-error-boundary library.
Back to top ⬆️
Category #3: Keys & Refs 🗝️
20. Use crypto.randomUUID
or Math.random
to generate keys
JSX elements inside a map()
call always need keys.
Suppose your elements don't already have keys. In that case, you can generate unique IDs using crypto.randomUUID
, Math.random
, or the uuid library.
Note: Beware that
crypto.randomUUID
is not defined in older browsers.
Back to top ⬆️
21. Make sure your list items IDs are stable (i.e., they don't change between renders)
As much as possible, keys/IDs should be stable.
Otherwise, React may re-render some components uselessly, or selections will no longer be valid, like in the example below.
❌ Bad: selectedQuoteId
changes whenever App
renders, so there is never a valid selection.
function App() {
const [quotes, setQuotes] = useState([]);
const [selectedQuoteId, setSelectedQuoteId] = useState(undefined);
// Fetch quotes
useEffect(() => {
const loadQuotes = () =>
fetchQuotes().then((result) => {
setQuotes(result);
});
loadQuotes();
}, []);
// Add ids: this is bad!!!
const quotesWithIds = quotes.map((quote) => ({
value: quote,
id: crypto.randomUUID(),
}));
return (
<List
items={quotesWithIds}
selectedItemId={selectedQuoteId}
onSelectItem={setSelectedQuoteId}
/>
);
}
✅ Good: The IDs
are added when we get the quotes.
function App() {
const [quotes, setQuotes] = useState([]);
const [selectedQuoteId, setSelectedQuoteId] = useState(undefined);
// Fetch quotes and save with ID
useEffect(() => {
const loadQuotes = () =>
fetchQuotes().then((result) => {
// We add the `ids` as soon as we get the results
setQuotes(
result.map((quote) => ({
value: quote,
id: crypto.randomUUID(),
}))
);
});
loadQuotes();
}, []);
return (
<List
items={quotes}
selectedItemId={selectedQuoteId}
onSelectItem={setSelectedQuoteId}
/>
);
}
Back to top ⬆️
22. Strategically use the key
attribute to trigger component re-renders
Want to force a component to re-render from scratch? Just change its key
.
In the example below, we use this trick to reset the error boundary when switching to a new tab.
Back to top ⬆️
23. Use a ref callback function
for tasks such as monitoring size changes and managing multiple node elements.
Did you know you can pass a function to the ref
attribute instead of a ref object?
Here's how it works:
- When the DOM node is added to the screen, React calls the function with the DOM node as the argument.
- When the DOM node is removed, React calls the function with
null
.
In the example below, we use this tip to skip the useEffect
❌ Before: Using useEffect
to focus the input
function App() {
const ref = useRef();
useEffect(() => {
ref.current?.focus();
}, []);
return <input ref={ref} type="text" />;
}
✅ After: We focus on the input as soon as it is available.
function App() {
const ref = useCallback((inputNode) => {
inputNode?.focus();
}, []);
return <input ref={ref} type="text" />;
}
Back to top ⬆️
Category #4: Organizing React code 🧩
24. Colocate React components with their assets (e.g., styles, images, etc.)
Always keep each React component with related assets, like styles and images.
- This makes it easier to remove them when the component is no longer needed.
- It also simplifies code navigation, as everything you need is in one place.
Back to top ⬆️
25. Limit your component file size
Big files with tons of components and exports can be confusing.
Plus, they tend to grow even bigger as more stuff gets added.
So, aim for a reasonable file size and split components into separate files when it makes sense.
Back to top ⬆️
26. Limit the number of return statements in your functional component file
Multiple return
statements in a functional component make it hard to see what the component is returning.
This was not a problem for class components where we could search for the render
term.
A handy trick is to use arrow functions without braces when possible (VSCode has an action for this 😀).
❌ Bad: It is harder to spot the component return statement
function Dashboard({ posts, searchTerm, onPostSelect }) {
const filteredPosts = posts.filter((post) => {
return post.title.includes(searchTerm);
});
const createPostSelectHandler = (post) => {
return () => {
onPostSelect(post.id);
};
};
return (
<>
<h1>Posts</h1>
<ul>
{filteredPosts.map((post) => {
return (
<li key={post.id} onClick={createPostSelectHandler(post)}>
{post.title}
</li>
);
})}
</ul>
</>
);
}
✅ Good: There is one return statement for the component
function Dashboard({ posts, searchTerm, onPostSelect, selectedPostId }) {
const filteredPosts = posts.filter((post) => post.title.includes(searchTerm));
const createPostSelectHandler = (post) => () => {
onPostSelect(post.id);
};
return (
<>
<h1>Posts</h1>
<ul>
{filteredPosts.map((post) => (
<li
key={post.id}
onClick={createPostSelectHandler(post)}
style={{ color: post.id === selectedPostId ? "red" : "black" }}
>
{post.title}
</li>
))}
</ul>
</>
);
}
Back to top ⬆️
27. Prefer named exports over default exports
I see default exports everywhere, and it makes me sad 🥲.
Let's compare the two approaches:
/// `Dashboard` is exported as the default component
export default function Dashboard(props) {
/// TODO
}
/// `Dashboard` export is named
export function Dashboard(props) {
/// TODO
}
We now import the component like this:
/// Default export
import Dashboard from "/path/to/Dashboard"
/// Named export
import { Dashboard } from "/path/to/Dashboard"
These are the problems with default exports:
- If the component is renamed, the IDE won't automatically rename the export.
For example, if Dashboard
is renamed into Console
, we will have this:
/// In the default export case, the name is not changed
import Dashboard from "/path/to/Console"
/// In the named export case, the name is changed
import { Console } from "/path/to/Console"
- It's harder to see what is exported from a file with default exports.
For example, in the case of named imports, once I type import { } from "/path/to/file"
, I get autocompletion when I set my cursor inside the brackets.
- Default exports are harder to re-export.
For example, if I wanted to re-export the Dashboard
component from let's say an index
file, I would have to do this:
export { default as Dashboard } from "/path/to/Dashboard"
The solution is more straightforward with named exports.
export { Dashboard } from "/path/to/Dashboard"
So, please default to named exports 🙏.
💡 Note: Even if you're using React lazy you can still use named exports. See an example here.
Back to top ⬆️
Category #5: Efficient State Management 🚦
28. Never create a state for a value that can be derived from other state or props
More state = more trouble.
Every piece of state can trigger a re-render and make resetting the state a hassle.
So, if a value can be derived from state or props, skip adding a new state.
❌ Bad: filteredPosts
doesn't need to be in the state.
function App({ posts }) {
const [filters, setFilters] = useState();
const [filteredPosts, setFilteredPosts] = useState([]);
useEffect(
() => {
setFilteredPosts(filterPosts(posts, filters));
},
[posts, filters]
);
return (
<Dashboard>
<Filters filters={filters} onFiltersChange={setFilters} />
{filteredPosts.length > 0 && <Posts posts={filteredPosts} />}
</Dashboard>
);
}
✅ Good: filteredPosts
is derived from posts
and filters.
function App({ posts }) {
const [filters, setFilters] = useState({});
const filteredPosts = filterPosts(posts, filters)
return (
<Dashboard>
<Filters filters={filters} onFiltersChange={setFilters} />
{filteredPosts.length > 0 && <Posts posts={filteredPosts} />}
</Dashboard>
);
}
Back to top ⬆️
29. Keep the state at the lowest level necessary to minimize re-renders
Whenever the state changes inside a component, React re-renders the component and all its children (there is an exception with children wrapped in memo).
This happens even if those children don't use the changed state. To minimize re-renders, move the state down the component tree as far as possible.
❌ Bad: When sortOrder
changes, both LeftSidebar
and RightSidebar
re-render.
function App() {
const [sortOrder, setSortOrder] = useState("popular");
return (
<div className="App">
<LeftSidebar />
<Main sortOrder={sortOrder} setSortOrder={setSortOrder} />
<RightSidebar />
</div>
);
}
function Main({ sortOrder, setSortOrder }) {
return (
<div>
<Button
onClick={() => setSortOrder("popular")}
active={sortOrder === "popular"}
>
Popular
</Button>
<Button
onClick={() => setSortOrder("latest")}
active={sortOrder === "latest"}
>
Latest
</Button>
</div>
);
}
✅ Good: sortOrder
change will only affect Main
.
function App() {
return (
<div className="App">
<LeftSidebar />
<Main />
<RightSidebar />
</div>
);
}
function Main() {
const [sortOrder, setSortOrder] = useState("popular");
return (
<div>
<Button
onClick={() => setSortOrder("popular")}
active={sortOrder === "popular"}
>
Popular
</Button>
<Button
onClick={() => setSortOrder("latest")}
active={sortOrder === "latest"}
>
Latest
</Button>
</div>
);
}
Back to top ⬆️
30. Clarify the distinction between the initial state and the current state
❌ Bad: It's unclear that sortOrder is just the initial value, which may lead to confusion or errors in state management.
function Main({ sortOrder }) {
const [internalSortOrder, setInternalSortOrder] = useState(sortOrder);
return (
<div>
<Button
onClick={() => setInternalSortOrder("popular")}
active={internalSortOrder === "popular"}
>
Popular
</Button>
<Button
onClick={() => setInternalSortOrder("latest")}
active={internalSortOrder === "latest"}
>
Latest
</Button>
</div>
);
}
✅ Good: Naming is clear about what is the initial state and what is current.
function Main({ initialSortOrder }) {
const [sortOrder, setSortOrder] = useState(initialSortOrder);
return (
<div>
<Button
onClick={() => setSortOrder("popular")}
active={sortOrder === "popular"}
>
Popular
</Button>
<Button
onClick={() => setSortOrder("latest")}
active={sortOrder === "latest"}
>
Latest
</Button>
</div>
);
}
Back to top ⬆️
31. Update state based on the previous state, especially when memoizing with useCallback
React allows you to pass an updater function to the set
function from useState
.
This updater function uses the current state to calculate the next state.
I use this behavior whenever I need to update the state based on the previous state, especially inside functions wrapped with useCallback.
In fact, this approach prevents the need to have the state as one of the hook dependencies.
❌ Bad: handleAddTodo
and handleRemoveTodo
change whenever todos
changes.
function App() {
const [todos, setToDos] = useState([]);
const handleAddTodo = useCallback(
(todo) => {
setToDos([...todos, todo]);
},
[todos]
);
const handleRemoveTodo = useCallback(
(id) => {
setToDos(todos.filter((todo) => todo.id !== id));
},
[todos]
);
return (
<div className="App">
<TodoInput onAddTodo={handleAddTodo} />
<TodoList todos={todos} onRemoveTodo={handleRemoveTodo} />
</div>
);
}
✅ Good: handleAddTodo
and handleRemoveTodo
remain the same even when todos
changes.
function App() {
const [todos, setToDos] = useState([]);
const handleAddTodo = useCallback((todo) => {
setToDos((prevTodos) => [...prevTodos, todo]);
}, []);
const handleRemoveTodo = useCallback((id) => {
setToDos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
}, []);
return (
<div className="App">
<TodoInput onAddTodo={handleAddTodo} />
<TodoList todos={todos} onRemoveTodo={handleRemoveTodo} />
</div>
);
}
Back to top ⬆️
32. Use functions in useState
for lazy initialization and performance gains, as they are invoked only once.
Using a function in useState ensures the initial state is computed only once.
This can improve performance, especially when the initial state is derived from an "expensive" operation like reading from local storage.
❌ Bad: We read the theme from local storage every time the component renders
const THEME_LOCAL_STORAGE_KEY = "101-react-tips-theme";
function PageWrapper({ children }) {
const [theme, setTheme] = useState(
localStorage.getItem(THEME_LOCAL_STORAGE_KEY) || "dark"
);
const handleThemeChange = (theme) => {
setTheme(theme);
localStorage.setItem(THEME_LOCAL_STORAGE_KEY, theme);
};
return (
<div
className="page-wrapper"
style={{ background: theme === "dark" ? "black" : "white" }}
>
<div className="header">
<button onClick={() => handleThemeChange("dark")}>Dark</button>
<button onClick={() => handleThemeChange("light")}>Light</button>
</div>
<div>{children}</div>
</div>
);
}
✅ Good: We only read from the local storage when the component mounts.
function PageWrapper({ children }) {
const [theme, setTheme] = useState(
() => localStorage.getItem(THEME_LOCAL_STORAGE_KEY) || "dark"
);
const handleThemeChange = (theme) => {
setTheme(theme);
localStorage.setItem(THEME_LOCAL_STORAGE_KEY, theme);
};
return (
<div
className="page-wrapper"
style={{ background: theme === "dark" ? "black" : "white" }}
>
<div className="header">
<button onClick={() => handleThemeChange("dark")}>Dark</button>
<button onClick={() => handleThemeChange("light")}>Light</button>
</div>
<div>{children}</div>
</div>
);
}
Back to top ⬆️
33. Use react context for broadly needed, static state to prevent prop drilling
I will use React context whenever I have some data that:
- Is needed in multiple places (e.g., theme, current user, etc.)
- Is mostly static or read-only (i.e., the user can't/doesn't change the data often)
This approach helps avoid prop drilling (i.e., passing down data or state through multiple layers of the component hierarchy).
See an example in the sandbox below 👇.
Back to top ⬆️
34. React Context: Split your context into parts that change frequently and those that change infrequently to enhance app performance
One challenge with React context is that all components consuming the context re-render whenever the context data changes, even if they don't use the part of the context that changed 🤦♀️.
A solution? Use separate contexts.
In the example below, we've created two contexts: one for actions (which are constant) and another for state (which can change).
Back to top ⬆️
35. React Context: Introduce a Provider
component when the value computation is not straightforward
❌ Bad: There is too much logic inside App
to manage the theme.
const THEME_LOCAL_STORAGE_KEY = "101-react-tips-theme";
const DEFAULT_THEME = "light";
const ThemeContext = createContext({
theme: DEFAULT_THEME,
setTheme: () => null,
})
function App() {
const [theme, setTheme] = useState(
() => localStorage.getItem(THEME_LOCAL_STORAGE_KEY) || DEFAULT_THEME
);
useEffect(() => {
if(theme !== "system") {
updateRootElementTheme(theme)
return;
}
// We need to get the class to apply based on the system theme
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light"
updateRootElementTheme(systemTheme)
// Then watch for changes in the system theme and update the root element accordingly
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
const listener = (event) => {
updateRootElementTheme(event.matches ? "dark" : "light")
};
darkThemeMq.addEventListener("change", listener);
return () => darkThemeMq.removeEventListener("change", listener);
}, [theme]);
const themeContextValue = {
theme,
setTheme: (theme) => {
localStorage.setItem(THEME_LOCAL_STORAGE_KEY, theme);
setTheme(theme);
}
}
const [selectedPostId, setSelectedPostId] = useState(undefined);
const onPostSelect = (postId) => {
// TODO: some logging
setSelectedPostId(postId);
};
const posts = useSWR("/api/posts", fetcher);
return (
<div className="App">
<ThemeContext.Provider value={themeContextValue}>
<Dashboard
posts={posts}
onPostSelect={onPostSelect}
selectedPostId={selectedPostId}
/>
</ThemeContext.Provider>
</div>
);
}
✅ Good: The theme logic is encapsulated in ThemeProvider
function App() {
const [selectedPostId, setSelectedPostId] = useState(undefined);
const onPostSelect = (postId) => {
// TODO: some logging
setSelectedPostId(postId);
};
const posts = useSWR("/api/posts", fetcher);
return (
<div className="App">
<ThemeProvider>
<Dashboard
posts={posts}
onPostSelect={onPostSelect}
selectedPostId={selectedPostId}
/>
</ThemeProvider>
</div>
);
}
function ThemeProvider({ children }) {
const [theme, setTheme] = useState(
() => localStorage.getItem(THEME_LOCAL_STORAGE_KEY) || DEFAULT_THEME
);
useEffect(() => {
if (theme !== "system") {
updateRootElementTheme(theme);
return;
}
// We need to get the class to apply based on the system theme
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
.matches
? "dark"
: "light";
updateRootElementTheme(systemTheme);
// Then watch for changes in the system theme and update the root element accordingly
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
const listener = (event) => {
updateRootElementTheme(event.matches ? "dark" : "light");
};
darkThemeMq.addEventListener("change", listener);
return () => darkThemeMq.removeEventListener("change", listener);
}, [theme]);
const themeContextValue = {
theme,
setTheme: (theme) => {
localStorage.setItem(THEME_LOCAL_STORAGE_KEY, theme);
setTheme(theme);
},
};
return (
<div className="App">
<ThemeContext.Provider value={themeContextValue}>
{children}
</ThemeContext.Provider>
</div>
);
}
Back to top ⬆️
36. Consider using the useReducer
hook as a lightweight state management solution
Whenever I have too many values in my state or a complex state and don't want to rely on external libraries, I will reach for useReducer
.
It's especially effective when combined with context for broader state management needs.
Example: See #Tip 34.
Back to top ⬆️
37. Simplify state updates with useImmer
or useImmerReducer
With hooks like useState
and useReducer
, the state must be immutable (i.e., all changes need to create a new state vs. modifying the current one).
This is often cumbersome to achieve.
This is where useImmer and useImmerReducer offer a simpler alternative. They allow you to write "mutable" code automatically converted to immutable updates.
❌ Tedious: We must carefully ensure we're creating a new state object.
export function App() {
const [{ email, password }, setState] = useState({
email: "",
password: "",
});
const onEmailChange = (event) => {
setState((prevState) => ({ ...prevState, email: event.target.value }));
};
const onPasswordChange = (event) => {
setState((prevState) => ({ ...prevState, password: event.target.value }));
};
return (
<div className="App">
<h1>Welcome</h1>
<p>
Email: <input type="email" value={email} onChange={onEmailChange} />
</p>
<p>
Password:{" "}
<input type="password" value={password} onChange={onPasswordChange} />
</p>
</div>
);
}
✅ More straightforward: We can directly modify draftState
.
import { useImmer } from "use-immer";
export function App() {
const [{ email, password }, setState] = useImmer({
email: "",
password: "",
});
const onEmailChange = (event) => {
setState((draftState) => {
draftState.email = event.target.value;
});
};
const onPasswordChange = (event) => {
setState((draftState) => {
draftState.password = event.target.value;
});
};
/// Rest of logic
}
Back to top ⬆️
38. Use Redux (or another state management solution) for complex client-side state accessed across multiple components
I turn to Redux whenever:
- I have a complex FE app with a lot of shared client-side state (for example, dashboard apps)
- I want the user to be able to go back in time and revert changes
- I don't want my components to re-render unnecessarily like they can with React context
- I have too many contexts that start getting out of control
For a streamlined experience, I recommend using redux-tooltkit.
💡 Note: You can also consider other alternatives to Redux, such as Zustand or Recoil.
Back to top ⬆️
39. Redux: Use Redux DevTools to debug your state
The Redux DevTools browser extension is an useful tool for debugging your Redux projects.
It allows you to visualize your state and actions in real-time, maintain state persistence across refreshes, and much more.
To understand its use, watch this great YouTube video.
Back to top ⬆️
Category #6: React Code Optimization 🚀
40. Prevent unnecessary re-renders with memo
When dealing with components that are costly to render and their parent components frequently update, wrapping them in memo can be a game changer.
memo
ensures that a component only re-renders when its props have changed, not simply because its parent re-rendered.
In the example below, I get some data from the server through useGetDashboardData
. If the posts
didn't change, wrapping ExpensiveList
in memo
will prevent it from re-rendering when other parts of the data update.
export function App() {
const { profileInfo, posts } = useGetDashboardData();
return (
<div className="App">
<h1>Dashboard</h1>
<Profile data={profileInfo} />
<ExpensiveList posts={posts} />
</div>
);
}
const ExpensiveList = memo(
({ posts }) => {
/// Rest of implementation
}
);
💡: This tip may be irrelevant once React compiler gets stable 😅.
Back to top ⬆️
41. Specify an equality function with memo
to instruct React on how to compare the props.
By default, memo
uses Object.is to compare each prop with its previous value.
However, specifying a custom equality function can be more efficient than default comparisons or re-rendering for more complex or specific scenarios.
Example 👇
const ExpensiveList = memo(
({ posts }) => {
return <div>{JSON.stringify(posts)}</div>;
},
(prevProps, nextProps) => {
// Only re-render if the last post or the list size changes
const prevLastPost = prevProps.posts[prevProps.posts.length - 1];
const nextLastPost = nextProps.posts[nextProps.posts.length - 1];
return (
prevLastPost.id === nextLastPost.id &&
prevProps.posts.length === nextProps.posts.length
);
}
)
Back to top ⬆️
42. Prefer named functions over arrow functions when declaring a memoized component
When defining memoized components, using named functions instead of arrow functions can improve clarity in React DevTools.
Arrow functions often result in generic names like _c2
, making debugging and profiling more difficult.
❌ Bad: Using arrow functions for memoized components results in less informative names in React DevTools.
const ExpensiveList = memo(
({ posts }) => {
/// Rest of implementation
}
);
✅ Good: The component's name will be visible in DevTools.
const ExpensiveList = memo(
function ExpensiveListFn({ posts }) {
/// Rest of implementation
}
);
Back to top ⬆️
43. Cache expensive computations or preserve references with useMemo
I will generally useMemo
:
- When I have expensive computations that should not be repeated on each render.
- If the computed value is a non-primitive value that is used as a dependency in hooks like
useEffect
. - The computed non-primitive value will be passed as a prop to a component wrapped in
memo
; otherwise, this will break the memoization since React uses Object.is to detect whether props changed.
❌ Bad: The memo
for ExpensiveList
does not prevent re-renders because styles are recreated on every render.
export function App() {
const { profileInfo, posts, baseStyles } = useGetDashboardData();
// We get a new `styles` object on every render
const styles = { ...baseStyles, padding: "10px" };
return (
<div className="App">
<h1>Dashboard</h1>
<Profile data={profileInfo} />
<ExpensiveList posts={posts} styles={styles} />
</div>
);
}
const ExpensiveList = memo(
function ExpensiveListFn({ posts, styles }) {
/// Rest of implementation
}
);
✅ Good: The use of useMemo
ensures styles
only changes when baseStyles
changes, allowing memo
to effectively prevent unnecessary re-renders.
export function App() {
const { profileInfo, posts, baseStyles } = useGetDashboardData();
// We get a new `styles` object only if `baseStyles` changes
const styles = useMemo(
() => ({ ...baseStyles, padding: "10px" }),
[baseStyles]
);
return (
<div className="App">
<h1>Dashboard</h1>
<Profile data={profileInfo} />
<ExpensiveList posts={posts} styles={styles} />
</div>
);
}
Back to top ⬆️
44. Use useCallback
to memoize functions
useCallback
is similar to useMemo
but is designed explicitly for memoizing functions.
❌ Bad: Whenever the theme changes, handleThemeChange
will be called twice, and we will push logs to the server twice.
function useTheme() {
const [theme, setTheme] = useState("light");
// `handleThemeChange` changes on every render
// As a result, the effect will be triggered after each render
const handleThemeChange = (newTheme) => {
pushLog(["Theme changed"], {
context: {
theme: newTheme,
},
});
setTheme(newTheme);
};
useEffect(() => {
const dqMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
handleThemeChange(dqMediaQuery.matches ? "dark" : "light");
const listener = (event) => {
handleThemeChange(event.matches ? "dark" : "light");
};
dqMediaQuery.addEventListener("change", listener);
return () => {
dqMediaQuery.removeEventListener("change", listener);
};
}, [handleThemeChange]);
return theme;
}
✅ Good: Wrapping handleThemeChange
in useCallback ensures that it is recreated only when necessary, reducing unnecessary executions.
const handleThemeChange = useCallback((newTheme) => {
pushLog(["Theme changed"], {
context: {
theme: newTheme,
},
});
setTheme(newTheme);
}, []);
Back to top ⬆️
45. Memoize callbacks or values returned from utility hooks to avoid performance issues
When you create a custom hook to share with others, memoizing the returned values and functions is crucial.
This practice makes your hook more efficient and prevents unnecessary performance issues for anyone using it.
❌ Bad: loadData
is not memoized and creates performance issues.
function useLoadData(fetchData) {
const [result, setResult] = useState({
type: "notStarted",
});
async function loadData() {
setResult({ type: "loading" });
try {
const data = await fetchData();
setResult({ type: "loaded", data });
} catch (err) {
setResult({ type: "error", error: err });
}
}
return { result, loadData };
}
✅ Good: We memoize everything so there are no unexpected performance issues.
function useLoadData(fetchData) {
const [result, setResult] = useState({
type: "notStarted",
});
// Wrap in `useRef` and use the `ref` value so the function never changes
const fetchDataRef = useRef(fetchData);
useEffect(() => {
fetchDataRef.current = fetchData;
}, [fetchData]);
// Wrap in `useCallback` and use the `ref` value so the function never changes
const loadData = useCallback(async () => {
setResult({ type: "loading" });
try {
const data = await fetchDataRef.current();
setResult({ type: "loaded", data });
} catch (err) {
setResult({ type: "error", error: err });
}
}, []);
return useMemo(() => ({ result, loadData }), [result, loadData])
}
Back to top ⬆️
46. Leverage lazy loading and Suspense
to make your apps load faster
When you're building your app, consider using lazy loading and Suspense
for code that is:
- Expensive to load.
- Only relevant to some users (like premium features).
- Not immediately necessary for the initial user interaction.
In the sandbox below 👇, the Slider assets (JS + CSS) only load after you click on a card.
Back to top ⬆️
47. Throttle your network to simulate a slow network
Did you know you can simulate slow internet connections directly in Chrome?
This is especially useful when:
- Customers report slow loading times that you can't replicate on your faster network.
- You're implementing lazy loading and want to observe how files load under slower conditions to ensure appropriate loading states.
Back to top ⬆️
48. Use react-window
or react-virtuoso
to efficiently render lists
Never render a long list of items all at once—such as chat messages, logs, or infinite lists.
Doing so can cause the browser to freeze.
Instead, virtualize the list. This means rendering only the subset of items likely to be visible to the user.
Libraries like react-window, react-virtuoso or @tanstack/react-virtual are designed for this purpose.
❌ Bad: NonVirtualList
renders all 50,000 log lines simultaneously, even if they aren't visible.
function NonVirtualList({ items }) {
return (
<div style={{ height: "100%" }}>
{items.map((log, index) => (
<div
key={log.id}
style={{
padding: "5px",
borderBottom:
index === items.length - 1 ? "none" : "1px solid #ccc",
}}
>
<LogLine log={log} index={index} />
</div>
))}
</div>
);
}
✅ Good: VirtualList
renders only the items likely to be visible.
function VirtualList({ items }) {
return (
<Virtuoso
style={{ height: "100%" }}
data={items}
itemContent={(index, log) => (
<div
key={log.id}
style={{
padding: "5px",
borderBottom:
index === items.length - 1 ? "none" : "1px solid #ccc",
}}
>
<LogLine log={log} index={index} />
</div>
)}
/>
);
}
You can switch between the two options in the sandbox below and notice how bad the app performs when NonVirtualList
is used 👇.
Back to top ⬆️
Category #7: Debugging React code 🐞
49. Use StrictMode
to catch bugs in your components before deploying them to production
Using StrictMode is a proactive way to detect potential issues in your application during development.
It helps identify problems such as:
- Incomplete cleanup in effects, like forgetting to release resources.
- Impurities in React components, ensuring they return consistent JSX given the same inputs (props, state, and context).
The example below shows a bug because clearInterval
is never called. StrictMode
helps catch this by running the effect twice, which creates two intervals.
Back to top ⬆️
50. Install the React Developer Tools browser extension to view/edit your components and detect performance issues
React Developer Tools is a must extension (Chrome, Firefox).
This extension lets you:
- Visualize and delve into the details of your React components, examining everything from props to state.
- Directly modify a component's state or props to see how changes affect behavior and rendering.
- Profile your application to identify when and why components are re-rendering, helping you spot performance issues.
- Etc.
💡 Learn how to use it in this great guide.
Back to top ⬆️
51. React DevTools Components: Highlight components that render to identify potential issues
I will use this trick whenever I suspect that my app has performance issues. You can highlight the components that render to detect potential problems (e.g., too many renders).
The gif below shows that the FollowersListFn
component re-renders whenever the time changes, which is wrong.
Back to top ⬆️
52. Leverage useDebugValue
in your custom hooks for better visibility in React DevTools
useDebugValue can be a handy tool for adding descriptive labels to your custom hooks in React DevTools.
This makes it easier to monitor their states directly from the DevTools interface.
For instance, consider this custom hook I use to fetch and display the current time, updated every second:
function useCurrentTime(){
const [time, setTime] = useState(new Date());
useEffect(() => {
const intervalId = setInterval(() => {
setTime(new Date());
}, 1_000);
return () => clearInterval(intervalId);
}, [setTime]);
return time;
}
❌ Bad: Without useDebugValue
, the actual time value isn't immediately visible; you'd need to expand the CurrentTime hook:
✅ Good: With useDebugValue
, the current time is readily visible:
useDebugValue(time)
Note: Use
useDebugValue
judiciously. It's best reserved for complex hooks in shared libraries where understanding the internal state is crucial.
Back to top ⬆️
53. Use the why-did-you-render
library to track component rendering and identify potential performance bottlenecks
Sometimes, a component re-renders, and it's not immediately clear why 🤦♀️.
While React DevTools is helpful, in large apps, it might only provide vague explanations like "hook #1 rendered," which can be useless.
In such cases, you can turn to the why-did-you-render library. It offers more detailed insights into why components re-render, helping to pinpoint performance issues more effectively.
I made an example in the sandbox below 👇. Thanks to this library, we can find the issue with the FollowersList
component.
Back to top ⬆️
54. Hide logs during the second render in Strict Mode
StrictMode helps catch bugs early in your application's development.
However, since it causes components to render twice, this can result in duplicated logs, which might clutter your console.
You can hide logs during the second render in Strict Mode to address this.
Check out how to do it in the gif below 👇:
Back to top ⬆️
Category #8: Testing React code 🧪
55. Use React Testing Library
to test your React components effectively
Want to test your React apps?
Make sure to use @testing-library/react.
You can find a minimal example here.
Back to top ⬆️
56. React Testing Library: Use testing playground to effortlessly create queries
Struggling to decide which queries to use in your tests?
Consider using testing playground to quickly generate them from your component's HTML.
Here are two ways to leverage it:
Option #1: Use screen.logTestingPlaygroundURL()
in your test. This function generates a URL that opens the Testing Playground tool with the HTML of your component already loaded.
Option #2: Install Testing Playground Chrome extension. This extension allows you to hover over elements in your app directly in the browser to find the best queries for testing them.
Back to top ⬆️
57. Conduct end-to-end tests with Cypress
or Playwright
Need to conduct end-to-end tests?
Make sure to check out Cypress or Playwright.
Note: Playwright's support for components is experimental at the time of writing.
Back to top ⬆️
58. Use MSW
to mock network requests in your tests
Sometimes, your tests need to make network requests.
Rather than implementing your own mocks (or, God forbid, making actual network requests 😅), consider using MSW (Mock Service Worker) to handle your API responses.
MSW allows you to intercept and manipulate network interactions directly in your tests, providing a robust and straightforward solution for simulating server responses without affecting live servers.
This approach helps maintain a controlled and predictable testing environment, enhancing the reliability of your tests.
Back to top ⬆️
Category #9: React hooks 🎣
59. Make sure you perform any required cleanup in your useEffect
hooks
Always return a cleanup function in your useEffect
hooks if you're setting up anything that needs to be cleaned up later.
This could be anything from ending a chat session to closing a database connection.
Neglecting this step can lead to poor resource usage and potential memory leaks.
❌ Bad: This example sets an interval. But we never clear it, which means it keeps running even after the component is unmounted.
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
setInterval(() => {
setTime(new Date());
}, 1_000);
}, []);
return <>Current time {time.toLocaleTimeString()}</>;
}
✅ Good: The interval is properly cleared when the component unmounts.
function Timer() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const intervalId = setInterval(() => {
setTime(new Date());
}, 1_000);
// We clear the interval
return () => clearInterval(intervalId);
}, []);
return <>Current time {time.toLocaleTimeString()}</>;
}
Back to top ⬆️
60. Use refs
for accessing DOM elements
You should never manipulate the DOM directly with React.
Methods like document.getElementById
and document.getElementsByClassName
are forbidden since React should access/manipulate the DOM.
So, what should you do when you need access to DOM elements?
You can use the useRef hook, like in the example below, where we need access to the canvas element.
Note: We could have added an ID to the canvas and used
document.getElementById
, but this is not recommended.
Back to top ⬆️
61. Use refs
to preserve values across re-renders
If you have mutable values in your React component that aren't stored in the state, you'll notice that changes to these values don't persist through re-renders.
This happens unless you save them globally.
You might consider putting these values in the state. However, if they are irrelevant to the rendering, doing so can cause unnecessary re-renders, which wastes performance.
This is where useRef also shines.
In the example below, I want to stop the timer when the user clicks on some button. For that, I need to store the intervalId somewhere.
❌ Bad: The example below won't work as intended because intervalId
gets reset with every component re-render.
function Timer() {
const [time, setTime] = useState(new Date());
let intervalId;
useEffect(() => {
intervalId = setInterval(() => {
setTime(new Date());
}, 1_000);
return () => clearInterval(intervalId);
}, []);
const stopTimer = () => {
intervalId && clearInterval(intervalId);
};
return (
<>
<>Current time: {time.toLocaleTimeString()} </>
<button onClick={stopTimer}>Stop timer</button>
</>
);
}
✅ Good: By using useRef
, we ensure that the interval ID is preserved between renders.
function Timer() {
const [time, setTime] = useState(new Date());
const intervalIdRef = useRef();
const intervalId = intervalIdRef.current;
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date());
}, 1_000);
intervalIdRef.current = interval;
return () => clearInterval(interval);
}, []);
const stopTimer = () => {
intervalId && clearInterval(intervalId);
};
return (
<>
<>Current time: {time.toLocaleTimeString()} </>
<button onClick={stopTimer}>Stop timer</button>
</>
);
}
Back to top ⬆️
62. Prefer named functions over arrow functions within hooks such as useEffect
to easily find them in React Dev Tools
If you have many hooks, finding them in React DevTools can be challenging.
One trick is to use named functions so you can quickly spot them.
❌ Bad: It's hard to find the specific effect among many hooks.
function HelloWorld() {
useEffect(() => {
console.log("🚀 ~ Hello, I just got mounted")
}, []);
return <>Hello World</>;
}
✅ Good: You can quickly spot the effect.
function HelloWorld() {
useEffect(function logOnMount() {
console.log("🚀 ~ Hello, I just got mounted");
}, []);
return <>Hello World</>;
}
Back to top ⬆️
63. Encapsulate logic with custom hooks
Let's say I have a component that gets the theme from the user's dark mode preferences and uses it inside the app.
It's better to extract the logic that returns the theme into a custom hook (to reuse it and keep the component clean).
❌ Bad: App
is overcrowded
function App() {
const [theme, setTheme] = useState("light");
useEffect(() => {
const dqMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
setTheme(dqMediaQuery.matches ? "dark" : "light");
const listener = (event) => {
setTheme(event.matches ? "dark" : "light");
};
dqMediaQuery.addEventListener("change", listener);
return () => {
dqMediaQuery.removeEventListener("change", listener);
};
}, []);
return (
<div className={`App ${theme === "dark" ? "dark" : ""}`}>Hello Word</div>
);
}
✅ Good: App
is much simpler, and we can reuse the logic
function App() {
const theme = useTheme();
return (
<div className={`App ${theme === "dark" ? "dark" : ""}`}>Hello Word</div>
);
}
// Custom hook that can be reused
function useTheme() {
const [theme, setTheme] = useState("light");
useEffect(() => {
const dqMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
setTheme(dqMediaQuery.matches ? "dark" : "light");
const listener = (event) => {
setTheme(event.matches ? "dark" : "light");
};
dqMediaQuery.addEventListener("change", listener);
return () => {
dqMediaQuery.removeEventListener("change", listener);
};
}, []);
return theme;
}
Back to top ⬆️
64. Prefer functions over custom hooks
Never put logic inside a hook when a function can be used 🛑.
In effect:
- Hooks can only be used inside other hooks or components, whereas functions can be used everywhere.
- Functions are simpler than hooks.
- Functions are easier to test.
- Etc.
❌ Bad: The useLocale
hook is unnecessary since it doesn't need to be a hook. It doesn't use other hooks like useEffect
, useState
, etc.
function App() {
const locale = useLocale();
return (
<div className="App">
<IntlProvider locale={locale}>
<BlogPost post={EXAMPLE_POST} />
</IntlProvider>
</div>
);
}
function useLocale() {
return window.navigator.languages?.[0] ?? window.navigator.language;
}
✅ Good: Create a function getLocale
instead
function App() {
const locale = getLocale();
return (
<div className="App">
<IntlProvider locale={locale}>
<BlogPost post={EXAMPLE_POST} />
</IntlProvider>
</div>
);
}
function getLocale() {
return window.navigator.languages?.[0] ?? window.navigator.language;
}
Back to top ⬆️
65. Prevent visual UI glitches by using the useLayoutEffect
hook
When an effect isn't caused by a user interaction, the user will see the UI before the effect runs (often briefly).
As a result, if the effect modifies the UI, the user will see the initial UI version very quickly before seeing the updated one, creating a visual glitch.
Using useLayoutEffect
ensures the effect runs synchronously after all DOM mutations, preventing the initial render glitch.
In the sandbox below, we want the width to be equally distributed between the columns (I know this can be done in CSS, but I need an example 😅).
With useEffect
, you can see briefly at the beginning that the table is changing. The columns are rendered with their default size before being adjusted to their correct size.
If you're looking for another great usage, check out this post.
Back to top ⬆️
66. Generate unique IDs for accessibility attributes with the useId
hook
Tired of coming up with IDs or having them clash?
You can use the useId hook to generate a unique ID inside your React component and ensure your app is accessible.
Example
function Form() {
const id = useId();
return (
<div className="App">
<div>
<label>
Name{" "}
<input type="text" aria-describedby={id} />
</label>
</div>
<span id={id}>Make sure to include full name</span>
</div>
);
}
Back to top ⬆️
67. Use the useSyncExternalStore
to subscribe to an external store
This is a rarely needed but super powerful hook 💪.
Use this hook if:
- You have some state not accessible in the React tree (i.e., not present in the state or context)
- The state can change, and you need your component to be notified of changes
In the example below, I want a Logger
singleton to log errors, warnings, info, etc., in my entire app.
These are the requirements:
- I need to be able to call this everywhere in my React app (even inside non-React components), so I won't put it inside a state/context.
- I want to display all the logs to the user inside a
Logs
component
👉 I can use useSyncExternalStore
inside my Logs
component to access the logs and listen to changes.
function createLogger() {
let logs = [];
let listeners = [];
const pushLog = (log) => {
logs = [...logs, log];
listeners.forEach((listener) => listener());
};
return {
getLogs: () => Object.freeze(logs),
subscribe: (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter((l) => l !== listener);
};
},
info: (message) => {
pushLog({ level: "info", message });
console.info(message);
},
error: (message) => {
pushLog({ level: "error", message });
console.error(message);
},
warn: (message) => {
pushLog({ level: "warn", message });
console.warn(message);
},
};
}
export const Logger = createLogger();
Back to top ⬆️
68. Use the useDeferredValue
hook to display the previous query results until the new results become available
Imagine you're building an app that represents countries on a map.
Users can filter to see countries up to a specific population size.
Every time maxPopulationSize
updates, the map is re-rendered (see sandbox below).
As a result, notice how janky the slider is when you move it too fast. This is because the map is re-rendered every time the slider moves.
To solve this, we can use the useDeferredValue
hook so that the slider updates smoothly.
<Map
maxPopulationSize={deferredMaxPopulationSize}
// …
/>
If you're looking for another great usage, check out this post.
Back to top ⬆️
Category #10: Must-known React Libraries/Tools 🧰
69. Incorporate routing into your app with react-router
If you need your app to support multiple pages, check out react-router.
You can find a minimal example here.
Back to top ⬆️
70. Implement first-class data fetching in your app with swr
or React Query
Data fetching can be notoriously tricky.
However, libraries like swr or React Query make it much easier.
I recommend swr for simple use cases and React Query for more complex ones.
Back to top ⬆️
71. Simplify form state management with libraries like formik
, React Hook Form
, or TanStack Form
I used to hate form management in React 🥲.
That was until I discovered libraries like:
- formik
- React Hook Form, or
- or TanStack Form
So make sure to check these out if you're struggling with forms.
Back to top ⬆️
72. Internationalize your app using Format.js,
Lingui,
or react-i18next.
If your app needs to support multiple languages, it should be internationalized.
You can achieve this with libraries like:
Back to top ⬆️
73. Effortlessly create impressive animations with framer-motion
Animations can make your app stand out 🔥.
You can create them easily with framer-motion.
Back to top ⬆️
74. Tired of re-inventing the wheel with custom hooks? Check out https://usehooks.com/
If you're like me, you've written the same hooks over and over again.
So check usehooks.com first to see if someone has already done the work for you.
Back to top ⬆️
75. Streamline app development by leveraging UI libraries like Shadcdn or Headless UI
It's hard to build UI at scale that is accessible, responsive, and beautiful.
Libraries like Shadcdn or Headless UI make it easier.
Shadcdn provides a set of accessible, reusable, and composable React components that you can copy and paste into your apps. At the time of writing, it requires Tailwind CSS.
Headless UI provides unstyled, fully accessible UI components that you can use to build your own UI components.
Back to top ⬆️
76. Check your website's accessibility with the axe-core-npm
library
Websites should be accessible to everyone.
However, it's easy to miss accessibility issues.
axe-core-npm is a fast, secure, and reliable way to check your website's accessibility while developing it.
💡 Tip: If you're a VSCode user, you can install the associated extension: axe Accessibility Linter.
Back to top ⬆️
77. Refactor React code effortlessly with react-codemod
Codemods are transformations that run on your codebase programmatically 💻.
They make it easy to refactor your codebase.
For example, React codemods can help you remove all React imports from your codebase, update your code to use the latest React features, and more.
So, make sure to check those out before manually refactoring your code.
Back to top ⬆️
78. Transform your app into a Progressive Web Application (PWA) using vite-pwa
Progressive Web Applications (PWAs) load like regular web pages but offer functionality such as working offline, push notifications, and device hardware access.
You can easily create a PWA in React using vite-pwa.
Back to top ⬆️
Category #11: React & Visual Studio Code 🛠️
79. Enhance your productivity with the Simple React Snippets snippets extension
Bootstrapping a new React component can be tedious 😩.
Snippets from the Simple React Snippets extension make it easier.
Back to top ⬆️
80. Set editor.stickyScroll.enabled
to true
to quickly locate the current component
I love this feature ❤️.
If you have a big file, it can be hard to locate the current component.
By setting editor.stickyScroll.enabled
to true
, the current component will always be at the top of the screen.
- ❌ Without sticky scroll
- ✅ With sticky scroll
Back to top ⬆️
81. Simplify refactoring with extensions like VSCode Glean or VSCode React Refactor
If you need to refactor your code frequently (for example, extract JSX into a new component), make sure to check out extensions like VSCode Glean or VSCode React Refactor.
Back to top ⬆️
Category #12: React & TypeScript 🚀
82. Use ReactNode
instead of JSX.Element | null | undefined | ...
to keep your code more compact
I see this mistake a lot.
Instead of typing the leftElement
and rightElement
props like this:
const Panel = ({ leftElement, rightElement }: {
leftElement:
| JSX.Element
| null
| undefined
// | ...;
rightElement:
| JSX.Element
| null
| undefined
// | ...
}) => {
// …
};
You can use ReactNode
to keep the code more compact.
const MyComponent = ({ leftElement, rightElement }: { leftElement: ReactNode; rightElement: ReactNode }) => {
// …
};
Back to top ⬆️
83. Simplify the typing of components expecting children props with PropsWithChildren
You don't have to type the children
prop manually.
In fact, you can use PropsWithChildren
to simplify the typings.
// 🟠 Ok
const HeaderPage = ({ children,...pageProps }: { children: ReactNode } & PageProps) => {
// …
};
// ✅ Better
const HeaderPage = ({ children, ...pageProps } : PropsWithChildren<PageProps>) => {
// …
};
Back to top ⬆️
84. Access element props efficiently with ComponentProps
, ComponentPropsWithoutRef
,…
There are cases where you need to figure out a component's props.
For example, let's say you want a button that will log to the console when clicked.
You can use ComponentProps
to access the props of the button
element then override the click
prop.
const ButtonWithLogging = (props: ComponentProps<"button">) => {
const handleClick: MouseEventHandler<HTMLButtonElement> = (e) => {
console.log("Button clicked"); //TODO: Better logging
props.onClick?.(e);
};
return <button {...props} onClick={handleClick} />;
};
This trick also works with custom components.
const MyComponent = (props: { name: string }) => {
// …
};
const MyComponentWithLogging = (props: ComponentProps<typeof MyComponent>) => {
// …
};
Back to top ⬆️
85. Leverage types like MouseEventHandler
, FocusEventHandler
and others for concise typings
Rather than typing the event handlers manually, you can use types like MouseEventHandler
to keep the code more concise and readable.
// 🟠 Ok
const MyComponent = ({ onClick, onFocus, onChange }: {
onClick: (e: MouseEvent<HTMLButtonElement>) => void;
onFocus: (e: FocusEvent<HTMLButtonElement>) => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) => {
// …
};
// ✅ Better
const MyComponent = ({ onClick, onFocus, onChange }: {
onClick: MouseEventHandler<HTMLButtonElement>;
onFocus: FocusEventHandler<HTMLButtonElement>;
onChange: ChangeEventHandler<HTMLInputElement>;
}) => {
// …
};
Back to top ⬆️
86. Specify types explicitly in useState, useRef, etc., when the type can't be or shouldn't be inferred from the initial value
Don't forget to specify the type when it can't be inferred from the initial value.
For example, in the example below, there is a selectedItemId
stored in the state. It should be a string
or undefined
.
since the type is not specified, TypeScript will infer the type as undefined
, which is not what we want.
// ❌ Bad: `selectedItemId` will be inferred as `undefined`
const [selectedItemId, setSelectedItemId] = useState(undefined);
// ✅ Good
const [selectedItemId, setSelectedItemId] = useState<string | undefined>(undefined);
💡 Note: The opposite of this is that you don't need to specify the type when TypeScript can infer it for you.
Back to top ⬆️
87. Leverage the Record
type for cleaner and more extensible code
I love this helper type.
Let's say I have a type that represents log levels.
type LogLevel = "info" | "warn" | "error";
We have a corresponding function for each log level that logs the message.
const logFunctions = {
info: (message: string) => console.info(message),
warn: (message: string) => console.warn(message),
error: (message: string) => console.error(message),
};
Instead of typing the logFunctions manually, you can use the Record type.
const logFunctions: Record<LogLevel, (message: string) => void> = {
info: (message) => console.info(message),
warn: (message) => console.warn(message),
error: (message) => console.error(message),
};
Using the Record
type makes the code more concise and readable.
Additionally, it helps capture any error if a new log level is added or removed.
For example, if I decided to add a debug
log level, TypeScript would throw an error.
Back to top ⬆️
88. Use the as const
trick to accurately type your hook return values
Let's say we have a hook useIsHovered
to detect whether a div element is hovered.
The hook returns a ref
to use with the div element and a boolean indicating whether the div is hovered.
const useIsHovered = () => {
const ref = useRef<HTMLDivElement>(null);
const [isHovered, setIsHovered] = useState(false);
// TODO : Rest of implementation
return [ref, isHovered]
};
Currently, TypeScript will not correctly infer the function return type.
You can either fix this by explicitly typing the return type like this:
const useIsHovered = (): [RefObject<HTMLDivElement>, boolean] => {
// TODO : Rest of implementation
return [ref, isHovered]
};
Or you can use the as const
trick to accurately type the return values:
const useIsHovered = () => {
// TODO : Rest of implementation
return [ref, isHovered] as const;
};
Back to top ⬆️
89. Redux: Ensure proper typing by referring to https://react-redux.js.org/using-react-redux/usage-with-typescript to correctly type your Redux state and helpers
I love using Redux to manage heavy client-side state.
It also works well with TypeScript.
You can find a great guide on how to use Redux with TypeScript here.
Back to top ⬆️
90. Simplify your types with ComponentType
Let's say you're designing an app like Figma (I know, you're ambitious 😅).
The app is made of widgets, each accepting a size
.
To reuse logic, we can define a shared WidgetWrapper
component that takes a widget of type Widget
defined as follows:
interface Size {
width: number;
height: number
};
interface Widget {
title: string;
Component: ComponentType<{ size: Size }>;
}
The WidgetWrapper
component will render the widget and pass the relevant size to it.
const WidgetWrapper = ({ widget }: { widget: Widget }) => {
const { Component, title } = widget;
const { onClose, size, onResize } = useGetProps(); //TODO: better name but you get the idea 😅
return (
<Wrapper onClose={onClose} onResize={onResize}>
<Title>{title}</Title>
{/* We can render the component below with the size */}
<Component size={size} />
</Wrapper>
);
};
Back to top ⬆️
91. Make your code more reusable with TypeScript generics
If you're not using TypeScript generics, only two things could be happening:
- You are either writing very simple code or,
- You are missing out 😅
TypeScript generics make your code more reusable and flexible.
For example, let's say I have different items on a blog (e.g., Post
, Follower
, etc.), and I want a generic list component to display them.
export interface Post {
id: string;
title: string;
contents: string;
publicationDate: Date;
}
export interface User {
username: string;
}
export interface Follower extends User {
followingDate: Date;
}
Each list should be sortable.
There is a bad and a good way to do this.
❌ Bad: I create a single list component that accepts a union of items.
This is bad because:
- Every time a new item is added, the functions/types must be updated.
- The function is not entirely type-safe (see
This shouldn't happen
comment). - This code depends on other files (e.g.:
FollowerItem
,PostItem
). - Etc.
import { FollowerItem } from "./FollowerItem";
import { PostItem } from "./PostItem";
import { Follower, Post } from "./types";
type ListItem = { type: "follower"; follower: Follower } | { type: "post"; post: Post };
function ListBad({
items,
title,
vertical = true,
ascending = true,
}: {
title: string;
items: ListItem[];
vertical?: boolean;
ascending?: boolean;
}) {
const sortedItems = [...items].sort((a, b) => {
const sign = ascending ? 1 : -1;
return sign * compareItems(a, b);
});
return (
<div>
<h3 className="title">{title}</h3>
<div className={`list ${vertical ? "vertical" : ""}`}>
{sortedItems.map((item) => (
<div key={getItemKey(item)}>{renderItem(item)}</div>
))}
</div>
</div>
);
}
function compareItems(a: ListItem, b: ListItem) {
if (a.type === "follower" && b.type === "follower") {
return (
a.follower.followingDate.getTime() - b.follower.followingDate.getTime()
);
} else if (a.type == "post" && b.type === "post") {
return a.post.publicationDate.getTime() - b.post.publicationDate.getTime();
} else {
// This shouldn't happen
return 0;
}
}
function getItemKey(item: ListItem) {
switch (item.type) {
case "follower":
return item.follower.username;
case "post":
return item.post.id;
}
}
function renderItem(item: ListItem) {
switch (item.type) {
case "follower":
return <FollowerItem follower={item.follower} />;
case "post":
return <PostItem post={item.post} />;
}
}
Instead, we can use TypeScript generics to create a more reusable and type-safe list component.
I made an example in the sandbox below 👇.
Back to top ⬆️
92. Ensure precise typing with the NoInfer
utility type
Imagine you're developing a video game 🎮.
The game has multiple locations (e.g., LeynTir
, Forin
, Karin
, etc.).
You want to create a function that teleports the player to a new location.
function teleportPlayer<L extends string>(
position: Position,
locations: L[],
defaultLocation: L,
) : L {
// Teleport the player and return the location
}
The function will be called this way:
const position = { x: 1, y: 2, z: 3 };
teleportPlayer(position, ["LeynTir", "Forin", "Karin"], "Forin");
teleportPlayer(position, ["LeynTir", "Karin"], "anythingCanGoHere"); // ❌ This will work, but it is wrong since "anythingCanGoHere" shouldn't be a valid location
The second example is invalid because anythingCanGoHere
is not a valid location.
However, TypeScript will not throw an error since it inferred the type of L
from the list and the default location.
To fix this, use the NoInfer
utility type.
function teleportPlayer<L extends string>(
position: Position,
locations: L[],
defaultLocation: NoInfer<L>,
) : NoInfer<L> {
// Teleport the player and return the location
}
Now TypeScript will throw an error:
teleportPlayer(position, ["LeynTir", "Karin"], "anythingCanGoHere"); // ❌ Error: Argument of type '"anythingCanGoHere"' is not assignable to parameter of type '"LeynTir" | "Karin"
Using the NoInfer
utility type ensures that the default location must be one of the valid locations provided in the list, preventing invalid inputs.
Back to top ⬆️
93. Effortlessly type refs with the ElementRef
type helper
There is a hard and easy way to type refs.
The hard way is to remember the element's type name and use it directly 🤣.
const ref = useRef<HTMLDivElement>(null);
The easy way is to use the ElementRef
type helper. This method is more straightforward since you should already know the element's name.
const ref = useRef<ElementRef<"div">>(null);
Back to top ⬆️
Category #13: Miscellaneous Tips 🎉
94. Boost your code's quality and safety with eslint-plugin-react
and Prettier.
You can't be serious about React if you're not using eslint-plugin-react😅.
It helps you catch potential bugs and enforce best practices.
So, make sure to install and configure it for your project.
You can also use Prettier to format your code automatically and ensure your codebase is consistent.
Back to top ⬆️
95. Log and monitor your app with tools like Sentry or Grafana Cloud Frontend Observability.
You can't improve what you don't measure 📏.
If you're looking for a monitoring tool for your production apps, check out Sentry or Grafana Cloud Frontend Observability.
Back to top ⬆️
96. Start coding quickly with online IDEs like Code Sandbox or Stackblitz
Local development environments can be a pain to set up.
Especially as a beginner 🐣.
So start with online IDEs like Code Sandbox or Stackblitz.
These tools allow you to start coding quickly without worrying about setting up your environment.
Back to top ⬆️
97. Looking for advanced react skills? Check out these books 👇
If you're looking for advanced React books 📚, I would recommend:
- Advanced React by @adevnadia
- Fluent React by @TejasKumar_
- Building Large Scale Web Apps by @addyosmani and @djirdehh
Back to top ⬆️
98. Prepping React interviews? Check reactjs-interview-questions
React interviews ⚛️ can be tricky.
Luckily, you can prep for them by checking this repo.
Back to top ⬆️
99. Learn React best practices from experts like Nadia, Dan, Josh, Kent, etc.
If you want to stay up-to-date with the best practices and learn tips, make sure to follow experts like:
- @adevnadia: https://x.com/adevnadia for advanced react tips
- @joshwcomeau: https://x.com/joshwcomeau
- @kentcdodds: https://x.com/kentcdodds
- @mattpocockuk: https://x.com/mattpocockuk for TypeScript tips
- @tejaskumar_: https://x.com/TejasKumar_
- @housecor: https://x.com/housecor
- or me 😅: https://x.com/_ndeyefatoudiop
Back to top ⬆️
100. Stay updated with the React ecosystem by subscribing to newsletters like This Week In React or ui.dev
React is a fast-moving ecosystem.
There are many tools, libraries, and best practices to keep up with.
To stay updated, make sure to subscribe to newsletters 💌 like:
- This Week In React by @sebastienlorber
- ui.dev
- Etc.
Back to top ⬆️
101. Engage with the React community on platforms like r/reactjs
The React community is fantastic.
You can learn a lot from other developers and share your knowledge.
So engage with the community on platforms like r/reactjs.
That's a wrap 🎉.
Leave a comment 📩 to share your favorite tip (or add one).
And don't forget to drop a "💖🦄🔥".
If you like articles like this, join my FREE newsletter, FrontendJoy.
If you want daily tips, find me on X/Twitter.
Top comments (33)
Very great article. I agree this should definitely be a must-read for anyone working with React
I would like to point out a few improvements to the examples.
#8
- Use IIFE to keep your code clean and avoid lingering variablesI would strongly discourage the use of
IIFE
. It's too easy to miss while reading the code and you don't really get much benefits from them anymore.Readability is better than performance unless performance is absolutely required. In my career, I've come across maybe 2 situations where performance needed to trump readability.
#35
- React Context: Introduce a Provider component when the value computation is not straightforwardWhile it's pretty nice to abstract the logic for a
provider
outside the actual component code, I believe your example shows the wrong abstraction. A custom hook (#63
) would have been more appropriate here.The problem with "self-contained context provider" components is they are harder to use in tests. We embed logic in the self-contained function that needs to be mocked for a lot of tests depending on the values in the context. It's easier to keep your context provider "raw" and then you can simply use the same context provider in your tests and pass whatever you want for values. Still some mocking, but so much simpler that way.
#37
Simplify state updates withuseImmer
oruseImmerReducer
While I fully see how easier it is with these hooks, it also trains you to manipulate an object without thinking about immutability.
This is a huge crutch! I've seen many developers being completely lost once they had to actually code the real thing. They didn't understand whatever principle they were replacing with these libraries, and they made a huge mess in the code base.
These libraries also tend to complicate the
typings
of your objects unnecessarily. I still have nightmares withRedux
andImmerJs
, the "cure" was worse than the "problem".My advice would be to avoid these kinds of libraries like the plague. They will not serve you well in your career in the long term.
#45
- Memoize callbacks or values returned from utility hooks to avoid performance issuesThe example is not the best. In this case,
fetchData
should have already been memoized before being passed touseLoadData
. Doing it in thechildren
is "wrong" as it will be much less efficient this way.#66
- Generate unique IDs for accessibility attributes with the useId hookYour example shows 2 HTML elements with the same
id
... (input/span)I recommend calling
useId()
once and composing yourid
for different components, ex:Obviously, I wouldn't name them
inputId
andspanId
, but for illustrative purpose this is fine...#82
Use ReactNode instead of JSX.Element | null | undefined | ...I agree 100% with the notion, but the example is not the best one.
This should be used like
slots
are used in web-components.children
is pretty much the exception and should always be used withPropsWithChildren
instead. You did use it right after in#83
Conclusion
Overall, an amazing article, kudos!!!
Thanks for the comments @thethirdrace 😀.
8: The reason why I recommend IIFE is that I don't like it when there are variables polluting the component. Ideally, we can extract the logic into a function, but sometimes it is not the best if the logic takes so many arguments (and it is hard to find a name for the function 😅). My example is indeed too simple to show the problem, but I can see code like this inside a component.
In these cases, I am ok with someone either using IIFE or extracting the logic into a function.
35 You are right. As pointed out, I am indeed using the hook 😅. Will change it to make it much clearer!
37 So I was actually thinking like you before. But recently, I have been using the redux toolkit, and damn, the experience is so much nicer. That is why I think they are still worth exploring.
45 I actually disagree here. When I write code, I want to make it super easy for consumers. And if we require every consumer to memoize the function passed, it won't work every time. That is why I am using a
ref
here to make sure my callback doesn't change while still using the correct value of the function when called66 I am using a single ID because the ID is associated with a single element: the name
82 You are right. The example is not the best. Will change it :)
#8
- IIFEIt's more about the syntax that is really hard to pick up for developers, mistakes are too easy to make.
I've seen people do some
IIFE
inuseEffect
just because they needed anasync
function there. But that isn't immediately obvious, and you can easily fix it with a syntax like this:As you can see, the syntax is basically the same without the
IIFE
that is hard to catch. We define thefunction
normally and call it right after. Same principle, less mistakes possibilities.While it may look like those 2 methods are the very same and there's no difference, there are in fact many.
The way people read text (western style => top->down & left->right), the
IIFE
will require 2 parallel contexts to load into memory, the function itself and the executing context (()()
). This increase cognitive load as they need to be loaded at the same time in parallel.Comparatively, by separating in 2 sections, you load those 2 contexts sequentially. You can read the whole function without worries and then you see it's called. This is already much easier to contextualize in memory because these things happen sequentially.
But there's another advantage to loading context sequentially like this. If you just want to have an idea of what the component does, you can easily see there's a function definition and skip to the next "block" without checking how the function does its work. Sequential context loading allows you to skim the code very fast without caring much about the details. This is not possible with
IIFE
because you need to figure out what that first parenthesis really means before closing the function context.I want also to point out that the spacing is important to really delimitate the
function
from thereturn
section. For the same reason we use spacing between paragraphs and after punctuation, spacing is essential to separate concepts in code.These are very subtle things to take into account, but on large scale it makes your system so much more accessible and easier to work with.
#37
- ImmerRedux toolkit
doesn't requireImmerJS
to work, it's an option you can use out of the box, but not a necessity.But I do get where you're coming from. It's much easier to read your state if there's a minimum amount of code.
I would argue it comes with a few problems though...
First, as mentioned in my previous post, you start to use that method everywhere because you're so used to it. Unfortunately, not all systems are built with
Immer
so you start to put bugs in other apps that don't use it because your brain isn't trained to see these mistakes. For your brain, all you can see is correct syntax. And the reviewers will not necessarily pick up on that because they never imagine someone would just assign the state like that because you just don't do that in their system. It's an unwritten rule for most likely 99.9% of production systems out there... The result is a bug in production, and not an obvious debug session because the coder and the review don't see the bug since their brain trick them into thinking the line is correct...Second, you lose context of your state. When you do not use
Immer
and rebuild the state from the ground up in your reducer, you always see the whole story. But when usingImmer
, you only see whatever changes without any context. This is potentially very dangerous because you might not take into account everything you should.Third, if you really need
Immer
to clean up the code, then I would argue your piece of state is way too complicated. You should split it up or flatten it, there's too much cognitive load in that state.I used to always mutate in-place until I got these nasty bugs that took hours to debug.
Immer
will fix it in your code, but not in your head. I would argue fixing the concept in your head is going to be much more important for you than fixing it for that particular line of code.#45
MemoizeYou definitely want to make your code super easy for consumers, no argument there. But I think you misunderstood what I meant here. I meant that the
fetchData
needs to be stable to begin with.Even if you memoize the value at the consumer level, if the parent doesn't have a stable reference, then the memoization in the consumer will re-run on render.
I see you used a
ref
to avoid the memoizationre-run
in the consumer. Don't get me wrong, this is a clever pattern, but it's just a band-aid. You are going to have to do this in every consumer now, and for the lifetime of the app, and all developers in your app will have to do the same, etc. The technical debt balloons very fast.By having a stable reference in the parent, the problem is solved at 1 place. You won't have to add code anywhere else; you solved it once and no consumer needs to worry about it. Simplicity at its finest.
I would also argue that a
re-render
is not the end of the world either. You can squeeze every ounce of performance out of your code, but if your performance budget is not totally used at the end of the render cycle, it's thrown out. So any excess in performance you got might not be perceivable by users anyway. You prematurely optimized code that didn't even needed it, this is time that could have been spent elsewhere. We all should strive for performant apps, but at some point there are diminishing returns and it's our job to figure out when it's necessary and when it's not.Also, this might not even be necessary at all now with the new React Compiler. The function will have a stable reference in the parent without requiring you to do anything about it. On the other hand, your pattern will still leave a lot of code in lots of consumers, which is less performant and less maintainable than no code at all.
#66
- idsid
should be unique by element, it has nothing to do withname
. It's how theHTML
syntax works.The
span
and theinput
should not, at any point, have the sameid
. Browsers will let you do it just fine, but developers will have a hard time when querying byid
.You should not ignore this because we're in React here. It doesn't matter, it is a bug. That's why I pointed it out.
#8
*IIFE * — I see your point. I am used to the syntax so that is probably why I missed that it could be hard for new devs. I will adapt it! Thanks :)#37
Immer— I see your point. However, I will still leave it as a tip since it can really make life easier. I will defer to people's judgment.#45
Memoize — I am not following your reasoning. The whole point with this hook is that consumers don't have any memoization to do. I am aware that React Compiler is coming: which gives even more sense to not having memoization be done by consumers. Whether or not memoization is needed, everyone's codebase is different. I prefer to memoize hooks' returns values (like libraries do) from experience and given my situation, but obviously, everyone is free to memoize or not ;)#66
ids — Oh sorry, I missed that. You are right: ids should always be unique. Replacing it!#45
- MemoizeAh I see, we simply are not focusing on the same thing😅
The problems with the example are manyfold.
1. The
ref
+useEffect
is memoization with extra stepsIf you simply wanted to memoize everything, you should do this:
The
useCallback
dependency array solves all your requirements and is much simpler.Instead, your "good" example replaced the standard dependency array method by 1 indirection (the
ref
) and 1 extra step for synchronization (theuseEffect
). Plus, now the value contained infetchDataRef
is not calculated at render, but at theuseEffect
phase so you added an asynchronous step to the state of your component.You don't need the
ref
or theuseEffect
steps at all. The dependency array was enough to adjust the value without any extra step. As a bonus, the value is calculated at render and in a synchronous way.Furthermore, the code is much simpler this way. You don't have to keep all these extra steps in your memory to understand how it works. The cognitive load is lower, and this makes it easier for people to understand the code.
2.
fetchData
is not memoized at all...🚨 When you want to memoize a value, you need to do it when you define the variable that holds the value.
Given that
fetchData
is defined outside the hook, as it's passed by the consumer, memoizing it here doesn't have an impact on re-renders in the consumer.If
fetchData
is defined in the consumer and you didn't memoize it at creation time, then it will be recalculated on every render of the consumer. What was the point insynchronizing
the new function reference inuseLoadData
if you could have simply used the new function to begin with?If
fetchData
came from the parent of the consumer, then any state change in the parent would re-render the consumer (because the consumer is achildren
of theparent
).Your memoization in the
consumer
itself didn't prevent a re-render of the consumer at all in this case. Again, what was the point in using all that extra code if it's going to be re-calculated anyway?I would even ask what was the point of
useMemo
inuseLoadData
?You need to execute the whole hook to know if 2 references have changed. If they did change, you are returning the 2 new references. If they didn't change, you are returning the 2 old references. In both case, you return 2 references, all the calculations have been done already and you memoized the tiniest and fastest possible data ever, so what did you save by adding so much code around it?
Don't get me wrong, I think your whole article is amazing. I'm simply pointing out that you might need to revisit the memoization concepts because there's a tiny gap there. And I cannot stress enough that you get it better than 99.99% of developers, even if you have a tiny gap missing.
This is also why most of the time it's better not to use memoization at all. It's a very easy concept to grasp, but it's a very hard one to apply correctly. And mistakes in applying it are worse than not applying it at all.
So, I still think there is a misunderstanding here. I am using a ref here instead of useCallback dependencies because I explicitly don't want my callback to change, which will be the case if I use a dependencies array (all items will need to be memoized).
Here, what I like in this pattern is that the entire complexity lives in this single hook.
Check similar implementations like in here or here for example.
I understand what you want to achieve, and I agree to a certain degree. If the hook didn't receive a parameter that is NOT memoized, I would be 100% behind the principle.
The flaw I'm pointing at is that the hook is getting a parameter that is not memoized. In turn, this has a few adverse effects...
One of them is that it's most likely slower to run the
useEffect
to synchronise thefetchDataRef
than actually redeclaring theloadData
function every render. Memoizing is supposed to be used for expansive compute, this is just a function declaration so there's no expansive compute.Another adverse effect is you have complexified your code a lot. Developers now have to take into account the
useCallback
implications AND theuseEffect
synching. Your code would most likely be just as fast without any memoization at all and would be much simpler. The "bad" example is actually good because you didn't mesure what you saved; you did premature optimization without an ounce of evidence.The worse adverse effect is the bug probability part. When reading a
useCallback
with an empty dependency array, most will take for granted that nothing can change the function. But this is not true because you can change thefetchData
function and fetch something completely different between 2 calls. Making a wrong assumption here can lead to many bugs down the line. Your good intention is opening the door for many possible problems because you didn't use the standard dependency array. You can't expect developers to follow uncommon patterns without making mistakes.Remember, any state update of the consumer component and all its hooks will re-render the consumer and all its children. The only way to stop a re-render down the chain is to either use
{children}
in the JSX or to usememo()
on a component or function. Anything else needs to execute the function/component to know if they should or not apply changes to the DOM. What you saved in the "good" example is the reference pointer to the values, you're not memoizing the execution of any code here, which kind of defeat the point of the memoization in your example.But at the end of the day, it's not the end of the world. I know I'm picking on a detail here, there are much worse things in the day to day code. I just hope that what I wrote and the "argument" we had will shed some light for people reading the article.
Thanks for your article and taking the time to explain more your ideas, it's really appreciated 👍
This article should be a must-read for anyone working with REACT!
So glad you like it Alberto ☺️
Thank you for the fantastic post!
Thanks 🙏
A very impressive article!
Thanks Nawael 🙏
Amazing article, very thorough. Kudos 👏
Thanks Cecile
Nice piece
Thanks!
Wow, really good piece. I came to read the post from your comment on it. dev.to/devteam/what-was-your-win-t...
Thanks so much! Glad you liked it ☺️
Great!! Mega-helpful. Thanks
So glad you like it ☺️
Thanks for taking time to put together the best practices and standards. This link will definitely go into our confluence page.
Thanks a lot GP!
Super glad you like it 🙏
Amazing
Wouaw ! Thanks 🙏