Building client-side apps with React
Learn how to create client-side apps using React
React makes dealing with the DOM in JavaScript more like writing HTML. It helps package up elements into "components" so you can divide your UI up into reusable pieces. You can try out the examples online by creating a new React playground.
Quick summary
A lot of React concepts are explained in detail below. If you just want to get started quickly here's a code sample with the most important features:
ES Modules
ES Modules are a way to isolate your JS files. This is similar to Node's require
syntax, but built-in to the JS language (rather than a proprietary Node feature). React apps usually use ES Modules because they're built for the browser rather than the server.
Modern browsers support modules loaded from a script tag with type="module"
. This tells the browser that this script may load more code from other files.
Generally (for wider browser support) apps use a tool called a "bundler" to parse all the imports and bundle them into a single file that older browsers will understand.
Exports
Files can have two types of exports: "default" and "named". A file can only have one default export, but can have many named exports.
This is how you create a default export:
This is how you create a named export:
You can only default export a single thing, but you can have as many named exports as you like:
You don't have to export things at the end of the file. You can do it inline:
Imports
There are also two kinds of imports: default and named. The way you import a value must match the way you exported it.
This is how you import something that was default-exported:
Named-exports must be imported with curly brackets:
You can import as many named-exports as you like on the same line:
In the browser import paths must be valid URIs (e.g. "./maths.js"
or "https://cdn.pika.dev/lower-case@^2.0.1"
). This means you must include the file extension for local files. Node and bundlers often support leaving this off for convenience however.
React elements
Interacting with the DOM can be awkward when you just want to render an element:
This is frustrating because there is a simpler, more declarative way to describe elements—HTML:
Unfortunately we can't use HTML inside JavaScript files. HTML is a static markup language—it can't create elements dynamically as a user interacts with our app. This is where React comes in:
This variable is a React element. It's created using a special syntax called JSX that lets us write HTML-like elements within our JavaScript.
The example above will be transformed into a JS function call that returns an object:
Templating dynamic values
JSX supports inserting dynamic values into your elements. It uses a similar syntax to JS template literals: anything inside curly brackets will be evaluated as a JS expression:
You can do all kinds of JS stuff inside the curly brackets, like referencing other variables, or conditional expressions.
Note on expressions
You can put any valid JS expression inside the curly brackets. An expression is code that resolves to a value. I.e. you can assign it to a variable. These are all valid expressions:
This is not a valid expression:
if
blocks are statements, not expressions. The main impact of this is that you have to use ternaries instead of if
statements inside JSX.
React components
React elements aren't very useful on their own, since they're just static objects. To build an interface we need something reusable and dynamic, like functions.
A React component is a function that returns a React element.
Valid elements
Your components don't have to return JSX. A React element can be JSX, or a string, number, boolean, or array of elements. Returning null
, undefined
, false
or ""
will cause your component to render nothing.
Returning an array is especially useful for rendering lists from data:
Array items in JSX must have a special unique key
prop so React can keep track of the order if the data changes.
Composing components
Components are useful because JSX allows us to compose them together just like HTML elements. We can use our Title
component as JSX within another component:
When we use a component in JSX (<Title />
) React will find the corresponding Title
function, call it, and use whatever element it returns.
Customising components
A component where everything is hard-coded isn't very useful. Functions are most useful when they take arguments. Passing different arguments lets us change what the function returns each time we call it.
JSX supports passing arguments to your components. It does this using the same syntax as HTML:
Most people name this object "props" in their component function:
We can use these props within your components to customise them. For example we can insert them into our JSX:
Now we can re-use our Title
component to render different DOM elements:
Non-string props
Since JSX is JavaScript it supports passing any valid JS expression to your components, not just strings. To pass JS values as props you use curly brackets, just like interpolating expressions inside tags.
Children
It would be nice if we could nest our components just like HTML. Right now this won't work, since we hard-coded the text inside our <h1>
:
JSX supports a special prop to achieve this: children
. Whatever value you put between JSX tags will be passed to the component function as a prop named children
.
You can then access and use it exactly like any other prop.
Now this JSX will work as we expect:
This is quite powerful, as you can now nest your components to build up more complex DOM elements.
Rendering to the page
You may be wondering how we get these components to actually show up on the page. React manages the DOM for you, so you don't need to use document.createElement
/.appendChild
.
React consists of two libraries—the main React
library and a specific ReactDOM
library for rendering to the DOM. We use the ReactDOM.render()
function to render a component to the DOM.
It's common practice to have a single top-level App
component that contains all the rest of the UI.
Event listeners
JSX makes adding event listeners simple—you add them inline on the element you want to target. They are always formatted as "on" followed by the camelCased event name ("onClick", "onKeyDown" etc):
React state
An app can't do much with static DOM elements—we need a way to create values that can change and trigger updates to the UI.
React provides a special "hook" function called useState
to create a stateful value. When you update the value React will automatically re-render the component to ensure the UI stays up-to-date.
When this button is clicked we want the count to go up one:
We need to use the useState
hook. It takes the initial state value as an argument, and returns an array. This array contains the state value itself, and a function that lets you update the state value.
It's common to use destructuring to shorten this:
If we call setCount(1)
React will re-run our Counter
component, but this time the count
variable will be 1
instead of 0
. This is how React keeps your UI in sync with the state.
Lifting state up
React components encapsulate their state—it lives inside that function and can't be accessed elsewhere. Sometimes however you need several components to read the same value. In these cases you should "lift the state up" to a shared parent component:
Here FancyButton
and FancyText
both need access to the state, so we move it up to Counter
and pass it down via props. That way both components can read/update the same state value.
Updates based on previous state
Sometimes your update depends on the previous state value. For example updating the count inside an interval. In these cases you can pass a function to the state updater. React will call this function with the previous state, and whatever you return will be set as the new state.
We cannot just reference count
, since this is 0
when the interval is created. It would just do 0 + 1
over and over (so the count would be stuck at 1
).
Form fields
React apps still use the DOM, so forms work the same way:
React tries to normalise the different form fields, so behaviour is consistent across e.g. <input>
and <select>
. If you need to keep track of values as they update you can add an onChange
listener and value
prop.
Side effects
So far we've seen how React keeps your UI in sync with your data. Your components describe the UI using JSX and React updates the DOM as required. However apps sometimes need to sync with something else, like fetching from an API or setting up a timer.
These are known as "side effects", and they can't be represented with JSX. This means we need a different way to ensure our side effects stay in sync just like our UI.
Using effects
React provides another "hook" like useState()
for running side-effects after your component renders. It's called useEffect()
. It takes a function as an argument, which will be run after every render by default.
Here's our counter, with an effect to sync the document title with the count:
Calling setCount
will trigger a re-render, which will cause the Effect to re-run, so the title will stay in sync with our state.
Skipping effects
By default all the Effects in a component will re-run after every render of that component. This ensures the Effect always has the correct state values. However what if we had multiple state values? Updating unrelated state would re-run the Effect even if count
hadn't changed.
useEffect()
takes a second argument: an array of dependencies for the Effect. Any variable used inside your Effect function should go into this array:
Now the Effect will only re-run if the value of count
has changed.
Effects with no dependencies
Sometimes your Effect will not be dependent on any props or state. In this case you can pass an empty array, to signify that the Effect has no dependencies and shouldn't need to be re-run.
Here we want to show what key the user pressed, so we need an event listener on the window. This listener only needs to be added once:
Without the empty dependency array we would end up adding a new event listener every time the Effect re-ran. This could cause performance problems.
Cleaning up effects
It's important your Effects can clean up after themselves. Otherwise they might leave their side-effects around when the component is "unmounted" (e.g. if the user navigates to another page).
Our previous example needs to make sure the event listener is removed from the window. We can tell React to do this by returning a function from the Effect. React will call this function whenever it needs to clean up: both when the component is unmounted and before re-running the Effect.
React helps you remember to do this by running Effects twice during development (even if you pass an empty dependency array). This is designed to help you catch places where you forgot to clean up your Effect.
Last updated