Introduction to ReactJS

Introduction to ReactJS

What is ReactJS?

  • ReactJS is a popular JavaScript library for building user interfaces.

  • Developed and maintained by Facebook.

  • Used to create interactive, reusable UI components.

  • Key Features

    • Component-Based Architecture
    • Virtual DOM
    • Unidirectional Data Flow
    • Reusable UI Components

The React Ecosystem

  • React is often used with other tools and libraries.
  • Key tools in the React ecosystem include:
    • React Router (for routing)
    • Redux (for state management)
    • Babel (for transpiling)
    • Webpack (for bundling)

React Ecosystem Example

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter as Router } from 'react-router-dom';

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById('root')
);

Setting up the Development Environment

  • Setting up a development environment is crucial.
  • You’ll need Node.js and npm (Node Package Manager).
  • Create a new React app using Create React App (CRA).
npx create-react-app my-react-app
cd my-react-app
npm start

Creating Your First React Application

  • A React app consists of components.
  • Components are building blocks of your UI.
  • Components are composed to create the app.
import React from 'react';

function App() {
  return (
    <div>
      <h1>Hello, React!</h1>
    </div>
  );
}

Understanding Components

  • Components are the core of React.
  • They are reusable, self-contained pieces of UI.
  • Components can be functional or class-based.
import React from 'react';

function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

JSX (JavaScript XML) Syntax

  • JSX is a syntax extension for JavaScript.
  • It looks similar to HTML, but it’s not.
  • JSX gets transpiled to JavaScript.
  • JSX allows embedding expressions.
import React from 'react';

function App() {
  const name = 'React';
  return (
    <div>
      <h1>Hello, {name}!</h1>
    </div>
  );
}

React Component Hierarchy

  • In React, components can be nested.
  • This forms a hierarchy of components.
  • Parent components can pass data to child components.
import React from 'react';

function App() {
  return (
    <div>
      <Header />
      <MainContent />
      <Footer />
    </div>
  );
}

Virtual DOM and Reconciliation

  • React uses a virtual DOM to optimize updates.
  • The virtual DOM is a lightweight copy of the actual DOM.
  • React performs “reconciliation” to update the real DOM efficiently.
const virtualDOM = (
  <div>
    <h1>Hello, React!</h1>
  </div>
);

Component State and Props

  • Components can have state.

  • State is mutable data that influences rendering.

  • Components can receive props (properties).

  • Props are read-only and help pass data from parent to child.

class Greeting extends React.Component {
  constructor(props) {
    super(props);
    this.state = { message: 'Hello' };
  }

  render() {
    return <h1>{this.state.message}, {this.props.name}!</h1>;
  }
}

Let’s Create React App with CRA

  • Create React App is an officially way (one of the ways) to create React applications.
    npx create-react-app my-app
    # where 'my-app' is name of our application
    
    # It will take a minute or more to create the app
    
    cd my-app

    #start the application
    npm start 
  • open http://localhost:3000/ to see the app.

Folder Structure

my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js
    └── setupTests.js
  • src/index.js is the JavaScript entry point.
  • public/index.html is the page template.

Commands

  • package.json contains following command to manage the application

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  }
  • npm start – Starts the development server.
  • npm run build – Bundles the app into static files for production.
  • npm test – Starts the test runner.
  • npm run eject – Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!

Let’s understand how it works

  • public/index.html defines root element for our application
<body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

    ...
  • In src/index.js, ReactDOM get holds of root element and renders React Component
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

React Component

src/App.js defines the React Component with name App


function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

Try It!!!

Remove everything in between ( ) of return and Just write <h2>Hello World!!!</h2>

React Component Fundamentals

Lifecycle Methods

  • Components have a lifecycle.
  • Lifecycle methods are functions called at different stages.
  • Common lifecycle methods include constructor, componentDidMount, and componentWillUnmount.
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    // Initialize state and other setup
  }

  componentDidMount() {
    // Perform tasks after component is mounted
  }

  componentWillUnmount() {
    // Clean up resources before unmounting
  }
}

Handling Events

  • React components can handle user events.
  • Event handlers are defined as functions.
  • Events like onClick and onChange trigger handlers.
import React, { Component } from 'react';

class ClickCounter extends Component {
  state = { count: 0 };

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

  render() {
    return (
      <button onClick={this.handleClick}>
        Clicked {this.state.count} times
      </button>
    );
  }
}

Conditional Rendering

  • Conditionally render components or content.
  • Use if statements or ternary operators.
  • Decide what to display based on component state or props.
import React from 'react';

function Greeting(props) {
  return (
    <div>
      {props.isLoggedIn ? <h1>Welcome, User!</h1> : <h1>Please sign in.</h1>}
    </div>
  );
}

