Rules to Better React - 15 Rules
Want to build your web application in React? Check SSW's React consulting page.
There are so many open-source platforms for making the front-end web application development easier, some people like Angular, some people like React. Let us take a look at the benefits of React.
Simplicity
ReactJS is just simpler to grasp right away. The component-based approach, well-defined lifecycle, and use of just plain JavaScript make React very simple to learn, build a professional web (and mobile applications), and support it. React uses a special syntax called JSX which allows you to mix HTML with JavaScript. This is not a requirement; Developers can still write in plain JavaScript but JSX is much easier to use.
Easy to learn
Anyone with a basic previous knowledge in programming can easily understand React while Angular and Ember are referred to as Domain specific Language, implying that it is difficult to learn them. For React you just need basic knowledge of CSS and HTML.
Native Approach
React can be used to create mobile applications (React Native). And React is a diehard fan of reusability, meaning extensive code reusability is supported. So at the same time we can make IOS, Android and Web applications.
Data Binding
React uses one-way data binding and an application architecture called Flux controls the flow of data to components through one control point – the dispatcher. It's easier to debug self-contained components of large ReactJS apps.
Performance
React does not offer any concept of a built-in container for dependency. You can use Browserify, RequireJS, EcmaScript 6 modules which we can use via Babel, ReactJS-di to inject dependencies automatically.
Testability
ReactJS applications are super easy to test. React views can be treated as functions of the state, so we can manipulate state we pass to the ReactJS view and take a look at the output and triggered actions, events, functions, etc.
Great Developer Tools
Developer toolset is another important factor when you are choosing a development platform. There are 2 great tools you should be aware of: React Developer Tools and Redux Developer Tools. Both can be installed as Chrome extensions.
References
Here are the best collection of resources for React.
Free Resources
- The official documentation - React.dev
- React Enlightenment guide
- github.com/enaqx/awesome-react
- github.com/markerikson/react-redux-links
Training Courses
- Codecademy: React 101 - Codecademy's introductory course for React.
- Egghead.io: Start Learning React - This series will explore the basic fundamentals of React to get you started.
- React Crash Course 2018 - A beginner-friendly crash course through the most important React topics.
Books
- The Road to learn React - Beautifully written and approachable guide to learning React and ES6.
- Pure React - Well-written introduction for true beginners.
- Learning React: Functional Web Development with React and Redux - Covers React and Redux. For developers already comfortable with JavaScript.
Free Events
- Find your local JavaScript or React User Group on MeetUp
The old standard way to start a React project, create-react-app is no longer actively supported by Facebook, and has been removed from the official developer documentation (https://react.dev/learn/start-a-new-react-project). Therefore, it is not the best choice for starting a client-side rendered React app.
Vite
Vite is a reliable frontend build tool for building fast and optimised frontend web apps that has easy integration with a wide range of frontend web frameworks, and built-in Typescript support.
Vite is much faster than using create-react-app, mainly because Vite does not use Webpack for bundling assets. It instead uses esbuild and Rollup, which are much faster and more modern tools for building great frontend web apps.
Note: Vite requires Node version 14.18+ or 16+.
- Run:
npm create vite@latest
- Enter the name of your project
- Select "React" from the list of frameworks
- Then, select the "TypeScript" variant from the list
- All done! Now navigate into the directory and run the app with the following commands:
cd {{ PROJECT_NAME }} npm install npm run dev
NextJS
As per the official React docs (https://react.dev/learn/start-a-new-react-project), the recommended way to start a new React project is with NextJS.
It is recommended in NextJS official docs (https://nextjs.org/docs/pages/api-reference/create-next-app) to start a new NextJS project by using:
npx create-next-app@latest
Developers can then manually choose how they want to set up their NextJS app.
Gatsby
As per the Gatsby docs (https://www.gatsbyjs.com/docs/quick-start/), the most effective approach to start a new Gatsby site is by executing the following command:
npm init gatsby
Through the provided prompts, you can also select your preferred language (JavaScript or TypeScript), CMS, and styling system that you intend to use.
Creating a Production Build of a React project is complicated, you need great tools.
Webpack
Webpack is a module bundler. It packs CommonJs/AMD modules.
Create React App
npm run build
Create React App uses Webpack under the hood.npm run build creates a build directory with a production build of your app.
When working with Node.js, choosing the right package manager can significantly impact your project's performance, consistency, and ease of use. While npm is the default, developers often seek alternatives like Yarn, Bun, or pnpm for various advantages. But which one should you use?
1. pnpm (Recommended ✅)
- Efficient Disk Space Usage: pnpm uses a content-addressable file system to store all files in a single place on the disk. This means multiple projects can share the same packages, reducing disk space usage
- Fast and Reliable: With pnpm, package installations are faster because it avoids duplicating files in
node_modules
. Instead, it creates hard links, which makes the process quicker and more efficient - Strict Dependency Management: pnpm enforces stricter rules for dependency resolution. Unlike npm and Yarn, pnpm prevents "phantom dependencies," ensuring that your project is more predictable and less prone to errors
2. npm
npm is the default package manager bundled with Node.js. It is straightforward to use and integrates seamlessly with the Node ecosystem.
Notable Incident: In 2016, the removal of the "left-pad" package from npm caused widespread issues, making developers reconsider their reliance on the platform.
Pros:
- Comes pre-installed with Node.js, so no additional setup is needed
- Vast package registry with millions of packages
Cons:
- Slower compared to pnpm and Yarn
- Issues with dependency resolution and "phantom dependencies."
3. Yarn
Yarn was developed by Facebook to address some of npm's shortcomings, such as speed and reliability.
Pros:
- Faster than npm, especially with the offline cache feature
- Better dependency management and deterministic builds with Yarn's
yarn.lock
file
Cons:
- Slightly more complex to configure compared to npm
- Still not as space-efficient as pnpm
4. Bun
Bun is a newer entrant that aims to be an all-in-one tool for Node.js, combining package management with a fast JavaScript runtime and bundler.
Pros:
- Extremely fast, built from the ground up in Zig, a systems programming language
- Includes built-in support for TypeScript and JSX, making it attractive for modern web development
Cons:
- Relatively new and less mature than the other options
- Smaller community and less extensive documentation
While npm, Yarn, and Bun each have their strengths, pnpm is the recommended package manager for most Node.js projects. Its efficient use of disk space, faster installations, and stricter dependency management make it a superior choice. However, the best package manager for you may depend on your specific project's needs and your team's preferences.
State management is complex and time-consuming. The redux pattern helps resolve this issue.
The 4 principles of the redux pattern
- The entire state of the application is represented in a single JavaScript object called a store.
- The store is acted upon using special functions called reducers.
- State is immutable and reducers are the only part of the application that can change state.
- Reducers are pure JavaScript functions. This means they cannot import external dependencies.
Side Effects
To perform operations that require external dependencies (such as communicating with a web server), we can implement side effects. These can use external dependencies but they cannot directly modify the store. They can invoke reducers to modify the store when the side effect is complete.
Redux-Saga is a library that provides redux application side effects.
The advantages of using Redux-Saga are:
- Collects all asynchronous operations in one place, making the code clearer
- Uses an ES6 feature called Generators to make asynchronous flows easy to read, write and test
- Generators also let these asynchronous flows look like your standard synchronous code (kind of like async/await in C#). This solves “callback hell"
There are many example projects created by the React community.
Below is a list of sample applications from https://reactjs.org/community/examples.html
- Calculator Implementation of the iOS calculator built in React
- Emoji Search Simple React app for searching emoji
- Github Battle App Battle two Github users and see the most popular Github projects for any language.
- React Powered Hacker News Client A React & react-router-powered implementation of Hacker News using its Firebase API.
- Pokedex The list of Pokémon with live search
- Shopping Cart Simple ecommerce cart application built using React
- Progressive Web Tetris Besides a beautiful, mobile-friendly implementation of Tetris, this project is a playground for integrating and experimenting with web technologies.
- Product Comparison Page Simple Product Compare page built in React
- Hacker News Clone React/GraphQL Hacker News clone rewritten with universal JavaScript, using React and GraphQL.
- Bitcoin Price Index Simple bitcoin price index data from CoinDesk API.
- Builder Book Open source web app to write and host documentation or sell books. Built with React, Material-UI, Next, Express, Mongoose, MongoDB.
- GFonts Space A space which allows user to play with Google fonts. Built with React, Redux and React-Router.
While using a regular
useEffect
to run when a component is loaded to fetch data is super easy, it may result in unnecesary duplicate requests for data or unexpected errors when unmounting components. It is best to use a library that can provide hooks for fetching data, as not only does it solve the above issues, but also comes with useful features such as caching, background updates, and pre-fetching.Below is an example of a standard data fetch in React:
const Component = () => { const [data, setData] = useState({}); const [loading, setLoading] = useState(true); useEffect(() => { fetch("https://jsonplaceholder.typicode.com/todos/1") .then(res => res.json()) .then(json => { setData(json); setLoading(false); }) }, []) return ( {loading ? <> {/* Display data here */} </> : <p>Loading...</p> } ) }
Figure: The traditional way of fetching data in React
This example is not ideal, as it means every time we reload this page component, or if we make the same request on another page, there will be an unnecessary request made instead of pulling the data from a cache.
Below are the two recommended options that both serve effectively the same purpose in providing developers with useful hooks for fetching data. These libraries not only give developers a wide range of other features, but also reduces the amount of boilerplate code they have to write.
TanStack Query (previously React Query) - Recommended
TanStack Query is a feature-rich data fetching library developed by Tanstack. It can be used with existing data fetching libraries such as Axios, GraphQL packages such as graphql-request, or just plain fetch.
Video: React Query in 100 Seconds by Fireship (2 mins)Here's a basic example of how you can use Tanstack Query:
import { useQuery, QueryClient, QueryClientProvider, } from "react-query"; const queryClient = new QueryClient(); function useTodos() { return useQuery("todos", async () => { const res = await fetch("/api/todos"); const json = await res.json(); return json; }) } export const Page = () => { const { status, data, error, isFetching } = useTodos(); if (status === "error") return <div>Error loading data: {error}</div> if (status === "loading") return <div>Loading...</div> return ( <QueryClientProvider client={queryClient}> <div> <div>{/* Display todos here */}</div> {isFetching && <p>Re-fetching data in the background...</p>} </div> </QueryClientProvider> }
This code employs the useQuery hook for asynchronous data fetching and a QueryClientProvider to manage the query cache in the component tree.
Some features of Tanstack Query:
- Request caching - key values pairs using
useQuery
- Duplicate request flattening -
- Background data fetching - using the
isFetching
value - Automatic retries - failed fetches are retried with the
retry
andretryDelay
options inuseQuery
, allowing you to specify the number of retries before giving up - Built-in pagination - using the
data.hasMore
value - Automatic revalidation - data revalidated on window focus - learn more
- Prefetching data - using
prefetchQuery
- Optimistic updates - if a request made fails, but a state variable in UI has already been updated optimistically, Tanstack Query can revert to the old UI state - learn more
- Suspense - built-in support for React 18's Suspense with the
{ queries: { suspense: true }}
option added toQueryClient
- Scroll Restoration - maintains the exact position you are scrolled on a webpage - learn more
- React Query DevTools - a Chrome extension that allows for easy debugging of data fetches + caching - learn more
You can find out more about Tanstack Query at tanstack.com/query.
SWR
SWR is an alternative to Tanstack Query developed by Vercel, the team behind Next.js. Much like Tanstack Query, SWR is library-agnostic, meaning you can use whatever data fetching library you are comfortable with.
Here's a basic example of how you can use the library's fetching hook:
const fetcher = (url) => fetch(url).then((res) => res.json()); export const Page = () => { const { data, error, isLoading } = useSWR("/api/todos", fetcher); if (error) return <div>Error loading data</div>; if (loading) return <div>Loading...</div>; return <div>{/* Display todos here */}</div>; };
Some features of SWR:
- Small bundle size - only 4.4 kB
- Caching - automatic caching of requests to avoid making duplicate requests
- Duplicate request flattening - a global cache to prevent different components fetching the same data
- Automatic revalidation - can happen when either the page is focused or when a user reconnects to the internet
- Pagination - using the
useSWRInfinite
hook - Suspense - Built-in support for React 18's Suspense with the
{ suspense: true }
option - Next.js - Integration with Next.js's SSR/SSG capabilities
- Real-time data - support for technologies such as WebSockets and SSE with the
useSWRSubscription
hook
Note: Currently, the vast majority of SWR APIs are not compatible with the App router in Next.js 13.
You can find out more about using SWR at swr.vercel.app.
RTK Query
Additionally, RTK Query, part of the Redux Toolkit, is a similar library to SWR and React Query with tight integration with Redux and seamless type-safe importing sourced from OpenAPI specifications.
Here's a basic example of how you can use RTK Query:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; const todosApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: "/api" }), endpoints: (builder) => ({ getTodos: builder.query<Array<Todo>, void>({ query: () => "todos", }), }), }); const { useGetTodosQuery } = todosApi; // For use with Redux const todosApiReducer = todosApi.reducer; const TodoPage = () => { const { data, isError, isLoading } = useGetTodosQuery(); if (isLoading) return <p>Loading...</p>; if (isError) return <p>Error fetching todos</p>; return <div>{/*( Display todos here */}</div>; };
Some features of RTK Query:
- Seamless Redux integration: Designed as part of the Redux Toolkit, RTK Query is intrinsically designed to work with Redux, providing a cohesive data management experience. Learn more
- OpenAPI schema code generation: Auto-generates end-to-end typed APIs based on OpenAPI schemas, drastically reducing boilerplate and ensuring type safety. Learn more
- Caching - cache management based on endpoint and serialized arguments - learn more
- Automatic retries - built-in mechanism to automatically retry failed queries, enhancing resilience - learn more
- Prefetching - fetches data in anticipation of user actions to enhance UX - learn more
- Parallel and dependent queries: Efficient handling of multiple simultaneous or dependent data fetching. Learn more
Discover more about RTK Query in Redux Toolkit's official documentation at redux-toolkit.js.org/rtk-query/overview.
- Request caching - key values pairs using
When developing Angular or React, there are lots of choices for code editors. The best experience by far is to use Visual Studio Code.
Many experienced teams are using Visual Studio for their C# backend and loving using Visual Studio Code for their Angular or React projects.
The recommended extension for Visual Studio Code is Angular Essentials from John Papa.
Angular Essentials is actually a meta-package that includes a list of great extensions for Angular Development – and this list may be updated in the future as more extensions become popular.
See https://github.com/johnpapa/vscode-angular-essentials.
You can find more extensions at Visual Studio | Marketplace.
Open in Visual Studio Code
This extension is for those times where you have a project open in Visual Studio and you want to be able to quickly open it in Visual Studio Code.
The whole React ecosystem improves every month. Tons of additional tools, libraries and components are released to simplify the developer’s job and minimize the required effort.
Starter kits
Developers still struggle on making a decision on how to setup their React project when joining the React community. There are thousands of boilerplate projects to choose from and every boilerplate project attempts to fulfil different needs. They vary in a range of minimalistic to almost bloated projects. Here are 4 great Starter kits for React developers.
- Vite - A modern tool for quicker development and building, compatible with different front-end frameworks, including React
- Create React App - A popular and easy way to start React projects with minimal setup
- Next.js - A flexible React framework that's great for making websites. It helps pages load fast and is user-friendly for developers while also providing SEO capabilities
- Gatsby - A powerful React framework for static websites and blogs with robust static site generation capabilities and SEO optimization
See more at Start a New React Project.
Utility Libraries for React
JavaScript ES6 and beyond gives you plenty of built-in functionalities dealing with arrays, objects, numbers, objects and strings. One of the most used JavaScript built-in functionalities in React is the built-in map() Array.
- Lodash is the most widespread utility library in JavaScript. Lodash comes with a powerful set of functions to access, manipulate and compose
- Ramda is also great utility library when leaning towards functional programming (FP) in JavaScript
Asynchronous Requests in React
- native fetch API - Nowadays, recent browsers implement the fetch API to conduct asynchronous requests. It uses promises under the hood
- axios - It can be used instead of the native fetch API when your application grows in size. Another alternative is called superagent
State Management Helpers
- Reselect - Creates a selector where the first functions passed in compute props for a final function. If none of those props have changed, then that function is not run and the result from the previous invocation is returned. This keeps the state from needlessly causing components to re-render
Global Serverless Deployments
- Vercel - Makes serverless application deployment easy
Testing
- Jest - Testing suite that provides a click-and-check API for automated in-browser smoke tests
The Single Responsibility Principle is a well understood, and well-accepted tenet of good code design. It states that a class should do one thing, and do it well - The same applies to Components used with Frameworks such as Angular, React, Vue and Blazor.
When designing components, keep them small, modular and reusable. For example, if you have a menu, put it into a menu component, don’t put it in your app component.
The main contenders for the best UI framework for React are:
MATERIAL-UI https://material-ui.com
Bootstrap https://getbootstrap.com
Tailwind https://tailwindcss.com/
Ant Design https://ant.design
MATERIAL-UI
MaterialUI is a set of React Components that Implement the Google’s Material Design Guidelines. When it comes to predefined components especially UI, one important thing we need to find is how many UI widgets are available and whether these can be customized with configurations. Material-UI has all components that you need and it is very configurable with a predefined color palette. Material UI is one of the best Reactjs based UI frameworks that have the most refined implementation of Material Design.
Get started: react-material-ui-official-docs-example
React Bootstrap
Bootstrap is one of the most popular and widely used CSS frameworks. It is no surprise to have the duo of React and Bootstrap. React Bootstrap is a set of React components that implement the Bootstrap framework. React-Bootstrap currently targets Bootstrap v5.3.
Get started: react-bootstrap-ui-official-docs-example
Tailwind
Tailwind is a popular utility-first CSS framework that simplifies the process of building user interfaces. It provides a comprehensive set of pre-built utility classes that can be easily combined to create responsive and flexible designs. With Tailwind, developers can quickly style their components without writing custom CSS, resulting in faster development and easier maintenance.
Get started: tailwind-example
Ant Design
Ant Design React is dedicated to providing a good development experience for programmers. An enterprise-class UI design language and React-based implementation. Ant Design is a set of high-quality React components out of the box which is written in TypeScript . It supports a browser, server-side rendering, and Electron environments have many components and even a tutorial with Create React App
Get started: ant-design-example
Typescript is the best choice when writing Angular and React applications. Angular is even written in Typescript itself!
Video: Typescript in 100 Seconds✅Advantages of Using TypeScript
-
Type Safety
- Error detection: - Identify and correct errors during the build phase, preventing runtime surprises.
- Top-notch tooling - Utilize enhanced features like autocomplete, Intellisense, efficient code navigation, and linting.
- Streamlined refactoring - Superior tooling simplifies refactoring compared to plain JavaScript.
- Embrace the latest - Leverage the latest language innovations for cleaner, more concise code.
-
Enhanced Code Expressivity
- Syntax sugar - Improves code readability and intuitiveness.
- Automatic imports - Streamline module integrations.
-
Wider Browser Support
- Multi-version targeting - Use a single TypeScript codebase across various JavaScript versions (e.g., ES5, ES6).
-
Boosted Code Confidence and Maintainability
- Minimized risk - Reduce the likelihood of bugs and bolster code reliability.
- Time efficiency - Dedicate less time to unit tests with increased code trustworthiness.
- Early bug detection - Identify and rectify issues early.
- Clarity - Craft cleaner, more transparent code.
❌ Disadvantages of TypeScript
- Learning curve - Developers unfamiliar with statically typed languages might face an initial learning challenge.
- Compilation step - An additional step to compile TypeScript to JavaScript can sometimes be perceived as a minor inconvenience.
- Integration with some libraries - Not all JavaScript libraries come with TypeScript definitions by default.
🔍 Explore TypeScript further at the official TypeScript website.
🎥 If you prefer video content, have a look at SSW TV Videos on TypeScript.
-
One of the main problems working on a huge monorepo solution is usually the development experience and the build time.Nx is one of many tools that can improve this experience in JavaScript projects.
The amount of code that needs to be processed by the compiler scales proportionally with the solution size. Hence, the compile time will grow naturally as the solution grows in size.This surely affects both the development experience and the team's velocity, leaving both developers and stakeholders unhappy.
Nx is a JavaScript build system that aims to make developing on monorepo solution easier and faster.Nx offers the following features:
- Cached build - faster development and build time
- Task Pipeline - provide tools to control how the monorepo build is performed
- Dependency graph - see the relationship between projects
- Affected graph - see which projects is affected by your commit
- and many more...
Currently, Nx supports many frameworks, such as Angular, React, Node, and many more.
Adding a tool such as Nx to a project will obviously add another moving parts to the solution, so it's a good idea to know the advantages and disadvantages of Nx.
Advantages:
- Faster development build time
- Faster CI time with Nx Cloud
- Monorepo collaboration tool with Package based or Integrated repo based strategy
Disadvantages:
- Additional external dependency to be maintained
- Learning curve
- Only supports JavaScript projects
Consider using Nx in a project when your solution:
- Is a JavaScript monorepo
- Is medium to large sized
- Contains multiple projects
- Share codes between projects
- Has slow build time
React Hooks streamline state management and lifecycle processes in functional components, resulting in cleaner, more performant code. These are the most common and useful hooks that you should use in your React project:
1. useState: Managing Local State 🧠
The
useState
hook lets you add state to functional components. CalluseState
at the top level of your component to declare one or more state variables.import { useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return <button onClick={handleClick}>You pressed me {count} times</button>; }
Figure: Using useState for a counter component
Naming Convention: It's a common convention to name state variables using the pattern [count, setCount] with array destructuring.
useState
returns an array with exactly two items:- The current state of this state variable, initially set to the initial state you provided.
- A function that lets you update its value.
✅ Recommended Usage
- Updating Objects and Arrays: Manage and adjust objects and arrays in the state. Remember, always create new references instead of mutating
- Avoiding Recreating the Initial State: Ensure the initial state is set only once, avoiding recalculations in subsequent renders
- Resetting State with a Key: Reset the component's state by altering its key
- Storing Information from Previous Renders: On rare occasions, adjust state as a reaction to a rendering process
⚠️ Pitfalls
- State Updates: A change in state doesn't instantly reflect within the current executing code. It determines what
useState
will return in future renders - Initializer Function: When you pass a function to
useState
, it gets called only during the initialization phase - State Updates with Functions: When deriving new state values from the previous state, it's better to use an updater function as an argument of the setter function instead of the new value i.e.
setObj(prev => { key: value ...prev }
. This ensures you're working with the most up-to-date state
Read more about
useState
on the offical docs2. useEffect: Side Effects & Lifecycles 🔄
In React functional components, useEffect serves as your toolkit to execute side effects, reminiscent of lifecycles in class-based components. Through dependencies, you can control when these effects run, granting granular control over side effect operations.
import { useState, useEffect } from "react"; export default function Counter() { const [count, setCount] = useState(0); useEffect(() => { const intervalId = setInterval(() => { setCount((c) => c + 1); }, 1000); return () => clearInterval(intervalId); }, []); return <h1>{count}</h1>; }
Figure: useEffect Count example
It's similar in concept to Angular's ngOnChanges lifecycle hook. While ngOnChanges in Angular detects and reacts to changes in input-bound properties, React's useEffect serves a broader purpose.
✅ Recommended Usage
- External System Connection: Link React components to other systems like APIs, networks, or third-party libraries
- Custom Hooks Encapsulation: Nest your effect logic inside custom hooks for clarity and better structure
- Non-React Widget Control: Bridge the gap between React components and non-React widgets
- Data Fetching: While
useEffect
can fetch data, it's optimal to use the framework's standard mechanisms or custom hooks - Reactive Dependencies: Identify reactive items (e.g., props, state) that influence your effect. When these alter, the effect kicks in again
- State Updates: Adjust state values referencing their former versions using
useEffect
- Access to Recent Props & State:
useEffect
ensures the most recent props and state are at your disposal - Distinct Server/Client Content: With effects operational solely on the client, you can orchestrate unique content for server and client views
⚠️ Pitfalls
- Placement: Ensure
useEffect
remains at the top level of your components/custom hooks. Bypass calling it within loops or conditionals - Avoid Overuse: Turn to
useEffect
mainly for external synchronization - Strict Mode Nuances: In strict mode, a preliminary setup+cleanup cycle, exclusive to development, verifies your cleanup's alignment with your setup
- Dependencies Oversight: If dependencies comprise inner-component objects or functions, they might trigger frequent effect repetitions
- Visual Effects Caution: A visual glitch before an effect suggests
useLayoutEffect
might be a better pick - Server Rendering:
useEffect
is client-centric and doesn't engage during server-side rendering
Read more about
useEffect
on the offical docs3. useContext: Using Context Seamlessly 🌍
useContext
is a pivotal React Hook, giving you the power to both read and subscribe to context values right within your component.import { createContext, useContext } from "react"; // Create a context const ThemeContext = createContext({ background: "light", foreground: "dark", }); function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); } export default function App() { return ( <ThemeContext.Provider value={{ background: "black", foreground: "white" }}> <ThemedButton /> </ThemeContext.Provider> ); }
Figure: A Themed button example using useContext
✅ Recommended Usage
- Reading and Subscribing to Context: Directly access and subscribe to context values straight from your component
- Passing Data Deeply: Bypass manual prop-drilling, letting you transmit data deeply through the component hierarchy
- Updating Data Passed via Context: Easily modify context values and integrate them with state for seamless updates across various components
- Specifying a Fallback Default Value: If no context provider is present upstream,
useContext
will resort to the default value established during context creation - Overriding Context: For tailored requirements, override the context in specific parts of the component tree by enveloping it in a provider with a distinct value
- Optimizing Re-renders: Amplify performance when transferring objects or functions through context using techniques such as
useCallback
anduseMemo
⚠️ Pitfalls
- Provider Position: The context search strategy employed by
useContext
is top-down, always eyeing the nearest provider. It disregards providers present in the invoking component - Server rendering: Similar to
useEffect
context providers can only be initialized and used within client components when using Next.js. Refer to the Next.js official documentation for information about using context providers in server components. - Re-rendering Children: Context shifts compel React to re-render all child components stemming from the provider with a changed value. The assessment hinges on the
Object.is
comparison, meaning that evenmemo
cannot fend off updates stemming from refreshed context values - Duplicate Modules: Be wary of build systems churning out duplicate modules (e.g., due to symlinks). This can disintegrate context as both the provider and consumer must be the exact same object, passing the
===
comparison test - Provider Without a Value: An absent value prop in a provider translates to
value={undefined}
. The default fromcreateContext(defaultValue)
comes into play only when there's a complete absence of a matching provider - Provider Cannot be Accessed: If
useContext
is used in a component that is not wrapped by a provider, this can cause client-side errors as the value accessed will be null
Read more about
useContext
on the offical docs4. useRef: Direct DOM Access & Persistent References 🎯
The
useRef
hook in React allows you to access and interact with DOM elements or maintain a mutable reference to values across renders without triggering a re-render.import { useRef } from "react"; function MyComponent() { const inputRef = useRef(null); function handleFocus() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleFocus}>Focus the input</button> </> ); }
Figure: On button click focus the input using useRef
✅ Recommended Usage
- Referencing a Value:
useRef
lets you reference a value that doesn't affect the rendering of your component - Manipulating the DOM: By attaching the returned ref object as a ref attribute to a JSX node, React sets its current property to that DOM node, allowing direct DOM manipulations
- Avoiding Recreating the Ref Contents: React preserves the initial ref value and doesn't recreate it during subsequent renders. This is beneficial for computationally expensive values
- Storing Information from Previous Renders: Refs persist their data across renders and can store information that doesn’t initiate a re-render
- Accessing Another Component's DOM Nodes: With
React.forwardRef()
, you can expose refs of the DOM nodes inside custom components
⚠️ Pitfalls
- Mutable current Property: Although
ref.current
is mutable, avoid mutating objects used in rendering - No Re-render on Change: Adjusting
ref.current
doesn’t trigger a re-render; React doesn’t detect changes to the ref - Avoid Reading/Writing During Rendering: Refrain from accessing or altering
ref.current
while rendering, except for its initialization - Strict Mode Double Render: In strict mode, React may execute your component twice for side effect detection. This double execution results in the ref object being created twice, though one is discarded
- Pure Component Behavior: React assumes your component is a pure function. Interacting with a ref during rendering contradicts this presumption
Read more about
useRef
on the offical docs5. useReducer: Advanced State Logic 📊
The
useReducer
is a React Hook that lets you add a reducer to your component, providing a more predictable state management method compared touseState
.import React, { useReducer } from "react"; function counterReducer(state, action) { switch (action.type) { case "increment": return { count: state.count + 1 }; case "decrement": return { count: state.count - 1 }; default: throw new Error(); } } function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 }); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: "increment" })}>Increment</button> <button onClick={() => dispatch({ type: "decrement" })}>Decrement</button> </div> ); }
Figure: React Counter Component Using useReducer
✅ Recommended Usage
- Adding a Reducer to a Component:
useReducer
allows you to manage your component's state using a reducer function - Predictable State Updates: Reducers specify how the state transitions from one state to the next, making state updates more predictable
- Handling Complex State Logic: It's suitable for managing state logic that's more complex than what useState can handle
- Avoiding Recreating the Initial State: React saves the initial state once and ignores it on subsequent renders. This is useful for values that are expensive to compute
- Dispatching Actions: Actions describe user interactions or events that trigger state changes. By convention, actions are objects with a type property
- Batching State Updates: React batches state updates, ensuring that the screen updates after all event handlers have run
⚠️ Pitfalls
- State Mutations: State in reducers should be treated as immutable. Avoid mutating state directly; always return new state objects
- Incomplete State Updates: Ensure that every branch in your reducer returns all parts of the state
- Unexpected State Values: If your state unexpectedly becomes undefined, it's likely due to missing state in one of the reducer cases or a mismatched action type
- Too Many Re-renders: This error typically indicates that you're unconditionally dispatching an action during render, leading to an infinite loop
- Impure Reducers: Reducers should be pure functions. Impurities can lead to unexpected behaviors, especially in strict mode where reducers might be called twice
Read more about
useReducer
on the offical docs