React.js is a client-side JavaScript library for building dynamic, interactive user interfaces using reusable pieces of UI called components.
function Component() {
return <h1> Component </h1>;
}
export default Component;
Components are reusable pieces of UI that have their own logic and appearance and are the building blocks of every React.js app. Components are JavaScript functions that return JavaScript XML (JSX) markup.
Components can be nested together, meaning that one component can contain multiple other components within it. Components can be as small as a button, or as large as an entire page. Components help us write reusable, modular, and better organized code. Data is passed from a parent component to a child component through props.
Props are like HTML attributes that can pass any JavaScript value. In the parent component, you assign the data to a prop when rendering the child, and in the child component, you access the data through the props object.
The Component Tree consists of the root component and all of its children components. When the application starts, React.js takes the component tree and builds a JavaScript data structure called the Virtual DOM.
The Virtual DOM is an in-memory representation of the UI as a tree of React elements, distinct from the browser's actual DOM.
When a component's state or props change, React.js creates a new virtual DOM tree reflecting the updated UI. React.js then performs reconciliation, during which it compares the new tree against the previous one to determine the minimal set of changes needed (called "diffing"). Finally, the react-dom renderer applies those changes to the real DOM in an efficient, batched manner.
node -v
npm create vite@latest
# or
npm create vite@4.1.0
cd react-app
npm install
npm run dev
# Remove old files
rm -rf src/assets
rm src/App.css
rm src/index.css
rm src/App.tsx
# Create new files
mkdir src/assets
mkdir src/components
touch src/App.tsx
touch src/App.css
touch src/index.css # Delete this
echo "import './App.css';
function App() {
return <h1> Basic React App </h1>;
}
export default App;" > src/App.tsx
touch src/components/Component.jsx
# or
touch src/components/Component.tsx
It's common to create a function-based component since they're more concise and easier to write. Remember to use PascalCasing when naming components.
See how JSX or TSX gets transpiled to vanilla JavaScript.function Component() {
return <h1> Component </h1>;
}
export default Component;
import Component from './components/Component.tsx';
Components need to be closed or they will result in a compilation
error. This can be done two ways:
<Component></ Component>, or
<Component />.
import Component from "./components/Component.tsx";
import './App.css';
function App() {
return (
<div>
<Component />
</div>
);
}
export default App;
In React, you specify a CSS class with className, which
works the same as the HTML class attribute.
function Component() {
function getName() {
const name = 'Grant';
if (name) {
return `Hello, ${name}!`;
} else {
return 'Hello, World!';
}
}
return <h1> {getName()} </h1>;
}
export default Component;
Note: class is reserved for vanilla JavaScript so we use className instead.
<li className="list-group-item"> An item </li>
function Component() {
return <h1 style={{ backgroundColor: 'blue' }}> Component </h1>;
}
npm install bootstrap
# or
npm install bootstrap@5.2.3
import 'bootstrap/dist/css/bootstrap.css';
export default function Card() {
return (
<div className="card" style={{ width: "18rem" }}>
<img src="..." className="card-img-top" alt="..." />
<div className="card-body">
<h5 className="card-title">Card title</h5>
<p className="card-text"> Some quick example text to build on the card title and make up the bulk of the card’s content. </p>
<a href="#" className="btn btn-primary"> Go somewhere </a>
</div>
</div>
);
}
In React.js, a component can't return more than one element. To
solve this, we need to wrap the collection of elements in a
containing element like a <div>. However, this
adds more elements to the DOM, which is something we don't always
want, so we can instead use a Fragment.
Fragments allow multiple elements to be wrapped together without adding another element to the DOM. You can add Fragments either by importing and adding them explicitly or by using the React.js shortcut.
return (
<React.Fragment>
<h1> Header </h1>
</React.Fragment>
)
import { Fragment } from 'react';
...
return (
<Fragment>
<h1> Header </h1>
</Fragment>
)
return (
<>
<h1> Header </h1>
</>
)
Rendering multiple components based on data, usually by looping over an array.
When creating lists, each child element needs to have a unique key
prop. This key prop is how the virtual DOM knows which element gets
changed.
const items = [
'London',
'New York',
'Barcelona',
'Los Angeles',
'Paris'
]
<ul className="list-group">
{items.map(item => <li key={item} className="list-group-item">{item}</li>)}
</ul>
const stickyNoteColors = [
{ color: 'Yellow', hex: '#ffffb6'},
{ color: 'Green', hex: '#c3ffb6'},
{ color: 'Blue', hex: '#b6fff8'},
{ color: 'Purple', hex: '#eeb6ff'},
{ color: 'Red', hex: '#ffb6b6'}
]
{stickyNoteColors.map(item => (
<option key={item.color} value={item.hex}>{item.color}</option>
))}
Note: When iterating over a select dropdown, you can set the default value with <select value={updatedNote.color}>.
Rendering HTML elements or components only if a certain condition is true.
{items.length === 0 && <p> No items found </p>}
{isModalOpen && <Modal setIsModalOpen={setIsModalOpen} />}
Rendering classes only if a certain condition is true.
<EllipsisHorizontalIcon className={`Icon ${showIcons ? 'Open' : 'Closed'}`} />
Rendering content only if a certain condition is true.
This is conditional rendering of text on a button that changes and opens and closes the modal.
<button onClick={toggleModal}>
{isModalOpen ? 'Close Modal' : 'Open Modal'}
</button>
There are many different types of events we want our React.js app to react to, like clicks, mouse movements, or keyboard events.
<button onClick={handleClick} />
<input onChange={handleChange} />
<form onSubmit={handleSubmit} />
const handleClick = () => {
alert('Alert');
}
<li onClick={() => console.log("Clicked!")}> Click Me </li>
<select onChange={(event) => handleChange(event.target.value)}>
import { MouseEvent } from "react";
function ListGroup() {
const items = ["New York", "San Francisco", "Tokyo", "London", "Paris"];
const handleClick = (event: MouseEvent) => console.log(event);
return (
<>
{items.length === 0 && <p> No items found </p>}
<ul className="list-group">
{items.map((item, index) => (
<li key={item} className="list-group-item" onClick={handleClick}> {item} </li>
))}
</ul>
</>
);
}
export default ListGroup;
Controlled and uncontrolled components describe how form data is managed.
A controlled component is an input whose value is controlled by React state. Provides faster real-time validation and better for complex forms.
<input value={value} onChange={(e) => setValue(e.target.value)} />
An uncontrolled component lets the DOM manage the input value. You access the value using ref. Real-time validation is harder and better for simpler forms.
import { useRef } from "react";
function MyForm() {
const inputRef = useRef();
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return (
<>
<input ref={inputRef} />
<button onClick={handleSubmit}>Submit>/button>
</>
);
}
Props, short for "properties", are the inputs passed to a component. They are similar to arguments for a function and are immutable (unchangeable). Props allow us to pass data downward from a parent component to a child component. They are JavaScript objects that follow a key-value format. When props change, the component re-renders and the DOM updates accordingly.
The core rule of Props is that a component should receive the DATA it needs, and the ACTIONS or BEHAVIOR it can perform.
What shape of data does a component actually need? For instance, do you need to pass:
Prop Drilling is an anti-pattern in component-based frameworks where data is passed down multiple layers of intermediate components, even if those middle components don't use the data, just to reach a deeply nested component. This makes code harder to maintain and debug as the application grows.
// Pass the data through props and make the value the name of the prop
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<Child count={count} setCount={setCount} />
</div>
);
}
// Without Destructuring
function Child(props) {
return (
<button onClick={() => props.setCount(count + 1)}>
Count: {props.count}
</button>
);
}
// With Destructuring
function Child({ count, setCount }) {
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Decide the shape of the input you want to pass into the child component.
{
heading: string,
items: []
}
interface Props {
heading: string;
items: string[];
}
<Card heading={'Card'} items={['One', 'Two', 'Three']} />
interface Props {
heading: string;
items: string[];
}
function Card(props: Props) {
return (
<div>
<h1> {props.heading} </h1>
<p> {props.items[0]} </p>
<p> {props.items[1]} </p>
<p> {props.items[2]} </p>
</div>
)
}
interface Props {
heading: string;
items: string[];
}
function Card({ heading, items }: Props) {
return (
<div>
<h1> {heading} </h1>
<p> {items[0]} </p>
<p> {items[1]} </p>
<p> {items[2]} </p>
</div>
)
}
Passing a setter instead of the actual setOpen functionality means less responsibility you’re giving the child component.
const [open, setOpen] = useState(true);
const toggleSideNavigation = () => {
setOpen((current) => !current)
}
interface SideNavigationProps {
open: boolean;
toggleSideNavigation: () => void;
}
function SideNavigation({ open, toggleSideNavigation }: SideNavigationProps) {..}
<button onClick={toggleSideNavigation}>
Create the function that will handle the logic.
const handleDeleteButtonClick = () => {
console.log('The delete button was clicked!');
}
Pass the function to the child component using props.
onDelete={handleDeleteButtonClick}
Add the function to props.
interface Props {
onDelete: () => void;
}
Add the function to the component (destructured)
function Component({ onDelete }: Props)
Add the function to the component.
<button onClick={() => onDelete}> Delete Sticky Note </button>
<Component>
// This is where we pass children
<p> I am a child </p>
</Component>
{props.children}
Key props are used so React.js can tell one component apart from another, usually when creating a list with the map function. A Key is a unique string or number to identify a component. If there is no unique key value, you can always use the index value as the key with index.
{<items.map((items, index) => (
<div key={index}>{item}</div>
))}
interface Props {
setIsModalOpen: (value: boolean) => void;
}
React.js Hooks are functions that let you "hook into" React.js features like state and lifecycle methods from within functional components.
These are common categories used to group React.js hooks.
useState and useReduceruseEffect - Connect with external systems useRef - Reference HTML elements useContext - Use data passed through contextuseMemo and useCallback - Improve performance by preventing unnecessary work State can be thought of as the current status of a component. State is internal data managed by a component that can change over time. State is mutable (changeable), and updating state causes the component to re-render and update the DOM accordingly. Each component created can have its own independent state.
React.js updates state in batches for performance optimization.
Ideal for simple, independent values.
The useState hook allows us to tell React.js that the component can have data or state that will change over time.
import { useState } from 'react';
There are two variables in the useState array. One for initializing
the variable (one that you can call to get the value), and another
for changing the variable (which is a function). By default, you need to pass in a default
value when adding useState().
const [selectedIndex, setSelectedIndex] = useState(-1);
const [name, setName] = useState('');
setSelectedIndex(3);
setName('Grant');
setState(prev => prev + 1);
setArrValues(prev => [...prev, newVal]); // Pass in the previous values and add a new one
setObjValues(prev => ({...values, last: "Davis"})); // Pass in the previous values and overwrite a single one
Better for complex state logic or when updates depend on previous state.
Effects let you reach outside of the React.js application. Effects can be done in event handlers and using the useEffect hook. One example would be fetching data right when a component loads.
Handles side effects in components, such as fetching data, subscriptions, timers, or manually interacting with the DOM. Runs after rending and can re-run when dependencies change.
The useEffect hook allows us to tell React.js to run certain code in response to changes in the component, such as fetching data from an API, updating the document title, or syncing with external systems. have data or state that will change over time.
import { useEffect } from 'react';
useEffect(() => {
// This code runs after the component renders for the first time.
// Perform initial setup, data fetching, or DOM manipulations here.
console.log('Hi, from the useEffect hook!')
}, []); // Empty dependency array ensures it runs only once
function handleSubmit(e) {
e.preventDefault()
post('/api/register', {email, password})
}
useEffect(() => {
fetchData().then(data => {
// use data here
})
}, [])
Provides a mutable reference that persists across renders without causing re-renders. Often used to access DOM elements directly or store values that shouldn't trigger UI updates.
The useRef hook allows us to keep a mutable value that persists across renders, often used to access and interact with DOM elements directly or store values that shouldn't trigger a re-render en they chance.
import { useRef } from 'react';
const inputRef = useRef(null);
const ref = useRef();
<input ref={ref} />
ref.current.focus();
The useContext hook allows us to access shared data from a Context provider without having to pass props through multiple levels of components, making state sharing simpler and cleaner across the application.
{`import { useContext } from 'react';`}
{`useContext`}
Context is a powerful way to pass data through components. To get data down multiple levels involves passing the same props through components that don't actually need that data. Context lets us jump through the component tree and use data at any level without making props.
Create context in a a parent component
const AppContext = createContext();
Wrap the parent using the Provider component
<AppContext.Provider>
<App />
</AppContext.Provider>
Put data on value prop
<AppContext.Provider value="title">
Access that data
function Title(){
const text = useContext(AppContext)
return <h1>{text}</h1>
}
useMemo memoizes computed values.
useCallback memoizes functions.
Need to add/experiment with useReducer, useMemo, and useCallback.
npm install react-router
import { BrowserRouter, Routes, Route } from "react-router";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
</Routes>
</BrowserRouter>
</StrictMode>
);
touch src/routes.ts
import HomePage from "./pages/HomePage";
export const routes = [
{ path: "/", title: "Home Page", component: HomePage }
]
import { routes } from './src/routes.ts';
<BrowserRouter>
<Routes>
{routes.map((route) => {
return (
<Route
key={route.path}
path={route.path}
element={<route.component />}
/>
);
})}
</Routes>
</BrowserRouter>