Lists and Keys

  • Render lists of items efficiently.
  • Use the map method to iterate and create components.
  • Provide a unique key to each list item for efficient updates.
import React from 'react';

function ItemList(props) {
  const items = props.items;
  const listItems = items.map((item) => (
    <li key={item.id}>{item.name}</li>
  ));

  return <ul>{listItems}</ul>;
}

Forms and Controlled Components

  • Create forms in React for user input.
  • Use controlled components to manage form state.
  • Handle form submission and validation.
import React, { Component } from 'react';

class NameForm extends Component {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }

  handleChange = (event) => {
    this.setState({ value: event.target.value });
  }

  handleSubmit = (event) => {
    alert('Name submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="text" value={this.state.value} onChange={this.handleChange} />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

What is JSX?

  • JSX stands for JavaScript XML.
  • It’s a syntax extension for JavaScript.
  • JSX allows us to write HTML-like code in JavaScript.

Why Use JSX?

  • JSX is used in React for defining the structure of components.
  • Benefits of JSX:
    • Declarative: Easier to visualize the UI.
    • Integrates HTML-like tags with JavaScript.
    • Provides a concise and readable way to create components.

JSX Syntax

  • JSX elements resemble HTML elements.
  • JSX expressions are enclosed in curly braces {}.
  • Elements must have a single root element (e.g., <div>).
  • Class and style attributes are written as className and style.
const element = <h1>Hello, JSX!</h1>;

JSX and JavaScript

  • JSX is transpiled to JavaScript.
  • JSX elements become React.createElement() calls.
  • JavaScript expressions are evaluated inside {}.
const element = <h1>Hello, JSX!</h1>;
const element = React.createElement("h1", null, "Hello, JSX!");

JSX Elements

  • JSX elements can be assigned to variables.
  • Elements can include children, attributes, and expressions.
const greeting = <h1>Hello, JSX!</h1>;
const name = "React";
const element = <p>{`Welcome, ${name}`}</p>;

Conditional Rendering with JSX

  • JSX can be used for conditional rendering.
  • Ternary operators or if statements can be used within {}.
const isLoggedIn = true;
const greeting = isLoggedIn ? <p>Welcome, User!</p> : <p>Please sign in.</p>;

Mapping and Lists with JSX

  • JSX is commonly used to map over arrays and create lists.
  • Each item must have a unique key for efficient rendering.
const items = ["Item 1", "Item 2", "Item 3"];
const listItems = items.map((item, index) => (
  <li key={index}>{item}</li>
));

JSX and Events

  • JSX supports event handlers.
  • Event handlers are defined as functions.
  • Common events include onClick, onChange, and more.
function handleClick() {
  alert("Button clicked!");
}

const button = <button onClick={handleClick}>Click Me</button>;

JSX and Components

  • JSX can include other React components.
  • Components can be composed within JSX.

Example

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

const element = <Welcome name="React" />;

Understanding Nested Components

  • Nested components are components within other components.

  • They enable complex UI structures and modular development.

  • Advantages of Nested Components

    • Reusability
    • Separation of Concerns
    • Improved Readability
    • Maintenance Ease

Real-World Example: iTunes Music Search App

  • Let’s explore a music search app.

  • This app allows users to search for music on iTunes.

  • Components in the App

  • App (Parent Component)

  • SearchBar (Child Component)

  • SearchResults (Child Component)

App Component

  • The top-level component.
  • Contains the state and logic for the app.
  • Renders the child components.
import React, { Component } from 'react';

class App extends Component {
  constructor() {
    super();
    this.state = { query: '', results: [] };
  }

  render() {
    return (
      <div>
        <SearchBar />
        <SearchResults results={this.state.results} />
      </div>
    );
  }
}

SearchBar Component

  • Responsible for user input and search actions.
  • Communicates with the parent component (App) using props and callbacks.
import React, { Component } from 'react';

class SearchBar extends Component {
  constructor() {
    super();
    this.state = { query: '' };
  }

  handleInputChange = (event) => {
    this.setState({ query: event.target.value });
  }

  handleSearch = () => {
    // Perform search and update parent's state
    this.props.onSearch(this.state.query);
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.query}
          onChange={this.handleInputChange}
        />
        <button onClick={this.handleSearch}>Search</button>
      </div>
    );
  }
}

SearchResults Component

  • Displays the search results.
  • Receives data from the parent (App) via props.
import React from 'react';

function SearchResults({ results }) {
  return (
    <ul>
      {results.map((result, index) => (
        <li key={index}>{result.trackName}</li>
      ))}
    </ul>
  );
}

