Getting Started

Setup React

// Using Create React App (recommended for beginners)
npx create-react-app my-app
cd my-app
npm start

// Using Vite (faster alternative)
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

// Manual setup with CDN (not recommended for production)
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
Note: Create React App is the official way to create single-page React applications. It offers a modern build setup with no configuration.

Basic Component

// Functional Component (modern approach)
import React from 'react';

function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}

// Arrow function component
const Welcome = ({ name }) => {
return <h1>Hello, {name}!</h1>;
};

// Exporting the component
export default Welcome;

// Using the component
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}

JSX Syntax

JSX Basics

// JSX is JavaScript XML - syntax extension for JavaScript
const element = <h1>Hello, world!</h1>;

// Embedding expressions in JSX
const name = 'Josh Perez';
const element = <h1>Hello, {name}!</h1>;

// JSX is an expression too
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}

// Specifying attributes with JSX
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;

// JSX prevents injection attacks
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
Note: JSX gets compiled to React.createElement() calls which return plain JavaScript objects called "React elements".

JSX Advanced

// Specifying children with JSX
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

// JSX represents objects
// This JSX:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);

// Gets compiled to this:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);

// Which creates an element object like:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};

// Using JavaScript expressions in JSX
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}

const user = {
firstName: 'Harper',
lastName: 'Perez'
};

const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);

Components & Props

Functional Components

// Simple functional component
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}

// Arrow function component
const Welcome = (props) => {
return <h1>Hello, {props.name}!</h1>;
};

// Implicit return (for simple components)
const Welcome = (props) => <h1>Hello, {props.name}!</h1>;

// Destructuring props
const Welcome = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old</p>
</div>
);
};

// Default props
Welcome.defaultProps = {
name: 'Guest',
age: 0
};

// PropTypes (for type checking)
import PropTypes from 'prop-types';

Welcome.propTypes = {
name: PropTypes.string,
age: PropTypes.number
};

Class Components

// Class component (legacy approach, still supported)
import React, { Component } from 'react';

class Welcome extends Component {
render() {
return <h1>Hello, {this.props.name}!</h1>;
}
}

// Class component with state
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}

increment = () => {
this.setState({ count: this.state.count + 1 });
};

render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.increment}>
Click me
</button>
</div>
);
}
}

// Class component lifecycle methods
class Example extends Component {
componentDidMount() {
// Runs after the component output has been rendered to the DOM
console.log('Component mounted');
}

componentDidUpdate(prevProps, prevState) {
// Runs after component updates
console.log('Component updated');
}

componentWillUnmount() {
// Runs before component is removed from the DOM
console.log('Component will unmount');
}

render() {
return <div>Example Component</div>;
}
}
Note: Functional components with hooks are now the preferred way to write React components. Class components are still supported but not recommended for new code.

Hooks

useState & useEffect

// useState Hook - managing state in functional components
import React, { useState } from 'react';

function Counter() {
// Declare a state variable called "count" with initial value 0
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

// useEffect Hook - handling side effects
import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

// useEffect with cleanup (similar to componentWillUnmount)
useEffect(() => {
const subscription = props.source.subscribe();
// Cleanup function
return () => {
subscription.unsubscribe();
};
}, [props.source]); // Only re-run if props.source changes

Additional Hooks

// useContext Hook - accessing context
import React, { useContext } from 'react';

const ThemeContext = React.createContext('light');

function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>I am styled by theme!</button>;
}

// useReducer Hook - alternative to useState for complex state logic
import React, { useReducer } from 'react';

const initialState = {count: 0};

function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}

// useRef Hook - accessing DOM elements or storing mutable values
import React, { useRef } from 'react';

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

Event Handling

Handling Events

// Handling events in React
function ActionLink() {
const handleClick = (e) => {
e.preventDefault();
console.log('The link was clicked.');
};

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}

// Binding methods in class components
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};

// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}

handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}

render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

// Alternative to binding: Class fields syntax (experimental)
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick
handleClick = () => {
console.log('this is:', this);
};

render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}

// Passing arguments to event handlers
const A = 'A';
const B = 'B';

function ActionLink() {
const handleClick = (id, e) => {
e.preventDefault();
console.log('Link ' + id + ' was clicked.');
};

return (
<>
<a href="#" onClick={(e) => handleClick(A, e)}>
Link A
</a>
<a href="#" onClick={(e) => handleClick(B, e)}>
Link B
</a>
</>
);
}

Conditional Rendering

Conditional Rendering Techniques

// Element variables - store JSX elements in variables
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}

function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}

function LoginControl() {
const [isLoggedIn, setIsLoggedIn] = useState(false);

const handleLoginClick = () => setIsLoggedIn(true);
const handleLogoutClick = () => setIsLoggedIn(false);

let button;
if (isLoggedIn) {
button = <LogoutButton onClick={handleLogoutClick} />;
} else {
button = <LoginButton onClick={handleLoginClick} />;
}

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}

// Inline If with Logical && Operator
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}

// Inline If-Else with Conditional Operator
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
return (
<div>
{isLoggedIn
? <UserGreeting />
: <GuestGreeting />
}
</div>
);
}

// Preventing Component from Rendering
function WarningBanner(props) {
if (!props.warn) {
return null; // Return null to prevent rendering
}

return (
<div className="warning">
Warning!
</div>
);
}

Lists & Keys

Rendering Lists

// Rendering multiple components
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);

function NumberList() {
return <ul>{listItems}</ul>;
}

// Keys help React identify which items have changed
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}

const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];

// Using the Blog component
function App() {
return <Blog posts={posts} />;
}

// Keys only need to be unique among siblings
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}

function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}

Forms

Controlled Components

// Controlled Components - form elements controlled by React state
function NameForm() {
const [value, setValue] = useState('');

const handleChange = (event) => {
setValue(event.target.value);
};

const handleSubmit = (event) => {
alert('A name was submitted: ' + value);
event.preventDefault();
};

return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={value} onChange={handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}

// The textarea Tag
function EssayForm() {
const [value, setValue] = useState('Please write an essay about your favorite DOM element.');

const handleChange = (event) => {
setValue(event.target.value);
};

const handleSubmit = (event) => {
alert('An essay was submitted: ' + value);
event.preventDefault();
};

return (
<form onSubmit={handleSubmit}>
<label>
Essay:
<textarea value={value} onChange={handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}

// The select Tag
function FlavorForm() {
const [value, setValue] = useState('coconut');

const handleChange = (event) => {
setValue(event.target.value);
};

const handleSubmit = (event) => {
alert('Your favorite flavor is: ' + value);
event.preventDefault();
};

return (
<form onSubmit={handleSubmit}>
<label>
Pick your favorite flavor:
<select value={value} onChange={handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}

// Handling Multiple Inputs
function Reservation() {
const [state, setState] = useState({
isGoing: true,
numberOfGuests: 2
});

const handleInputChange = (event) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;

setState({
...state,
[name]: value
});
};

return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={state.isGoing}
onChange={handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={state.numberOfGuests}
onChange={handleInputChange} />
</label>
</form>
);
}

Further Learning

Best Practices
  • Keep components small and focused on a single responsibility
  • Use functional components and hooks instead of class components
  • Use descriptive component and variable names
  • Lift state up to the closest common ancestor when needed
  • Use keys properly when rendering lists
  • Use controlled components for form inputs