React.js

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

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

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.

React.js Basics

How React.js Works

The Component Tree

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

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.

Creating a New React.js Application

Check the current Node.js version

node -v

Create the application

npm create vite@latest

# or

npm create vite@4.1.0

Install dependencies and run the server

cd react-app

npm install
npm run dev

Recommended Next Steps

# 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

Creating a React.js Component

Create the component file

touch src/components/Component.jsx

# or

touch src/components/Component.tsx

Create the component

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 the component to the parent file

import Component from './components/Component.tsx';

Add the component to the page

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;

Modify the component

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;

React.js Components

Adding Classes to Components

Note: class is reserved for vanilla JavaScript so we use className instead.

<li className="list-group-item"> An item </li>

Adding CSS to Components

function Component() {
  return <h1 style={{ backgroundColor: 'blue' }}> Component </h1>;
}

Adding Bootstrap for Simple Styling

Install Bootstrap

npm install bootstrap

# or

npm install bootstrap@5.2.3

Import the Bootstrap file

import 'bootstrap/dist/css/bootstrap.css';

Find Bootstrap Components to Add

Bootstrap Docs
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>
  );
}

Using Fragments

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.

Using Fragments

return (
    <React.Fragment>
      <h1> Header </h1>
    </React.Fragment>
  )

Importing Fragments

import { Fragment } from 'react';

  ...

  return (
    <Fragment>
      <h1> Header </h1>
    </Fragment>
  )

Fragment Short Syntax

return (
  <>
    <h1> Header </h1>
  </>
)

Dynamically Rendering Components

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.

Iterating Over an Array

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>

Iterating Over an Array of Objects

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}>.

Conditionally Rendering Components

Element & Component Rendering

Rendering HTML elements or components only if a certain condition is true.

{items.length === 0 && <p> No items found </p>}
{isModalOpen && <Modal setIsModalOpen={setIsModalOpen} />}

Class Rendering

Rendering classes only if a certain condition is true.

<EllipsisHorizontalIcon className={`Icon ${showIcons ? 'Open' : 'Closed'}`} />

Content Rendering

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>

Handling Events in a Component

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');
}

Adding Click Events

<li onClick={() => console.log("Clicked!")}> Click Me </li>
<select onChange={(event) => handleChange(event.target.value)}>

Adding Event Handlers

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 & Uncontrolled Components

Controlled and uncontrolled components describe how form data is managed.

Controlled Components

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)} />

Uncontrolled Components

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>
    </>
  );
}

React.js Props

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:

  • A whole object
  • Individual fields
  • Callbacks / functions
  • State setters
  • Configuration variables (isOpen, size)

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.

Passing Data in JSX w/ Props

Inside the Parent Component

// 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>
  );
}

Inside the Child Component

// 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>
  );
}

Passing Data in TSX w/ Props & Interfaces

Decide the shape of the input you want to pass into the child component.

{
  heading: string,
  items: []
}
interface Props {
  heading: string;
  items: string[];
}

Inside the Parent Component

<Card heading={'Card'} items={['One', 'Two', 'Three']} />

Inside the Child Component

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>
  )
}

Destructuring Props

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

Passing a setter instead of the actual setOpen functionality means less responsibility you’re giving the child component.

Parent Component

const [open, setOpen] = useState(true);
const toggleSideNavigation = () => {
  setOpen((current) => !current)
}

Child Component

interface SideNavigationProps {
  open: boolean;
  toggleSideNavigation: () => void;
}
function SideNavigation({ open, toggleSideNavigation }: SideNavigationProps) {..}
<button onClick={toggleSideNavigation}>

Passing Functions (Lifting State Up) w/ Props

Inside the Parent Component

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}

Inside the Child Component

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>

Passing Children

Inside the Parent Component

<Component>
  // This is where we pass children
  <p> I am a child </p>
</Component>

Inside the Child Component

{props.children}

Key Props

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

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.

  • State Hooks - useState and useReducer
  • Effect Hooks - useEffect - Connect with external systems
  • Ref Hooks - useRef - Reference HTML elements
  • Context Hooks - useContext - Use data passed through context
  • Performance Hooks - useMemo and useCallback - Improve performance by preventing unnecessary work

The useState Hook

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.

Importing the useState Hook

import { useState } from 'react';

Adding the useState Hook

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('');

Updating State

setSelectedIndex(3);
setName('Grant');

Updating State from Previous State

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

useReducer

Better for complex state logic or when updates depend on previous state.

The useEffect Hook

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 the useEffect Hook

import { useEffect } from 'react';

Add the useEffect Hook

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
  })
}, [])

The useRef Hook

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 the useRef Hook

import { useRef } from 'react';

Add the useRef Hook

const inputRef = useRef(null);
const ref = useRef();
<input ref={ref} />
ref.current.focus();

The useContext Hook

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 the useContext Hook

{`import { useContext } from 'react';`}

Add the useContext Hook

{`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

useMemo memoizes computed values.

useCallback

useCallback memoizes functions.

Need to add/experiment with useReducer, useMemo, and useCallback.

React.js Router

Setting Up React.js Router

Install React Router

npm install react-router

Import the BrowserRouter and Routes

import { BrowserRouter, Routes, Route } from "react-router";

Wrap Components and Create Routes

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<App />} />
      </Routes>
    </BrowserRouter>
  </StrictMode>
);

Adding a Custom Routes File

Create the routes file

touch src/routes.ts

Add the routes

import HomePage from "./pages/HomePage";

export const routes = [
  { path: "/", title: "Home Page", component: HomePage }
]

Import the routes

import { routes } from './src/routes.ts';

<BrowserRouter>
  <Routes>
    {routes.map((route) => {
      return (
        <Route
          key={route.path}
          path={route.path}
          element={<route.component />}
        />
      );
    })}
  </Routes>
</BrowserRouter>