Nested Component Interaction

  • Interaction between parent and child components.
  • SearchBar communicates search queries to App.
  • App fetches data and passes it to SearchResults.
  1. User inputs a query.
  2. SearchBar sends the query to App.
  3. App fetches music data.
  4. App updates the state with the results.
  5. SearchResults displays the search results.

Benefits of Nested Components

  • Modularity: Each component has a specific responsibility.
  • Reusability: Components can be reused in other parts of the app.
  • Separation of Concerns: Easier to understand and maintain.
  • Scalability: Easy to extend and add new features.

Real-World Applications

  • Nested components are used in various applications.
  • E-commerce websites, social media platforms, and more.
  • React’s component-based architecture simplifies complex UIs.

React Components

  • React apps are made out of components.
  • A component is a part of the UI with its own logic and appearance
  • React components are JavaScript functions that return markup in the form of JSX
// Function Component 
function Greeting() {
    return (
        <h1>Hello World!!!</h1>
    )
}
// Class component 
import React from "react";
class Greeting extends React.Component {
    render() {
        return <h1>Hello, World</h1>;
    }
}

Component Structure

  • Typically React Components defined in its own JS file
  • Component names must always start with capital letter
  • Components must return markup using return

function <Component Name>() {
    return <JSX>
}

export default <Component Name>;

What is JSX

  • JavaScript Extension to write HTML in React
  • It is similar to HTML but not lenient
  • It helps to write logic, markup in same place which is not the case in HTML
Rules
  • Must return only a single root element
  • Fragment need to be used in case of returning multiple elements
  • Follows strict syntax eg. all tags must be closed
  • Element attributes names follow cameCases

Examples


    function Track() {
        return (  // OK Return single DIV as root element 
            <div>
              <h4>Track Name: Jack Johnson</h4>
              <p>artistName: This Bike Is a Pipe Bomb</p>
            </div>
        )
    }

    function Track() {
        return (  // NOT OK  Multiple root elements
            <div>
              <h4>Track Name: Jack Johnson</h4>
              <p>artistName: This Bike Is a Pipe Bomb</p>
            </div>
            <div> 
              <img src="https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/f3/ee/b3/f3eeb3ff-ca32-273a-15aa-709bdfa64367/mzi.izwiyqez.jpg/30x30bb.jpg" />
            </div>
        )
    }

Accessing Dynamic Data in JSX

  • Dynamic data can be accessed using {}
  • {{}} used for passing JavaScript Objects including CSS

const track = {
    title: "Let It Be",
    artist: "The Beatles",
    album: "This Bike Is a Pipe Bomb",
    coverUrl: "https://is1-ssl.mzstatic.com/image/thumb/Music124/v4/f3/ee/b3/f3eeb3ff-ca32-273a-15aa-709bdfa64367/mzi.izwiyqez.jpg/100x100bb.jpg"
}
function Track() {
    return (
        <div>
            <h4>Track Name: {track.title}</h4>
            <div>
                <div style={{ "float": "left", "paddingRight": "12px" }}>
                    <img src={track.coverUrl} alt={track.title} width={100} height={100} />
                </div>
                <div>
                    Artist: {track.artist} <br />
                    Album Name: {track.album}<br />
                    Last Fetched On: {Date()}<br />
                </div>
            </div>
        </div>
    )
}

What are Props?

  • Props (short for properties) are used for passing data from a parent component to a child component.
  • They are read-only and help make your components reusable.

Example Component

function Greeting(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// Usage
<Greeting name="Janardhan" />
  • In the example, the name prop is passed from the parent component.

Default Props

  • You can provide default values for props.
  • These defaults are used if the parent component doesn’t provide a value.
Greeting.defaultProps = {
  name: "Guest",
};

What is State?

  • State is a mechanism for components to have their internal data that can change over time.
  • State allows components to be dynamic and interactive.

Example Class Component

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    return (
      <div>
        Count: {this.state.count}
      </div>
    );
  }
}
  • The count property in the component’s state is displayed in the render method.

Modifying State

Example Class Component with State Modification

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  incrementCount() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        Count: {this.state.count}
        <button onClick={() => this.incrementCount()}>Increment</button>
      </div>
    );
  }
}
  • setState is used to update the state, which triggers a re-render of the component.

Props vs. State

  • Props are external and should not be changed by the component.
  • State is internal and can be changed by the component.
  • Props are for parent-to-child communication, while State is for a component’s internal data.

Lets Greet by name


function Greeting(props) {
    return (
        <h1>Hello {props.name}</h1>
    )
}
export default Greeting;

// We can pass name from parent component
<Greeting name="Janardhan" />
  • we can pass functions also as props
const logGreeting = () => {
    console.log('Greeted')
}

<Greeting name="Janardhan" onGreet={logGreeting} />

