Reading the Official React Documentation
- front
- react
I started with React using the now-legacy previous official documentation. I recall beginning with the documentation for creating a tic-tac-toe game, then moving on to Velopert's Modern React to build a to-do list.
However, that was quite some time ago, and now the React official documentation has been revamped. I will briefly summarize some parts that I was previously unaware of by reading through the new documentation.
1. JSX
If you need to convert a lot of HTML to JSX, you can use an online converter.
JSX is stricter than HTML. Tags must always be closed, such as <br />
, and multiple JSX tags cannot be returned. This is because JSX is essentially JavaScript, which cannot return multiple values from a function.
2. React Hooks
Functions that start with use
are referred to as hooks in React, and these hooks should only be called at the top level of a component or another hook (likely a custom hook). This means you cannot use hooks directly inside conditional or looping statements. If you need that functionality, you must create a new component.
There are also built-in hooks provided by React, such as useState
, which users can combine to create new hooks, known as custom hooks.
3. Lifting State
When multiple child components need to share the same state, it is recommended in the official documentation to place the state in a parent component and pass it down to the child components via props. This process is referred to as lifting state. This allows child components to easily maintain synchronized states.
4. Designing with React
When structuring the UI with React, you should first break it down into components and then think about the states that each component should represent. After that, you will design how data flows among the components.
4.1. Component Division
When dividing components, you can follow the Single Responsibility Principle by extracting portions that have specific roles into separate components, or you can divide components based on CSS to facilitate better use of class selectors.
Thinking about design composition while dividing components is also beneficial. However, since UI and data models usually go hand in hand, if the data is well-structured, it should not be difficult to split components accordingly.
4.2. Managing State
Once the site's structural design is complete, the static structure of the components should be in place. You should then design the application's states to be minimal.
For example, if an array is stored as state, the length of the array can be computed from the state, meaning it should not exist as a state itself. Immutable values should also not exist as state. State is meant to facilitate user interactions.
Once you have envisioned the minimal states, consider which components should hold those states.
5. Key Props
When rendering elements within an array as components using the JS array method .map(item, index)
, you must include a unique value called key
for each component. This allows React to detect what has changed among the components and determines what to re-render, acting as a unique ID for the component.
const listItems = numbers.map((number) =>
<li key={number}>
{number}
</li>
);
When the list is re-rendered, React compares the keys of the previous list with those of the updated list. If the updated list has a key that did not exist before, React creates a component for that key. Conversely, if a key from the previous list does not appear in the updated list, React removes the component for that key. If a key exists in both the previous and updated lists, React will update or move the corresponding component.
In summary, key
provides React with the unique identifier for each component, indicating which components are added, removed, or updated during re-rendering.
Although key
appears similar to props, it is special and reserved. React uses the key
prop internally to decide which components to update.
Thus, assigning appropriate keys when rendering dynamic lists is crucial. It is not advisable to use array indices as keys, as React will throw an error and automatically use the index as a key if none is explicitly assigned.
If a key changes, React will remove and recreate the component, while the index can change too easily due to array modifications. Using a unique value from each array element as a key allows for the targeted updating of the corresponding component when an element is edited.
Additionally, keys do not need to be globally unique and only need to be unique among the component and its siblings.
6. React Frameworks
Many boilerplates facilitate starting React projects easily, such as create-react-app or Vite. However, various frameworks incorporate commonly required functionalities like routing, data fetching, and HTML generation when working on React projects.
A prominent example is Next.js, which powers this blog. Other similar full-stack React frameworks include Remix, known for static site generation, and Gatsby, among others. Next.js is managed by Vercel, while Gatsby is supported by Netlify.
6.1. Benefits of Using Frameworks with React
It is possible to use React without a framework. Originally, React's advantage was its capability for incremental migration via methods like render
. However, if you plan to build an entire page in React, using a framework is advisable.
During development, you will frequently need to implement routing, data fetching, preloading, and sometimes want static HTML builds. Implementing these functionalities from scratch requires significant time and effort, and learning how to use libraries is necessary. Moreover, self-configuring environments can make it difficult to receive assistance from others since everyone may have experience with different setups.
Using a framework allows you to start developing quickly, as many components are already configured. Additionally, if issues arise, you can seek help from the framework's community.
6.2. Frameworks and React
The React team collaborates with several well-known React framework developers. For instance, they discuss React features such as React Server Components with developers from frameworks like Next.js.
You can experiment with these server components in the Next.js App Router documentation. Features like server components and Suspense are part of React but have been implemented in Next.js first due to the challenges of applying them directly to React.
6.3. Editor Setup
Here are some helpful official documentation links for VSCode.
7. Adding React to Existing Projects
The emergence of React boilerplates like CRA has made starting new projects with React seem standard. However, React's strength lies in its ability to enable gradual migration, allowing you to incrementally integrate React into existing projects.
You can approach it in two ways: building certain pages with React or rendering parts of individual pages as React components.
7.1. Adding React Pages
Suppose you have a page built with another server technology, such as Ruby on Rails, and you want to build specific routes with React—let's call this page witch.com
. For example, if you want to create all routes starting with /witch
using React.
First, you would set up that page in React, potentially using a framework like Next.js. Then, in the framework's configuration file, you would set the base path for the desired route. If you want the /witch
path to be the root for the React page, configure it as follows:
If using Next.js, edit the next.config.js
:
module.exports = {
basePath: '/witch',
}
Then, you need to set up a proxy on the server to route all requests going to /witch
to the React page.
7.2. Adding React Components to Existing Pages
You can also choose to use React only for specific components on an existing page. Meta has extensively utilized this method for a long time.
Start by installing JSX syntax and the React library via npm. Create the desired React components and render them as needed.
You will also need to configure the settings for compiling JS modules, which can be easily accomplished using Vite. There’s even a repository compiling code on integrating Vite with various backend frameworks.
First, install React:
npm install react react-dom
Then, use createRoot
and render
to render React components within a DOM element:
import {createRoot} from 'react-dom/client';
const root = document.getElementById('root');
createRoot(root).render(<App />);
This approach can be observed in the structure of the main.tsx
file when initially creating a project with Vite's TypeScript template. It finds the tag with the root
ID and renders the React component within it.
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
This operation can be performed on any tag within the existing application. By assigning a unique ID and using getElementById
to find the tag, then createRoot
and render
to output the React component, you can proceed.
For example, if there is a pre-existing header element on the page, you can render a React component into that header tag:
<!-- ...skipped... -->
<header>
<div id="header"></div>
</header>
<!-- ...skipped... -->
You can find the tag with the ID header
and render the React component inside it as follows:
import { createRoot } from 'react-dom/client';
// Assume that the component to go inside the header is already created
import Header from './Header';
const header = document.getElementById('header');
const root = createRoot(header);
root.render(<Header />);
This way, you can progressively migrate elements of the page to React.
8. Types in React
This section introduces some types from the @types/react
and @types/react-dom
packages that provide type definitions for React elements. It includes types related to hooks and others that may be useful.
You can install the React types with npm install @types/react @types/react-dom
. Additionally, you must use the .tsx
file format to leverage TS with JSX.
8.1. useState
useState
is the most fundamental hook in React. This hook infers the state type based on the initial state provided.
// The type of count is inferred to be number.
// The type of setCount is inferred to be a function type that receives a number or returns a function that returns a number.
const [count, setCount] = useState(0);
You can also directly provide the state type for useState
using generics, which is useful for defining union type states.
type Theme = 'light' | 'dark';
const [theme, setTheme] = useState<Theme>('light');
8.2. useReducer
useReducer
is similar to useState
but updates state through a reducer. The reducer function's type is also inferred from the initial state. While you can provide types directly via generics, it is usually better to allow inference from the initial state.
type Action = { type: 'increment' } | { type: 'decrement' };
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
}
}
// When used later
const [count, dispatch] = useReducer(reducer, 0);
8.3. useContext
The useContext
hook is used to pass data through the component tree without having to prop drill. Typically, a custom hook is created to pass values down to child components.
The type of the value provided by the context is inferred from the value passed to the createContext
function. It can also be provided separately using generics.
type Theme = 'light' | 'dark';
const ThemeContext = React.createContext<Theme>('light');
If there are cases where there is no initial value, set the generic type to Theme | null
and perform null
checks when using useContext
to narrow the type.
8.4. useMemo, useCallback
useMemo
and useCallback
infer the return type of the hook's result from the type of the function passed as the first argument. You can also provide types generically for the hooks.
// The return type of memoizedValue is inferred from the return type of computeExpensiveValue
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallback
infers the parameter and return types of the callback function.
// The type of onClick is inferred as (e: React.MouseEvent<HTMLButtonElement>) => void.
const onClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
console.log('button clicked');
}, []);
You can use the EventHandler
type provided by React based on your preferences.
const handleClick = useCallback<React.ClickEventHandler<HTMLButtonElement>>((e) => {
console.log('button clicked');
}, []);
8.5. DOM Events
React wraps DOM events and provides them. Event types can often be inferred from event handlers, but if you want to create functions tailored for event types, you can provide event types directly.
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log('button clicked');
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log('input changed');
}
You can find the types of all events in the MDN Event Reference.
All event types' base type is React.SyntheticEvent
.
8.6. Children
There are two widely used methods to represent child components. One is React.ReactNode
, which is the union of all types that can be passed as children in JSX.
The second is React.ReactElement
, which represents only JSX elements and excludes primitive values like strings or numbers.
It is also impossible to set a children type that only accepts specific types of JSX elements, e.g., accepting only <section>
elements as children.
8.7. Style Props
When applying inline styles in React, use React.CSSProperties
. This serves as a union type of all possible CSS properties, allowing you to verify the validity of CSS properties.
interface Props {
style: React.CSSProperties;
}