React guide
These notes are not intended as a comprehensive guide to the topic. Their purpose is to guide you through the main areas you should learn about, with resources provided for further exploration. The goal is for you to learn enough to complete the associated challenge.
Introducing Single Page Applications (SPAs)
- Traditional websites load a new HTML page from the server every time the user navigates
- Single Page Applications load one main HTML page and dynamically update the content using JavaScript
- This makes navigation faster and allows for a smoother, more app-like experience
- SPAs often communicate with APIs to load or update data without a full page reload
- Frameworks like React, Vue, and Angular are commonly used to build SPAs efficiently
Further Learning
Introducing React
- React is a JavaScript library for building user interfaces.
- It focuses on components, which are small, reusable pieces of UI that combine to form an application
- React can be used on its own or with additional tools such as routers, state management libraries, and build tools
Further Learning
Installing Node.js and npm
What is it?
- Node.js is a JavaScript runtime
- We’re used to using JavaScript in a browser
- Node.js allows us to run JavaScript outside the browser
- npm (Node Package Manager) is included with Node.js
- It is used to install and manage packages (also known as dependencies). This is third-party code that we can incorporate into our projects
- We can use it to install React, and other tools
Why do we need it?
- We need a way to compile code
- We also need a way to manage our third-party dependencies
- Node.js lets us run these tools on our computers
- npm provides access to the open-source libraries
How do we use it?
- Visit https://nodejs.org
- Install the LTS version of Node
- Run these commands to check that it has worked:
- Create a test folder and initialise npm:
mkdir test-project
cd test-project
npm init -y
- Check that a
package.json file has been created
Further Learning
Installing React with Vite, and create a “hello world” app
What is it?
- Vite is a modern build tool that we can use to set up a new project
- It is faster and simpler than older tools like Webpack and Create React App
Why do we need it?
- Vite is quick to get running
- We can use it to compile our code during development
- It also creates optimised builds for production
- It abstracts away complex configuration
How do we use it?
- Create a new Vite app:
npm create vite@latest my-react-app
- Choose React
- Move into the new folder and install dependencies:
cd my-react-app
npm install
- Run the development server:
- Open the local server URL (shown in terminal, e.g.
http://localhost:5173/) and confirm the React starter page loads
- Edit
src/App.jsx and check the browser updates accordingly
Further Learning
JSX
What is it?
- JSX stands for JavaScript XML
- It allows us to write HTML-like syntax inside JavaScript files
- JSX is not HTML, React converts it into JavaScript function calls
Why do we need it?
- It provides a cleaner, more readable way to describe the structure of UI components
- JSX allows developers to mix markup with logic, keeping related code together
- It helps React update only the parts of the DOM that change
How do we use it?
- Write JSX inside a React component file, usually with a
.jsx file extension (or .tsx if it’s TypeScript)
- Each component returns a single root element
- Embed JavaScript within JSX using
{} braces.
function Greeting({ name }) {
return <h1 className="title">Hello, {name}</h1>
}
export default Greeting
Further Learning
Components
What is it?
- Components are the building blocks of a React application
- Each component is a small, reusable piece of UI that can contain its own structure and logic
- Every React app starts with a main component (usually
App) which renders other components inside it
Why do we need it?
- Components make code more modular and easier to maintain
- They allow developers to reuse UI patterns across multiple parts of an application
- Each component can handle its own state and logic, keeping responsibilities clear and separate
How do we use it?
- Create a new file in the
src folder, for example Header.jsx
- Define a function that returns JSX
function Header() {
return (
<header>
<h1>My React App</h1>
</header>
)
}
export default Header
- Import and use the component inside another file, such as
App.jsx
import Header from './Header'
function App() {
return (
<div>
<Header />
<p>Welcome to the site</p>
</div>
)
}
export default App
- Components must begin with a capital letter to be recognised by React
Further Learning
Props
What is it?
- Props are short for properties
- They allow data to be passed from a parent component to a child component
- Props make components reusable by giving them configurable values
- In React, props are read-only, meaning a component cannot change the props it receives
Why do we need it?
- Props let components communicate with each other
- They make components flexible and adaptable to different data
- They allow you to separate layout from content so one component can display many variations
How do we use it?
- Create a new file called
Welcome.jsx
- Define a component that accepts props
function Welcome({ name }) {
return <h2>Hello, {name}</h2>
}
export default Welcome
- Import and use it inside
App.jsx
import Welcome from './Welcome'
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
</div>
)
}
export default App
- Confirm each instance displays a different greeting
Further Learning
Handling Events
What is it?
- Handling events means responding to user actions such as clicks, typing, or submitting a form
- React uses a consistent system for events that works across all browsers
- Event names use camelCase, and event handlers are passed as functions
Why do we need it?
- Interactivity in a React app depends on responding to user input
- Events let you trigger updates to state, display messages, or control navigation
- Understanding event handling is key to creating dynamic, responsive components
How do we use it?
- Create a new file
ButtonExample.jsx
- Define a component with a button and an event handler
function ButtonExample() {
function handleClick() {
alert('Button clicked')
}
return <button onClick={handleClick}>Click me</button>
}
export default ButtonExample
- Import and render the component inside
App.jsx
- Test that clicking the button shows an alert
- Try using other events such as
onChange, onMouseEnter, or onSubmit
Further Learning
Hooks
What is it?
- Hooks are special functions that let React components use features such as state and lifecycle logic
- They were introduced to replace the need for class components
- Common Hooks include
useState, useEffect, useContext, and useRef
- All Hooks start with the word
use
Why do we need it?
- Hooks make components simpler and more reusable
- They allow you to share logic between components without rewriting code
- They remove the need to learn class syntax or lifecycle methods
How do we use it?
- Import the Hook you need from React
- Call the Hook inside your component’s function (never in loops, conditions, or nested functions)
import { useState } from 'react'
function Example() {
const [text, setText] = useState('Hello')
return <p>{text}</p>
}
export default Example
Further Learning
useState
What is it?
useState is a Hook that lets you store and update data inside a component
- State represents information that can change while the app is running
- Each time state changes, the component re-renders with the new value
Why do we need it?
- Without state, components would be static and unable to react to user input
- State lets components keep track of changing values like form inputs or counts
- It helps manage interactive behaviour and dynamic updates
How do we use it?
- Import
useState from React
- Create a state variable and a function to update it
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
)
}
export default Counter
- Add more controls to decrease or reset the count
- Each state update triggers a re-render
Further Learning
useEffect
What is it?
useEffect is a Hook for running side effects in components
- Side effects are actions that affect something outside the component, such as fetching data or interacting with the browser
- It runs after the component renders
Why do we need it?
- Components often need to synchronise with external data or systems
useEffect helps manage effects like network requests, event listeners, or updating the document title
How do we use it?
- Import
useEffect from React
- Write an effect that runs after render
import { useEffect, useState } from 'react'
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const interval = setInterval(() => setCount(c => c + 1), 1000)
return () => clearInterval(interval)
}, [])
return <p>Seconds: {count}</p>
}
export default Timer
- The empty array
[] ensures the effect only runs once when the component mounts
Further Learning
Conditional Rendering
What is it?
- Conditional rendering means showing or hiding parts of the UI based on certain conditions
- In React, this is done using JavaScript expressions inside JSX
Why do we need it?
- Real apps often need to display different content depending on state or props
- Conditional rendering helps create responsive, context-aware interfaces
How do we use it?
function Message({ loggedIn }) {
return (
<div>
{loggedIn ? <p>Welcome back</p> : <p>Please log in</p>}
</div>
)
}
export default Message
- Use
&& for short conditions and ternary operators for two outcomes
Further Learning
Loops with .map
What is it?
.map() is a JavaScript method that transforms arrays
- In React, it is used to create lists of elements dynamically
Why do we need it?
- Data often comes as arrays, and we need to render each item as a component or HTML element
.map() helps render dynamic lists efficiently
How do we use it?
function List() {
const items = ['Apple', 'Banana', 'Cherry']
return (
<ul>
{items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
)
}
export default List
- Always include a
key prop for each list item to help React track updates
Further Learning
What is it?
- Forms let users enter and submit data
- In React, form inputs are controlled by state so that React manages the value
Why do we need it?
- Forms allow interaction and data input in almost every web app
- Controlled components make it easier to validate and manage input
How do we use it?
import { useState } from 'react'
function FormExample() {
const [name, setName] = useState('')
function handleSubmit(e) {
e.preventDefault()
alert(`Hello ${name}`)
}
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} />
<button type="submit">Submit</button>
</form>
)
}
export default FormExample
Further Learning
Prop Drilling and Lifting State
What is it?
- Prop drilling happens when data is passed through multiple layers of components unnecessarily
- Lifting state means moving shared state up to a common ancestor component
Why do we need it?
- These techniques help manage data flow between components
- They ensure that state lives where it logically belongs and can be accessed where needed
How do we use it?
function Parent() {
const [message, setMessage] = useState('Hello')
return <Child message={message} />
}
function Child({ message }) {
return <p>{message}</p>
}
- Move state to the nearest common parent of all components that use it
Further Learning
useContext
What is it?
useContext provides a way to share data without passing props manually through every level
- It uses a Context object created with
React.createContext()
Why do we need it?
- It prevents repetitive prop drilling
- Useful for global data like themes, user settings, or authentication
How do we use it?
import { createContext, useContext } from 'react'
const ThemeContext = createContext('light')
function Child() {
const theme = useContext(ThemeContext)
return <p>Theme: {theme}</p>
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Child />
</ThemeContext.Provider>
)
}
export default App
Further Learning
Async Data Fetching
What is it?
- Fetching data means requesting information from an external source such as an API
- React uses
useEffect with fetch or libraries like axios to load data
Why do we need it?
- Most apps rely on remote data
- Understanding how to fetch and display data is essential for building real-world apps
How do we use it?
import { useEffect, useState } from 'react'
function Users() {
const [users, setUsers] = useState([])
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => setUsers(data))
}, [])
return (
<ul>
{users.map(u => (
<li key={u.id}>{u.name}</li>
))}
</ul>
)
}
export default Users
Further Learning
Using third party packages
Small, focused packages can save time and reduce bugs. Add them when they clearly improve readability or remove boilerplate. Keep the number small and review them regularly
A simple example to add now
Format dates with date-fns
- Install
- Create a tiny helper so your components stay clean
src/lib/formatDate.js
import { parseISO, format } from 'date-fns'
export function formatDate(isoString) {
try {
return format(parseISO(isoString), 'd LLL yyyy')
} catch {
return isoString
}
}
- Use it in any component that shows a date
src/components/Users/Users.jsx
import { useEffect, useState } from 'react'
import { formatDate } from '../../lib/formatDate'
export default function Users() {
const [users, setUsers] = useState([])
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(r => r.json())
.then(setUsers)
}, [])
return (
<ul>
{users.map(u => (
<li key={u.id}>
<strong>{u.name}</strong>
<span> joined {formatDate('2020-05-01')}</span>
</li>
))}
</ul>
)
}
Where it goes
- Put small, reusable helpers in one location in your codebase, e.g.
src/lib or similar
- Keep third party usage behind your own helper when possible. It makes swapping or upgrading easier
Good habits with packages
- Read the project’s README and check its popularity on GitHub
- Ensure the new package is added to the
package.json file (this should happen automatically when you install it)
- Remove any packages you no longer use
- Run
npm audit occasionally to check you don’t have any dangerous dependencies, you may need to update them if so
Routing
What is it?
- Routing controls navigation between different parts of a React app
- It is commonly handled by libraries such as React Router
Why do we need it?
- SPAs use routing to change views without reloading the page
- It makes the app behave more like a traditional website
How do we use it?
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
import Home from './Home'
import About from './About'
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
)
}
export default App
Further Learning
Styling Approaches
What is it?
- React supports multiple ways to style components
- Common options include CSS files, CSS Modules, inline styles, and libraries such as Tailwind or styled-components
Why do we need it?
- Styling gives structure and visual clarity to an application
- Choosing a consistent method makes code easier to maintain
How do we use it?
import './App.css'
function App() {
return <h1 className="title">Hello React</h1>
}
export default App
Further Learning
What is it?
- React Dev Tools is a browser extension for inspecting React components and state
- It shows the component hierarchy and current prop and state values
Why do we need it?
- Debugging helps you understand what is happening in your app
- Dev Tools make it easier to find logic or state errors
How do we use it?
- Install React Developer Tools for your browser
- Open the React tab in DevTools while your app is running
- Select a component to inspect its props, state, and rendered output
Further Learning
Deploying to Netlify
What is it?
- Netlify is a hosting platform for static front-end projects
- It connects directly to GitHub or other git providers for continuous deployment
Why do we need it?
- Deployment makes your app accessible on the web
- Netlify simplifies the process with minimal setup
How do we use it?
- Push your project to a GitHub repository
- Go to Netlify and connect your repository
- Choose the Vite build command:
npm run build and publish directory: dist
- Netlify builds and deploys automatically
Further Learning
What is it?
- State management libraries handle shared or complex application state
- Redux Toolkit provides a standard way to write Redux logic
- Zustand is a lightweight alternative for simpler use cases
Why do we need it?
- Some state must be accessed by many components
- Centralised state management simplifies updates and reduces prop drilling
How do we use it?
import { configureStore, createSlice } from '@reduxjs/toolkit'
import { Provider, useSelector, useDispatch } from 'react-redux'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => { state.value += 1 }
}
})
const store = configureStore({ reducer: { counter: counterSlice.reducer } })
function Counter() {
const count = useSelector(s => s.counter.value)
const dispatch = useDispatch()
return <button onClick={() => dispatch(counterSlice.actions.increment())}>{count}</button>
}
export default function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
)
}
Further Learning
Thunks
What is it?
- A thunk is a function that allows you to handle asynchronous logic in Redux
- It enables you to dispatch actions after async operations like data fetching
Why do we need it?
- Apps often need to load data before updating state
- Thunks help manage async state updates cleanly
How do we use it?
import { createAsyncThunk, createSlice, configureStore } from '@reduxjs/toolkit'
const fetchUsers = createAsyncThunk('users/fetch', async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
return res.json()
})
const usersSlice = createSlice({
name: 'users',
initialState: [],
extraReducers: builder => {
builder.addCase(fetchUsers.fulfilled, (state, action) => action.payload)
}
})
const store = configureStore({ reducer: { users: usersSlice.reducer } })
Further Learning
Auth
What is it?
- Authentication manages user identity and access control
- It can involve login forms, tokens, and secure routes
Why do we need it?
- Many apps restrict access to certain areas or data
- Auth ensures that only authorised users can view or modify protected resources
How do we use it?
- Protect routes using React Router and conditional rendering
- Store auth tokens securely
- Avoid localStorage for sensitive data, HTTP only tokens can help
Further Learning
Environment variables
APIs often need keys. Do not hard code secrets in your codebase. Instead you can keep them in an .env file that you don’t commit to git
Instead you can create an example .env.example file that you do commit to git. This file contains the names of environmental variables you use in your app, but not their values
If you don’t already have one, add a .gitignore file in the root of your repo, and add the following line:
Next, create a .env file at the project root and add any important values, such as:
VITE_API_BASE=https://api.example.com
Note that when using Vite, environmental variable names must start with VITE_API_
You can then read it in your JavaScript code with import.meta.env.VITE_API_BASE when using Vite:
const apiUrl = import.meta.env.VITE_API_BASE;
Further learning
Error boundaries
Catch render errors and show a friendly message rather than a blank screen
- Add one near the top of the component tree and another around risky areas like routes or data-heavy pages
Further learning
Testing the UI
Add one or two tests to prove behaviour
- React Testing Library focuses on what the user sees
- Start with a test that renders a component and asserts that text is on screen
Example:
import { render, screen } from '@testing-library/react'
import Greeting from './Greeting'
test('renders greeting', () => {
render(<Greeting name="Alice" />)
expect(screen.getByText(/Alice/)).toBeInTheDocument()
})