Styling in React

Understanding Styling in React

  • Styling in React refers to how you apply CSS styles to your components.

  • Several approaches for styling in React, including traditional CSS, CSS modules, and CSS-in-JS.

  • Why Styling Matters

    • A well-styled UI enhances user experience.
    • Maintainable styling is crucial for codebases.
    • Consistent design across components.

Traditional CSS

  • Using traditional CSS files to style React components.

  • Styles can be applied using class names.

  • Pros

    • Familiar to web developers.
    • Separation of concerns (HTML and CSS).
    • Browser caching for styles.
  • Cons

    • Global scope for styles can lead to naming conflicts.
    • Limited reusability and encapsulation.

CSS Modules

  • CSS Modules are a solution for local CSS scoping in React.

  • Each component gets its own isolated scope for styles.

  • How It Works

    • Create a .module.css file.
    • Import styles in your component.
    • Use the imported styles as objects.
  • Pros

    • Local scoping eliminates naming conflicts.
    • Encapsulated styles for each component.
    • Improved reusability and maintainability.

Styled-Components

  • A popular CSS-in-JS library for styling in React.

  • Styles are defined as JavaScript template literals.

  • Provides dynamic styling and theming capabilities.

  • How It Works

    • Define styles using template literals.
    • Use the styles as components in your JSX.
  • Pros

    • Dynamic styling based on props and state.
    • Encapsulation and component-level styling.
    • Theming support for consistent design.

Styling in React: Best Practices

  • Maintain consistency in your project’s styling approach.

  • Use CSS Modules or Styled-Components for local scoping.

  • Keep styles close to the components they affect.

  • Consider using a CSS preprocessor like SASS or LESS for advanced styling.

  • CSS Preprocessors

    • SASS and LESS offer features like variables and mixins.
    • Compile preprocessor code to CSS in your project.

Styling Example: CSS Modules

// Button.module.css
.button {
  background-color: #0077b6;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

// Button.js
import React from 'react';
import styles from './Button.module.css';

function Button() {
  return <button className={styles.button}>Click me</button>;
}

Styling Example: Styled-Components

// Button.js
import React from 'react';
import styled from 'styled-components';

const StyledButton = styled.button`
  background-color: #0077b6;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
`;

function Button() {
  return <StyledButton>Click me</StyledButton>;
}

State Management with Redux

What is Redux?

  • Redux is a predictable state container for JavaScript applications.
  • It’s commonly used with React but can be integrated into any JavaScript app.
  • Redux centralizes and manages the state of your application.

Key Concepts

  • 1. Store
    • The single source of truth for your application’s state.
    • Maintains the state tree.
    • Accessible to all components.
  • 2. Actions
    • Plain JavaScript objects that describe changes to the state.
    • Dispatched by components to update the state.
  • 3. Reducers
    • Pure functions that specify how the state changes.
    • Take the current state and an action, and return the new state.

Redux Workflow

  1. An action is dispatched by a component.
  2. The action is processed by a reducer.
  3. The reducer updates the state.
  4. The updated state is stored in the Redux store.
  5. Connected components receive the updated state.

Setting Up Redux

  • Install the redux and react-redux packages in your project.
  • Create a Redux store using createStore.
  • Use the Provider component to wrap your app in Redux.
// store.js
import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

Actions

  • Actions are plain JavaScript objects.
  • They have a type property that describes the action.
  • Additional data can be included in the payload property.
{
  type: 'ADD_TODO',
  payload: 'Buy groceries'
}

Reducers

  • Reducers are functions that specify how the state changes.
  • They take the current state and an action as parameters.
  • The reducer calculates the new state based on the action.
const initialState = {
  todos: [],
};

const rootReducer = (state = initialState, action) => {
  if (action.type === 'ADD_TODO') {
    return {
      ...state,
      todos: [...state.todos, action.payload],
    };
  }
  return state;
};

Dispatching Actions

  • Components can dispatch actions using dispatch from react-redux.
import { connect } from 'react-redux';

const TodoList = ({ todos, dispatch }) => {
  const addTodo = (text) => {
    dispatch({ type: 'ADD_TODO', payload: text });
  };

  // Rest of the component code
}

Connect Components to Redux

  • Use the connect function to connect a component to the Redux store.
  • It provides the component with state and dispatch.
import { connect } from 'react-redux';

const mapStateToProps = (state) => ({
  todos: state.todos,
});

export default connect(mapStateToProps)(TodoList);

Middleware in Redux

  • Middleware allows you to add custom functionality to the Redux dispatch process.
  • Common middleware includes redux-thunk for async actions and redux-logger for debugging.
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducers';

const store = createStore(rootReducer, applyMiddleware(thunk, logger));

Combining Reducers

  • As your app grows, you can split your reducers into smaller functions.
  • Use combineReducers to combine them into one root reducer.
import { combineReducers } from 'redux';
import todosReducer from './todosReducer';
import userReducer from './userReducer';

const rootReducer = combineReducers({
  todos: todosReducer,
  user: userReducer,
});

Actions with Async Operations

  • Use redux-thunk or other middleware for actions with async operations.
const fetchTodos = () => {
  return (dispatch) => {
    dispatch({ type: 'FETCH_TODOS_REQUEST' });

    // Perform async operation
    fetch('/api/todos')
      .then((response) => response.json())
      .then((data) => {
        dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: data });
      })
      .catch((error) => {
        dispatch({ type: 'FETCH_TODOS_FAILURE', payload: error });
      });
  };
};

Redux DevTools

  • Redux DevTools is a browser extension for debugging Redux applications.
  • It provides a visual interface to inspect and time-travel through your state changes.

What is Redux Toolkit?

  • Redux Toolkit is an official package for efficient state management in Redux.

  • It simplifies common Redux patterns and provides utilities to streamline development.

  • Why Redux Toolkit?

    • Reduces boilerplate code.
    • Encourages best practices.
    • Improves developer productivity.

Key Features of Redux Toolkit

  • 1. ConfigureStore
    • A function that simplifies store configuration.
    • Automatically includes middleware and enables the Redux DevTools Extension.
  • 2. createSlice
    • A function for generating reducer functions and actions.
    • Defines reducers, actions, and initial state in one place.
  • 3. useDispatch
    • A hook for accessing the store’s dispatch function in functional components.
  • 4. useSelector
    • A hook for selecting data from the store’s state.

Setting Up Redux Toolkit

  • Install the @reduxjs/toolkit package in your project.
  • Create a slice using createSlice for each piece of state.
  • Combine slices using configureStore to create the Redux store.
// todoSlice.js
import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload);
    },
  },
});

export const { addTodo } = todoSlice.actions;
export default todoSlice.reducer;
// store.js
import { configureStore } from '@reduxjs/toolkit';
import todoReducer from './todoSlice';

const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
});

Actions with Redux Toolkit

  • Redux Toolkit automatically generates action creators.
  • Use these action creators to dispatch actions.
import { useDispatch } from 'react-redux';
import { addTodo } from './todoSlice';

const TodoForm = () => {
  const dispatch = useDispatch();

  const handleAddTodo = (text) => {
    dispatch(addTodo(text));
  };

  // Rest of the component code
}

Selectors with Redux Toolkit

  • Redux Toolkit simplifies state selection using selectors.
  • Use useSelector to access specific parts of the state.
import { useSelector } from 'react-redux';

const TodoList = () => {
  const todos = useSelector((state) => state.todos);

  // Rest of the component code
}

Middleware in Redux Toolkit

  • Redux Toolkit configures middleware and the Redux DevTools Extension automatically.
  • You can still add custom middleware when needed.

Example

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import todoReducer from './todoSlice';
import customMiddleware from './customMiddleware';

const store = configureStore({
  reducer: {
    todos: todoReducer,
  },
  middleware: [...getDefaultMiddleware(), customMiddleware],
});

Summary

  • Redux Toolkit simplifies and streamlines state management in Redux.
  • Key features include configureStore, createSlice, useDispatch, and useSelector.
  • Actions are generated with action creators.
  • Selectors provide easy access to specific parts of the state.
  • Middleware configuration is simplified.
  • Redux Toolkit promotes best practices and reduces boilerplate code.

Building an iTunes Search App with Redux Toolkit

Step 1: Project Setup

  • Create a new React project using Create React App.
  • Install required dependencies:
    • react-redux for integrating Redux.
    • @reduxjs/toolkit for Redux Toolkit.

Project Structure

my-itunes-search-app/
  src/
    components/
    features/
      search/
      results/
    app/
    store.js
    index.js

Step 2: Define a Slice

  • Create a new slice for the search feature.
  • Define the initial state for search results.
searchSlice.js
import { createSlice } from '@reduxjs/toolkit';

const searchSlice = createSlice({
  name: 'search',
  initialState: {
    results: [],
    loading: false,
    error: null,
  },
  reducers: {
    setSearchResults: (state, action) => {
      state.results = action.payload;
      state.loading = false;
      state.error = null;
    },
    setSearchLoading: (state) => {
      state.loading = true;
    },
    setSearchError: (state, action) => {
      state.error = action.payload;
      state.loading = false;
    },
  },
});

export const { setSearchResults, setSearchLoading, setSearchError } = searchSlice.actions;
export default searchSlice.reducer;

Step 3: Create the Redux Store

  • Create the Redux store using configureStore.
  • Add the search slice to the store’s configuration.
store.js
import { configureStore } from '@reduxjs/toolkit';
import searchReducer from './features/search/searchSlice';

const store = configureStore({
  reducer: {
    search: searchReducer,
  },
});

export default store;

Step 4: Set Up the Search Component

  • Create a Search component that dispatches search actions.
  • Use the useDispatch hook to dispatch actions.
Search.js
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { searchiTunes } from './searchSlice';

function Search() {
  const [query, setQuery] = useState('');
  const dispatch = useDispatch();

  const handleSearch = () => {
    dispatch(searchiTunes(query));
  }

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}

Step 5: Define an Async Action

  • Create an async action to fetch iTunes search results.
  • Use Redux Toolkit’s createAsyncThunk for async actions.
searchSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const searchiTunes = createAsyncThunk('search/searchiTunes', async (query) => {
  const response = await fetch(`https://itunes.apple.com/search?term=${query}`);
  const data = await response.json();
  return data.results;
});

Step 6: Handle Async Action Results

  • Update the slice to handle the async action results.
  • Use the fulfilled, pending, and rejected reducers.
searchSlice.js
const searchSlice = createSlice({
  name: 'search',
  initialState: {
    results: [],
    loading: false,
    error: null,
  },
  reducers: { /* ... */ },
  extraReducers: (builder) => {
    builder
      .addCase(searchiTunes.fulfilled, (state, action) => {
        state.results = action.payload;
        state.loading = false;
        state.error = null;
      })
      .addCase(searchiTunes.pending, (state) => {
        state.loading = true;
      })
      .addCase(searchiTunes.rejected, (state, action) => {
        state.error = action.error.message;
        state.loading = false;
      });
  },
});

Step 7: Display Search Results

  • Create a Results component to display search results.
  • Use the useSelector hook to access the state.
Results.js
import React from 'react';
import { useSelector } from 'react-redux';

function Results() {
  const results = useSelector((state) => state.search.results);

  return (
    <ul>
      {results.map((result) => (
        <li key={result.trackId}>{result.trackName}</li>
      ))}
    </ul>
  );
}

Step 8: Integrate Components

  • Integrate the Search and Results components in your app.

App.js

import React from 'react';
import Search from './features/search/Search';
import Results from './features/results/Results';

function App() {
  return (
    <div>
      <Search />
      <Results />
    </div>
  );
}

Routing in React

Introduction to Routing

  • Routing is a fundamental aspect of building modern web applications.
  • In single-page applications (SPAs), routing allows you to navigate between different views or components without causing full page reloads.
  • For React applications, the de facto solution for routing is the React Router library.

React Router

  • React Router is a powerful and widely-used library for declarative routing in React applications.
  • It provides components like BrowserRouter, Route, Link, and Switch to facilitate routing.
Setting Up React Router
npm install react-router-dom

Basic Routing

  • Start by wrapping your entire application with a BrowserRouter component.
  • Define routes using the Route component, specifying a path and the corresponding component to render.
Example
import { BrowserRouter, Route } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Route path="/" exact component={Home} />
      <Route path="/about" component={About} />
    </BrowserRouter>
  );
}

Linking Between Routes

  • To navigate between different routes, use the Link component.
  • It’s essential for providing users with a means to navigate without the need for full-page reloads.
Example
import { Link } from 'react-router-dom';

function Navbar() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
    </nav>
  );
}

Route Parameters

  • Route parameters are used for dynamic routing, such as displaying user profiles or product details.
  • You can access these parameters in the component using match.params.
Example
<Route path="/user/:id" component={UserDetail} />

function UserDetail({ match }) {
  const userId = match.params.id;
  return <p>User ID: {userId}</p>;
}

Nested Routes

  • You can nest routes within components, which is useful for complex applications.
  • Employ the Switch component to ensure that only the first matching route is rendered.
Example
<Route path="/products" component={Products} />

function Products() {
  return (
    <div>
      <Route path="/products/category/:category" component={CategoryProducts} />
    </div>
  );
}

Redirects

  • Redirects are useful for guiding users from one route to another.
  • Utilize the Redirect component to achieve this behavior.
Example
<Route path="/old-route">
  <Redirect to="/new-route" />
</Route>

Route Guards

  • Route guards are essential for securing your routes based on user permissions or conditions.
  • You can use the Route component with a render function to implement custom logic.
Example
<Route path="/admin" render={() => (isAdmin ? <AdminPanel /> : <Redirect to="/" />)} />

Route Animation

  • To provide a more engaging user experience, you can add animations to your route transitions.
  • Achieve this by utilizing CSS transitions or popular animation libraries like react-transition-group.
Example
<Route path="/page" component={Page} />

.page-enter {
  opacity: 0;
  transform: scale(0.9);
}

.page-enter-active {
  opacity: 1;
  transform: scale(1);
  transition: opacity 300ms, transform 300ms;
}

.page-exit {
  opacity: 1;
  transform: scale(1);
}

.page-exit-active {
  opacity: 0;
  transform: scale(0.9);
  transition: opacity 300ms, transform 300ms;
}

Route Data Fetching

  • In many cases, you need to fetch data when a route is activated.
  • You can do this by utilizing lifecycle methods or the useEffect hook in functional components.
Example
<Route path="/profile/:username" component={UserProfile} />

function UserProfile({ match }) {
  const { username } = match.params;

  useEffect(() => {
    // Fetch user data based on the username
  }, [username]);

  // Rest of the component code
}

Summary

  • Routing is an integral part of single-page applications, enabling seamless navigation within your React app.
  • React Router is a highly popular and effective library for handling routing in React.
  • Basic routing involves BrowserRouter, Route, and Link components.
  • Advanced routing includes route parameters, nested routes, redirects, route guards, animations, and data fetching.

Unit Testing and Jest

What is Unit Testing?

  • Unit Testing is a software testing technique where individual components or functions of a program are tested in isolation to ensure they work correctly.

  • Purpose

    • Validation: Verify that each part of the software performs its functions as expected.
    • Regression Testing: Detect and prevent software bugs when making changes.
    • Documentation: Provide documentation and examples of how the code should be used.

What is Jest?

  • Jest is a popular JavaScript testing framework.

  • Developed by Facebook, it’s commonly used for testing JavaScript and TypeScript applications.

  • Jest is built on top of Jasmine and provides a rich set of features for writing unit tests.

  • Features

    • Automatic Mocking: Mocking of functions and modules.
    • Assertion Library: Built-in assertions using expect.
    • Test Runner: Execute tests and provide test results.
    • Snapshot Testing: Capture snapshots of rendered UI components.

Writing Your First Jest Test

  1. Install Jest:

    npm install --save-dev jest      
  2. Create a test file:

    // myFunction.test.js
    
    test('Example Test', () => {
      // Test code here
    });
  3. Run the tests:

    npx jest

Writing Test Suites and Test Cases

  • Test Suite: A collection of test cases for a specific module or component.

  • Create a test suite using describe:

    describe('Math Functions', () => {
      // Test cases go here
    });
  • Test Case: An individual test scenario within a test suite.

  • Create a test case using test:

    test('Addition', () => {
      // Test addition functionality here
    });

Using Jest’s Expect for Assertions

  • expect is used for making assertions in Jest.
  • It allows you to specify what you expect the outcome to be.
test('Addition', () => {
  expect(add(2, 3)).toBe(5);
});
  • Jest provides a variety of assertion methods, like toBe, toEqual, toBeTruthy, and more.

Running and Organizing Tests

  • Run all tests in the project:

    npx jest
  • Run specific test files or test suites:

    npx jest myFunction.test.js
  • Keep test files alongside the code to be tested (e.g., myFunction.js and myFunction.test.js).

  • Organize tests into folders and subfolders to match your project’s structure.

Basic Matchers and Matchers API

  • Common Matchers:

    • toBe(): Strict equality (===).
    • toEqual(): Deep equality.
    • not.toBeNull(): Ensure a value is not null.
  • Chaining Matchers:

    expect(result).toBeGreaterThan(10).toBeLessThan(20);
  • Negating Matchers:

    expect(result).not.toBeFalsy();

Detailed Explanation of Matchers

  • Matchers are functions that check values against expectations.
    • Jest provides a rich set of matchers for various scenarios.
    • toBe(value): Strict equality.
    • toEqual(value): Deep equality.
    • not: Inverts the matcher.
    • toContain(item): Checks if an array or string contains an item.
    • toMatch(regexp): Matches a string against a regular expression.

Testing React Components

  • Component Testing is the practice of testing individual React components in isolation to ensure they work as intended.

  • Importance of Component Testing

    • Ensures components render correctly and behave as expected.
    • Helps catch UI bugs early in the development process.
    • Provides confidence when making changes or adding new features.

Rendering Components for Testing

  • Setting Up a Testing Environment
    • Choose a testing framework: Jest is commonly used with React.
    • Install required dependencies, e.g., react-testing-library.
    • Configure your test environment.
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('MyComponent renders correctly', () => {
  const { getByText } = render(<MyComponent />);
  expect(getByText('Hello, World!')).toBeInTheDocument();
});

Assertions on React Components

  • Assertions validate whether the component behaves as expected.
  • Common assertions are provided by the testing library.
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('MyComponent renders correctly', () => {
  const { getByText } = render(<MyComponent />);
  expect(getByText('Hello, World!')).toBeInTheDocument();
});

Common Assertions

  • toBeInTheDocument(): Checks if an element is in the document.
  • toHaveTextContent(text): Checks for specific text content.
  • toHaveAttribute(attrName): Checks for attributes.

Testing Component Props and State

  • Pass props when rendering components for testing.
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('MyComponent receives and displays a prop', () => {
  const { getByText } = render(<MyComponent message="Hello, Test!" />);
  expect(getByText('Hello, Test!')).toBeInTheDocument();
});

Testing State

  • Use React Testing Library to interact with and test state changes.
import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';

test('Counter increments when the button is clicked', () => {
  const { getByText } = render(<Counter />);
  const button = getByText('Increment');
  fireEvent.click(button);
  expect(getByText('Count: 1')).toBeInTheDocument();
});

Importance of Mocking in Unit Testing

  • What is Mocking?

    • Mocking is the practice of simulating the behavior of external dependencies or functions during unit testing.
    • It allows you to isolate the code being tested and control the behavior of external components.
  • Why Mocking Is Important

    • Ensures tests focus on the specific functionality under examination.
    • Speeds up tests by avoiding actual external calls.
    • Makes testing more deterministic and reproducible.

Using Jest Mocks for Functions and Modules

  • Jest Mocks
    • Jest provides a built-in mocking system for JavaScript functions and modules.
// Original function
const fetchData = () => { /* ... */ };

// Mocked function
jest.mock('./api', () => ({
  fetchData: jest.fn(() => 'Mocked Data'),
}));

Mocking a Module

// Original module
import axios from 'axios';

// Mocked module
jest.mock('axios');
axios.get.mockResolvedValue({ data: 'Mocked Data' });

Handling Asynchronous Code with Mocks

  • Mocking asynchronous functions with Jest:
const fetchData = jest.fn().mockResolvedValue('Mocked Data');

test('Async Function Test', async () => {
  const result = await fetchData();
  expect(result).toBe('Mocked Data');
});
  • Controlling Asynchronous Timers

  • Use jest.advanceTimersByTime(ms) to control timers:

jest.useFakeTimers();
jest.advanceTimersByTime(1000); // Advance timers by 1 second

Mocking API Calls and Third-Party Libraries

  • Mocking HTTP requests with axios:
import axios from 'axios';

jest.mock('axios');
axios.get.mockResolvedValue({ data: 'Mocked Data' });
  • Mocking external libraries with jest.mock:
jest.mock('external-library', () => ({
  someFunction: jest.fn(() => 'Mocked Result'),
}));

Testing Asynchronous Operations with async/await

  • Asynchronous Testing

    • Many functions in modern applications involve asynchronous operations like API calls, timers, and events.
    • It’s essential to test these asynchronous behaviors to ensure your application functions correctly.
  • Using async/await

    • async/await simplifies testing asynchronous code.
    • await is used to pause the test until the promise resolves or rejects.
test('Async Function Test', async () => {
  const result = await fetchData();
  expect(result).toBe('Expected Data');
});

Handling Promises in Tests

  • Use .resolves and .rejects matchers to test promise resolutions and rejections.
test('Promise Resolves', async () => {
  await expect(Promise.resolve('Resolved')).resolves.toBe('Resolved');
});

test('Promise Rejects', async () => {
  await expect(Promise.reject('Rejected')).rejects.toThrow('Rejected');
});
  • You can combine await with .resolves and .rejects for testing.
test('Async Promise Resolves', async () => {
  await expect(fetchData()).resolves.toBe('Expected Data');
});

Testing Timeouts and Delays

  • Use jest.useFakeTimers() to control timers.
test('Timeout Test', () => {
  jest.useFakeTimers();
  delayedFunction();
  jest.advanceTimersByTime(1000);
  expect(mockCallback).toHaveBeenCalledTimes(1);
});
  • Use setTimeout with a Promise to simulate delays in tests.
test('Delayed Function Test', async () => {
  const result = await delayedFunction();
  expect(result).toBe('Delayed Result');
});

Simulating Async Behavior with Jest

  • Jest provides methods for simulating async behavior without actual delays.
test('Simulated Async Test', async () => {
  jest.spyOn(global, 'fetch').mockResolvedValue({
    json: jest.fn().mockResolvedValue('Simulated Data'),
  });
  const result = await fetchData();
  expect(result).toBe('Simulated Data');
});

Resetting Spies and Mocks

  • Don’t forget to reset mocks and spies after testing.
beforeEach(() => {
  jest.clearAllMocks();
});
☀️