Important Topics in React.js: The Expert’s Guide to JSX, Components, Performance, & Best Practices

27 min read
106

To really use React.js, you need to understand its core concepts and master the important topics in React.js.

React has fundamentally changed how we think about building dynamic web interfaces. Its elegance, efficiency, and focus on developer experience have made it the darling of frontend development. I’ve been a React enthusiast from the early days, and this article is my way of sharing the knowledge and insights I’ve gained along the way.

Important Topics in React.js: The key concepts

React.js, a popular JavaScript library developed by Facebook, is used to build dynamic and interactive user interfaces. Its component-based structure, efficient updates, and declarative style make it a powerful tool for web developers.

To master it, let’s explore React.js key concepts in a structured, multi-part series: Fundamentals, Essentials, and Advanced Concepts.

Part 1: Fundamentals

JSX: More Than Just Syntax Sugar

JSX may look like HTML embedded within your JavaScript code, but it’s a powerful extension that transforms how you build user interfaces.

Let’s examine why it’s more powerful than it may initially appear.

The Power of Expressions

👉 Variables and Logic: Seamlessly inject dynamic content into your components using curly braces {}.

const name = "Amit";
const greeting = <h1>Hello, {name}!</h1>;

const isLoggedIn = false;
const message = isLoggedIn ?  <h1>Welcome Back!</h1> : <h1>Please Login</h1>;

👉 Function Calls: JSX isn’t limited to simple variables. Call functions directly, opening the door to component composition and reusability:

function formattedGreeting(userName) {
   return 'Welcome, ' + userName + '!'; 
}

function UserProfile(props) {
   return (
       <div>
           <h1>{formattedGreeting(props.name)}</h1>
           <img src={props.imageUrl} alt="Profile" />
       </div>
   );
}
Best Practices and Pitfalls

👉 Quoting Attributes: Reinforce the distinction: string attributes require quotes, numeric ones don’t.

<button type="button" onClick={handleClick}>Click Me</button> 
<div style={{ height: 100 }}>A div with height</div> 

👉 Whitespace Matters: JSX can be finicky with whitespace. To be explicit, either wrap text in a tag (e.g., <span>) or use {} :

// Less ideal if you want to preserve spacing between elements
const element = <p>Hello World</p>; 

// Better
const element = <p>Hello {' '}World</p>; 
const element = <p>Hello <span>World</span></p>;

👉 Reserved Words: Avoid clashes with JavaScript keywords by using className instead of class and htmlFor instead of for:

<label htmlFor="myInput">Enter your name:</label>
<input type="text" id="myInput" className="form-input" />
Advanced Examples

👉 Mapping over Arrays: Create lists easily:

const colors = ['red', 'green', 'blue'];
const listItems = colors.map((color) => <li key={color}>{color}</li>);

👉 Conditional Rendering: Show techniques with &&, ternary operators, and if...else:

// Using && for short-circuiting
{isLoggedIn && <p>Welcome Back!</p>}

// Using a ternary operator
{isLoggedIn ? <p>Welcome Back!</p> : <p>Please Login</p>} 

// Using a full if...else within JSX
{ 
  if (isLoggedIn) {
    return <p>Welcome Back!</p>; 
  } else {
   return <p>Please Login</p>; 
  }
}
Key Takeaway

Remember that JSX is essentially a convenient syntax that gets converted into standard JavaScript function calls, specifically React.createElement(). This transformation typically happens during your build process using a tool like Babel.

Translating JSX to React.createElement()

Let’s consider a simple example:

const element = <h1>Hello, world!</h1>;

This JSX, behind the scenes, is roughly equivalent to the following JavaScript:

const element = React.createElement(
  'h1',  // Element type
  null,  // props (no props in this example)
  'Hello, world!' // Children 
);

👉 Let’s break it down:

  • Element Type: The first argument to React.createElement is the type of the element to be created, in this case, an ‘h1’.
  • Props: The second argument is an object containing any attributes (or properties, hence ‘props’) you want to pass to the element. Here, we have none.
  • Children: The third and any subsequent arguments are the children of the element. This can be text content, other React elements, or even arrays of elements.

👉 Why does this matter?

Understanding this translation helps you:

  • Debug: When debugging issues with your UI, it’s sometimes helpful to think about the React.createElement calls your JSX is generating.
  • Performance: While JSX is convenient, being aware of how it generates nested function calls can be informative when optimizing exceptionally performance-critical components.
Note:
In modern React development, you likely won’t see these React.createElement calls directly, as the JSX transformation takes care of them. Yet, having this fundamental knowledge empowers you!

Components: The Heart and Soul of React

React revolves around the idea of building reusable components. Each component represents a self-contained part of your user interface, encapsulating both its appearance (via JSX) and its behavior (via state and logic).

1. Composition: The Key to Scalability

👉 Breaking Down Complex UIs: The true power of React lies in breaking down complex interfaces into a hierarchy of smaller, manageable components. Imagine a website as a tree: the topmost component is your entire app, branching into components for navigation, content areas, individual buttons, etc.

Visual Example: You could include a simple diagram of a component tree, perhaps starting with an “App” component that splits into “Header,” “MainContent,” and “Footer” components, with further subdivisions inside each.

👉 Common Composition Patterns: Established patterns help structure your components effectively:

  • Container/Presentational Pattern: Decouples data fetching and logic (container components) from purely visual components (presentational components), fostering clean code.
2. Controlled vs. Uncontrolled Components

Form elements in React can be handled in two primary ways:

👉 Explicit Control (Controlled Components): The component fully manages the value of an input element by syncing it with React state. Every user change updates the state, which, in turn, updates the input field’s value. This is common for most forms.1

function Form() {
  const [inputValue, setInputValue] = useState(''); 

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

  return (
    <input type="text" value={inputValue} onChange={handleChange} />
  );
}

👉 Letting the DOM Go its Own Way (Uncontrolled Components): The DOM maintains the input element’s value independently. You can access this value using ref. These are less common but valuable when integrating with non-React libraries or needing direct form manipulation.

3. Higher-Order Components (HOCs)
  • Abstracting Functionality: HOCs are a powerful technique for reusing component logic. They are functions that take a component as input and return a new component with enhanced functionality.
  • Common Use Cases:
    • State Management
    • Authentication/Authorization
    • Theming
    • And many more!

👉 Note: We can revisit HOCs in more detail later, as it’s a slightly more advanced topic.

Props and State: Data’s Dynamic Dance

Understanding props and state is essential for building dynamic and interactive React applications.

  • Props: Data passed down from parent to child components, like the ingredients to a recipe. Props are read-only and promote unidirectional data flow.
  • State: Internal data managed within a component, like the current cooking stage of that recipe. State updates can trigger re-renders to keep the UI in sync with your component’s data.
1. State Updates and Re-renders

Let’s break down how a state update with useState causes a re-render:

👉 Event Occurs: A user interacts with your component (clicks a button, types in a field, etc.).

  • State Change: You use the state setter (e.g., setCount from useState) to update the state value.
  • React Does Its Thing: React detects the modified state and schedules a re-render of the component and its affected children.
  • New UI Reflects the Changes: React efficiently compares the previous output with the new one and updates the DOM only where changes are necessary.

👉 Immutability Matters: Always treat state (and props!) as immutable. Instead of modifying existing objects/arrays, create new ones when updating:

// Bad: Mutating State Directly
const [items, setItems] = useState([]);
items.push('New Item'); // Don't do this!
setItems(items);  // React might not detect this change

// Good: Creating a New Array
const [items, setItems] = useState([]);
const newItems = [...items, 'New Item']; 
setItems(newItems); // Correct approach
2. Lifting State Up
  • Finding the Common Ancestor: Sharing data between siblings often means going up the component tree. Find the nearest parent that needs access to the data.
  • Passing State Down: The state lives in the parent component and is passed down to children as props.
3. When to Avoid State

👉 Derived Data: If values can be calculated directly from props or other state, you don’t need additional state.

function OrderSummary({ items }) {
  const totalCost = items.reduce((sum, item) => sum + item.price, 0);
  return <div>Total Cost: ${totalCost}</div>;
}

👉 Complexity vs. Maintainability: Consider the trade-off: Does introducing extra state simplify your component logic over time, even with a slight performance overhead?

ALSO READ:  Mastering the Basics: Operators and Operands in Programming

Let’s solidify this! How about an example where refactoring by ‘lifting state up’ improves data flow? Or an exercise demonstrating the consequences of mutating the state incorrectly?

Let’s see both those scenarios in action.

Scenario: Lifting State Up for Cleaner Data Flow

Scenario: Imagine a simple app with two sibling components:

  • Counter A: A button and a display showing a count.
  • Counter B: A button and a display showing the same count.

Initial (Less Ideal) Implementation: Each counter manages its count state independently. This leads to unnecessary redundancy and potential synchronization issues.

👉 Refactored with State Lifting:

  1. Move the State Up: Lift the count state into a new parent component (CounterContainer).
  2. Pass State as Props: Pass the count and a handleIncrement function (to update the count ) as props to both CounterA and CounterB.

Example:

// Before Lifting State Up
function CounterA() {
  const [count, setCount] = useState(0); 
  // ...
}

function CounterB() {
  const [count, setCount] = useState(0); 
  // ...
}

// After Lifting State Up
function CounterContainer() {
  const [count, setCount] = useState(0);

  const handleIncrement = () => {
    setCount(count + 1);
  }

  return (
    <div>
      <CounterA count={count} onIncrement={handleIncrement} />
      <CounterB count={count} onIncrement={handleIncrement} />
    </div>
  );
}

👉 Benefits:

  • Single Source of Truth: The count is managed in one place, preventing inconsistencies.
  • Easier Synchronization: Changes to the count automatically flow to both counters.
  • Cleaner Code: Less redundancy and clearer component responsibilities.
Scenario: Mutating State Incorrectly

Let’s see why immutability matters:

function ShoppingCart() {
  const [items, setItems] = useState([
    { id: 1, name: 'Milk', qty: 2 },
  ]);

  const addItem = () => {
    items.push({ id: 2, name: 'Bread', qty: 1 }); // Incorrect Mutation!
    setItems(items);  
  };

  return (
    // ... render the cart
  );
}

👉 Problem: React might not detect the change due to the mutation. The UI might not update even though the array has changed internally.

Correct Approach:
  const addItem = () => {
    const newItems = [...items, { id: 2, name: 'Bread', qty: 1 }]; 
    setItems(newItems);  
  };

👉 Try it out in an online code editor! Incorrect mutation can lead to subtle and hard-to-debug issues.

Part 2: Mastering the Essentials

This section will acknowledge:

  1. Lifecycle methods emphasize the power of hooks like useState and useEffect
  2. React’s performance prowess stems largely from its intelligent use of the Virtual DOM
  3. State Management, for simple applications, React’s built-in state management might be enough

Lifecycle Methods: Hooked on Change

Hooks were introduced in React 16.8 as a way to add state and other React features to functional components. They allow developers to reuse stateful logic across components without writing class-based components. useState, useEffect, and useContext are some of the most commonly used hooks in React.js.

Legacy of Lifecycle Methods (Class Components)
  • componentDidMount: Perfect for tasks that should happen right after a component is first added to the DOM:
    • Fetching data
    • Setting up timers or subscriptions
  • componentDidUpdate: Executes after a component updates (re-renders). Use it to:
    • Update the DOM based on state or prop changes
    • Fetch new data if props change
  • componentWillUnmount: Called before a component is removed. Essential for cleaning up:
    • Removing event listeners
    • Clearing timers or intervals
The Rise of Hooks

Hooks have revolutionized how we manage state and side effects in React.

  1. useState: The go-to hook for introducing simple state into functional components.
  2. useEffect: The powerhouse behind side effects! Think of it as componentDidMount, componentDidUpdate, and componentWillUnmount combined, but with more finesse:
    • Running after Initial Render: Use it for fetching data, setting up subscriptions, etc.
    • Reacting to Changes: Specify an array of values as the second argument to useEffect. It only re-runs if one of those values change.
  3. useRef and Custom Hooks
    • useRef: Creates a “ref” to hold values that persist across renders, often used for directly accessing DOM elements or storing mutable values without triggering re-renders.
    • Custom Hooks: Bundle up reusable component logic with state and effects into a single custom hook to enhance code organization and testability.
Practical Examples

👉 Fetching data on mount:

function NewsFeed() {
   const [articles, setArticles] = useState([]);

   useEffect(() => {
     const fetchData = async () => {
       const result = await fetch('/api/news');
       const data = await result.json();
       setArticles(data);
     };

     fetchData();
   }, []); // Empty dependency array: runs only on mount
 }

👉 Setting up a timer or interval:2

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

  useEffect(() => {
    const intervalId = setInterval(() => {
      setCount(count => count + 1);
    }, 1000); 

    return () => clearInterval(intervalId); // Cleanup on unmount
  }, []); 
}

👉 Implementing componentWillUnmount logic:

// Example: Removing an event listener
useEffect(() => {
  const handleResize = () => { /* ... */ };
  window.addEventListener('resize', handleResize);

  return () => window.removeEventListener('resize', handleResize); 
}, []);
Note
While you can still use the lifecycle methods in class-based components, hooks are generally preferred for their flexibility and ease of use in functional components.

Let’s make this concrete! How about an example build an interactive component that demonstrates the use of useEffect or write a custom hook that simplifies some reusable logic?

Let’s dive into both of those. I think they perfectly illustrate the power of hooks.

Example 1: Interactive Component with useEffect

Let’s build a simple component that fetches and displays a random quote, updating it on a button click.3

import { useState, useEffect } from 'react';

function QuoteFetcher() {
  const [quote, setQuote] = useState(null);

  const fetchQuote = async () => {
    const response = await fetch('https://api.quotable.io/random');
    const data = await response.json();
    setQuote(data);
  };

  useEffect(() => {
    fetchQuote(); // Get an initial quote
  }, []); 

  return (
    <div>
      {quote ? (
        <div>
          <p>{quote.content}</p>
          <p>- {quote.author}</p>
        </div>
      ) : (
        <p>Loading a quote...</p> 
      )}
      <button onClick={fetchQuote}>New Quote</button>
    </div>
  );
}

export default QuoteFetcher;

👉 How it works:

  • useEffect on mount: The empty dependency array ([]) makes useEffect run only after the first render, fetching the initial quote.
  • fetchQuote: A helper function to encapsulate fetching logic.
  • Button Click: Each button click triggers fetchQuote.
Example 2: Custom Hook for Re-usable Logic

Let’s create a useWindowSize hook to track the browser window’s dimensions4 5 6.

import { useState, useEffect } from 'react';

function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };

    window.addEventListener('resize', handleResize);

    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return windowSize;
}

export default useWindowSize;

👉 Usage in a component:

function MyComponent() {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>Window width: {width}</p>
      <p>Window height: {height}</p>
    </div>
  );
}

👉 Benefits of a custom hook:

  • Encapsulation: Hides resizing logic, making components cleaner.
  • Reusability: Easily track window dimensions across multiple components.

The Virtual DOM: React’s Secret Weapon

The virtual DOM is one of the key innovations that sets React apart. Understanding how it works empowers you to build highly performant React applications.

DOM Manipulation Bottlenecks

The DOM is a tree-like structure representing a webpage. Modifying the DOM directly (adding elements, changing attributes, etc.) is relatively slow. Excessive DOM manipulation, especially in complex applications, can lead to noticeable performance issues.

Diffing Algorithms and Reconciliation

React’s brilliance lies in its ability to intelligently minimize the number of costly DOM updates. It achieves this with the following process:

  • Virtual DOM Updates: When state or props change, React re-renders the relevant component in a lightweight virtual DOM representation.
  • Diffing: React compares the updated virtual DOM with a previous snapshot (before the change) using a diffing algorithm. It pinpoints the exact differences that need to occur in the real DOM.
  • Reconciliation: React applies the calculated changes to the real DOM in the most efficient way possible.

React’s diffing algorithm employs several heuristics:

  • Tree Comparisons: It generally compares elements at the same level of the component tree.
  • Component Types: If components of different types are in the same position, the entire subtree beneath that component is likely to change.
  • Keys: Using unique key props for elements within lists helps React optimize updates.
Optimization Implications

Breaking down large components into smaller ones offers several advantages in React.js development. By dividing complex UIs into smaller, more manageable components, developers can isolate changes effectively. When modifications occur, only the affected portions of the virtual DOM require re-rendering and diffing.

👉 shouldComponentUpdate: In class components, this lifecycle method allows fine-grained control to prevent unnecessary re-renders if the props or state that affect the component’s output haven’t changed. (Hooks and memoization techniques provide similar functionality for functional components).

Let’s illustrate this! How about an example a visual representation of the diffing process or explore how shouldComponentUpdate (or its functional counterpart React.memo) can prevent unnecessary re-renders?

Let’s explore both scenarios. I think they’ll perfectly illuminate the concepts.

Scenario 1: Visualizing the Diffing Process

There are excellent online tools that help visualize React’s diffing algorithm. Let’s look at one together:

👉 React DevTools: If you use React’s browser development tools (available for Chrome and Firefox), there’s often a tab that will let you inspect component updates and see what parts of the UI React has reconciled.

ALSO READ:  In what ways do Python and JavaScript collaborate or interact?

👉 Simplified Example: Consider a simple list rendering component:

function ItemList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}
  1. Initial Render: React creates a virtual DOM representation.
  2. Update: Let’s say the text of a single item changes.
  3. New Virtual DOM: React re-renders the ItemList component, creating a new virtual DOM representation.
  4. Diffing: React’s diffing algorithm compares the old and new virtual DOM representations. It notices that most of the <li> elements are the same, except for the one that changed its text content.
  5. Reconciliation: React updates only that single <li> element in the real DOM.
Scenario 2: Preventing Unnecessary Re-renders

Let’s use React.memo (the functional equivalent of shouldComponentUpdate) in an example:

import { memo } from 'react';

function Profile({ name, age }) {
  // ... some expensive calculations based on name and age

  return (
    <div>
      <h1>{name}</h1>
      <p>Age: {age}</p>
    </div>
  );
}

export default memo(Profile); 

👉 How it works:

  • memo: This higher-order component “memoizes” the Profile component. Essentially, it means React will skip re-rendering if the name and age props haven’t changed.
  • Performance Gain: Prevents those expensive calculations from running unnecessarily if the component’s input props remain the same.

👉 Caveats:

  • Shallow Comparison: React.memo performs a shallow comparison of props. If you have complex data structures as props, you might need custom comparison logic.
  • Over-optimization: Be mindful that excessive use can sometimes hinder performance.

State Management: Taming the Complexity Beast

State management is crucial for managing data within React.js applications. It involves storing and updating the state of components to reflect changes in the UI. React provides built-in state management capabilities, but developers can also use external libraries like Redux or MobX for more complex applications. Effective state management ensures that components remain synchronized and responsive to user interactions.

The Need for Structure

As your React application grows, state spread across numerous components can turn unruly. Challenges you might face include:

  • Unpredictability: Updates to the state in one component might have unintended consequences somewhere else in your app.
  • Debugging: Tracing and pinpointing the source of state changes can become a nightmare.
Redux Principles

Redux introduces predictability and structure to global state management:

👉 Actions: Actions are plain JavaScript objects with a type property that describes what happened (e.g., { type: 'ADD_TODO', text: 'Write an email' }).

👉 Reducers: Pure functions that take the current state and an action and return the new state. It is crucial that reducers are pure (no side effects) and don’t mutate the state directly.

👉 The Store: The single, central location holding your application’s entire state. The store is created using a reducer function.

👉 Unidirectional Data Flow: A clear, linear flow:

  • Components dispatch actions
  • Reducers compute the new state based on the action
  • The store updates
  • The UI re-renders based on the new state
Middleware

Think of middleware as interceptors that sit between the action being dispatched and the reducer. They allow for:

  • Logging: Logging actions and state changes for debugging purposes.
  • Asynchronous API calls: Handling interactions with external servers.
  • Crash Reporting: And many other custom behaviors!
Redux with React

👉 connect: This higher-order component from the react-redux library injects parts of the Redux state (mapStateToProps) and the dispatch function (mapDispatchToProps) into your component props.

👉 Provider: A component at the root of your application that makes the Redux store available to all connected components below it.

👉 useSelector and useDispatch: The preferred hooks-based way to interact with Redux.

  • useSelector: Extract specific parts of the state.
  • useDispatch: Obtain the dispatch function to trigger actions.

Okay, let’s solidify this with a simple example! How about we build a basic todo list app using Redux? Or perhaps create some middleware to demonstrate logging or asynchronous behavior?

Let’s build a simplified todo list app with Redux to showcase the core concepts.

Project Setup

1. Create a React Project: You can use Create React App or your preferred tool.

2. Install Redux Dependencies:

npm install redux react-redux
Project Structure
src/
  store.js
  actions/
    todoActions.js
  reducers/
    todoReducer.js
  components/
    TodoList.js 
   TodoForm.js
  App.js
  index.js
Steps
1. Define Actions (actions/todoActions.js)
// actions/todoActions.js
export const addTodo = (text) => ({
  type: 'ADD_TODO',
  id: Math.random(), // Simplified, in a real app use a unique ID generator
  text,
});

export const toggleTodo = (id) => ({
  type: 'TOGGLE_TODO',
  id
});
2. Create a Reducer (reducers/todoReducer.js)7 8
// reducers/todoReducer.js
const initialState = [];

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: action.id, text: action.text, completed: false }]; 
    case 'TOGGLE_TODO':
      return state.map(todo => 
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

export default todoReducer;
3. Configure the Store (store.js)9
// store.js
import { createStore } from 'redux';
import todoReducer from './reducers/todoReducer';

const store = createStore(todoReducer);

export default store;
4. Component Structure (components/TodoList.js, components/TodoForm.js)10
// components/TodoList.js
import { useSelector, useDispatch } from 'react-redux';
import { toggleTodo } from '../actions/todoActions';

const TodoList = () => {
  const todos = useSelector(state => state);
  const dispatch = useDispatch();

  return (
    <ul>
      {todos.map(todo => (
        <li 
          key={todo.id} 
          onClick={() => dispatch(toggleTodo(todo.id))}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  );
};

// components/TodoForm.js (similar structure, dispatching the 'addTodo' action) 
5. Root Component and Provider (App.js)
// App.js
import { Provider } from 'react-redux';
import store from './store';
import TodoList from './components/TodoList';
import TodoForm from './components/TodoForm';

function App() {
  return (
    <Provider store={store}>
      <div>
        <TodoForm />
        <TodoList />
      </div>
    </Provider>
  );
}
6. In your Main file (index.js)
// index.js
import App from './App';
// ... rest of your React app setup

Okay, now let’s add some middleware to our todo app to demonstrate these concepts.

Steps
1. Logging Middleware

Let’s create a simple logger that will log the actions dispatched and any state changes:11

// middleware/logger.js
const logger = (store) => (next) => (action) => {
  console.log('dispatching', action);
  const result = next(action); 
  console.log('next state', store.getState());
  return result;
};

export default logger;
2. Applying Middleware

Modify your store configuration (store.js):

import { createStore, applyMiddleware } from 'redux';
import todoReducer from './reducers/todoReducer';
import logger from './middleware/logger';

const store = createStore(todoReducer, applyMiddleware(logger)); 

export default store;

Now, each time you dispatch an action (add a todo, toggle a todo), you should see logs in the console!

3. Async Middleware (Example)

Let’s make a middleware to simulate fetching todos from an API:12

// middleware/async.js 
const asyncMiddleware = (store) => (next) => (action) => {
  if (typeof action === 'function') { 
    return action(store.dispatch, store.getState); 
  }

  return next(action); 
};

Now, instead of dispatching plain actions, you can dispatch functions:13

// Inside a component
const fetchTodos = () => async (dispatch) => { 
  const response = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=5');
  const todos = await response.json();
  dispatch({ type: 'TODOS_LOADED', todos });
};

// ... in your component, call it like this
dispatch(fetchTodos());

👉 Enhancements:

  • We added a new action type ‘TODOS_LOADED’. You’ll need to update your reducer to handle it.
  • For production apps, consider a dedicated middleware library like redux-thunk or redux-saga for better async handling.

👉 Important Notes:

  • Middleware runs between dispatching an action and the action reaching the reducer.
  • Middleware is immensely powerful for handling asynchronous actions, logging, error reporting, and more!

👉 You can experiment! You could modify your async middleware to introduce artificial delays to demonstrate how it handles loading states in the UI.

Part 3: Advanced Concepts – Routing

Now let’s dive into the world of React Router, a powerful library designed to make navigation in your React applications seamless and organized.

Routing: Navigation Made Seamless (Focus on React Router)

1. Declarative Routing

React Router embraces React’s component-based nature. Instead of manually managing navigation logic, you define routes using declarative components like <Route>, <Link>, and <Switch> (or newer versions of these).

👉 Benefits:

  • Predictability: Your UI’s structure mirrors the URL structure.
  • Readability: Your application’s navigation is more easily understood.
2. Dynamic Routing and Nested Routes

👉 URL Parameters: Create dynamic routes that respond to different values in the URL.

<Route path="/products/:productId" component={ProductDetails} />

The :productId is a parameter, and its value can be accessed in the ProductDetails component.

👉 Nesting Complexity: Build hierarchical navigation reflecting your app’s structure.

<BrowserRouter>
  <Switch> {/* Use Switch in older React Router versions */}
    <Route path="/" component={Home} exact />
    <Route path="/products">
       <ProductsLayout>
         <Route path="/products/:productId" component={ProductDetails} />
       </ProductsLayout>
    </Route>
    {/* ... more routes */}
  </Switch>
</BrowserRouter>
3. Code Splitting and Lazy Loading

Code-splitting allows you to divide your JavaScript bundle into smaller chunks, loaded on demand when the corresponding route is needed.

👉 React.lazy and Suspense:

import React, { Suspense, lazy } from 'react';
const ProductDetails = lazy(() => import('./ProductDetails'));

// Within a route definition
<Route path="/products/:productId">
  <Suspense fallback={<div>Loading...</div>}>
    <ProductDetails /> 
  </Suspense>
</Route>
  • React.lazy lets you define a component to be loaded dynamically.
  • Suspense provides a placeholder UI while the lazy-loaded component is being fetched.
ALSO READ:  Creating a Love Calculator Using HTML, CSS, and JavaScript
Note
For simpler applications, you might start without code splitting, adding it later as an optimization when your app grows.

Styling: Beyond Plain CSS

Let’s take a look into the colorful world of styling React applications!

1. Inline Styles

👉 Pros:

  • Quick and Dirty: Useful for rapid prototyping and minor adjustments.
  • Situational Specificity: Inline styles have the highest specificity, overriding other styles.

👉 Cons:

  • Readability & Maintenance: Styles can quickly clutter your JSX.
  • No Pseudo-Selectors: You can’t use handy selectors like :hover:focus, or :before.
  • Loss of CSS Features: Media queries, advanced selectors, etc., become difficult.
2. CSS Modules

CSS Modules generate unique class names locally for each component. No more worries about naming collisions in large projects!

👉 Integration:

  • Webpack: Often used with Webpack or other build tools; loaders are typically needed for seamless integration.
  • Example File Name: styles.module.css
3. CSS-in-JS Deep Dive (Focus on Styled Components)

CSS-in-JS like styled-components lets you write CSS directly within your JavaScript components using template literals.

👉 Benefits:

  • Component Scoping: Styles automatically scoped to the component, eliminating global leaks.
  • Dynamic Styling with Props: Pass props to your styled components to change styles based on component state or user input.
  • Powerful Features: Automatic vendor prefixing, built-in theming support, and easier to write nested styles.

👉 Drawbacks:

  • Learning Curve: A shift in how you approach styling.
  • Setup: Might require some initial setup and build process configuration.
4. Example with Styled Components14
import styled from 'styled-components';

const Button = styled.button`
  background-color: ${props => props.primary ? 'palevioletred' : 'white'};
  color: ${props => props.primary ? 'white' : 'palevioletred'};
  padding: 10px 15px;
  border: none;
`;

const MyComponent = () => {
  return (
    <div>
      <Button>Normal Button</Button>
      <Button primary>Primary Button</Button>
    </div>
  );
};
Note
For very small projects, inline styles or regular CSS might suffice. CSS Modules are a great middle ground for medium-sized projects. Some developers strongly prefer the separation of CSS, while others love the power of CSS-in-JS. Most CSS-in-JS libraries have good performance, but if you have extremely performance-critical components, consider these trade-offs.
5. Let’s Build!

Let’s experiment with CSS Modules by setting it up in a small project Or see dynamic styling in action with styled-components to get a hands-on feel for their differences.

Option 1: CSS Modules

👉 Project Setup (assuming Create React App):

  • Start a new project: npx create-react-app css-modules-demo
  • You won’t need to install anything extra; CRA has basic CSS Modules support built-in.

👉 Create a Component: Make a new component file: src/Button.js15

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

const Button = (props) => {
  return <button className={styles.button}>{props.children}</button>;
};

export default Button;

👉 CSS Styling: Create src/Button.module.css:

.button {
  background-color: teal;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 5px; 
} 

👉 Use the Component: n App.js or another component:

import Button from './Button';

// Inside your component:
<Button>Click me!</Button>
Option 2: Styled Components

👉 Project Setup: If you don’t have a project already, start one with Create React App or your preferred tool. Install styled-components:

npm install styled-components

👉 Create a Styled Component:

import styled from 'styled-components';

const Button = styled.button`
  background-color: palevioletred;
  color: white;
  padding: 10px 15px;
  border: none;
  border-radius: 5px; 

  /* Dynamic Styles */
  ${(props) =>
    props.primary &&
    css`
      background-color: white;
      color: palevioletred;
    `} 
`;

👉 Use the Styled Component:

import Button from './Button'; // Assuming your styled component is named Button

// Inside your component:
<Button>Normal Button</Button>
<Button primary>Primary Button</Button> 
Observations:
  • CSS Modules:
    • Styles exist in a separate .css file.
    • Class names are auto-generated (e.g., styles.button) for local scoping.
  • Styled Components:
    • Styles are JavaScript!
    • Leverages props to control styling dynamically.
Let’s experiment!
  • Change styles: Adjust colors, add hover effects, etc. Observe how this is done differently in each approach.
  • Additional Components: Try creating another component with both methods to compare further.

Performance Optimization: Because Speed Matters

Excellent! Let’s continue on the mission to bring more performance out of your React applications.

1. Profiling for Bottlenecks

👉 React DevTools Profiler:

  • A built-in tool in the React Developer Tools browser extension.
  • Records component renders and lets you analyze a flame graph to spot expensive components.

👉 Browser Performance Tools:

  • Chrome, Firefox, and other browsers have excellent developer tools.
  • Analyze JavaScript execution time, network activity, and memory usage.
2. Memoization with React.memo and useMemo

👉 When to Use Them:

  • React.memo: For preventing a functional component from re-rendering if its props haven’t changed.
  • useMemo: To avoid re-computing expensive values unless any of its dependencies change.

👉 Trade-offs:

  • Extra memory overhead to store previous results.
  • Use judiciously; over-memoization can sometimes hinder performance.
3. Beyond the Basics

👉 useCallback: Memoizes callback functions passed as props to child components.

👉 Benefits:

  • Prevents child components from re-rendering unnecessarily if the prop callback itself is a dependency.
  • Useful for optimizing event handlers.

👉 Web Workers:

  • Create background threads for heavy work.
  • Use cases: Complex calculations, data processing, image manipulation.
  • Good to know: Communication with the main UI thread happens via message passing.

👉 Server-Side Rendering (SSR):

  • Render React components on the server into HTML, sending it to the browser.
  • Advantages: Faster initial load (perceived performance), better SEO.
  • Considerations: Setup complexity, potential impact on maintainability.
Important Considerations
Establish performance metrics before optimizing. Profile first to ensure you’re optimizing the right parts of your app. Sometimes, better algorithms offer more significant improvements than React-related optimizations alone.
Let’s put this into practice!

👉 Scenarios:

  1. A complex component that re-renders far too often due to parent prop changes.
    • Would React.memo be a viable solution in this case?
  2. A list that performs computationally intensive filtering of a large dataset on each keystroke.
    • How might useMemo or useCallback help?

👉 Experimentation:

  1. Use the React DevTools Profiler to find potential performance issues in an existing small project
  2. Set up a code example where we can demonstrate the before-and-after performance impact of these optimization techniques

References:

  1. https://github.com/jurgar/react-app ↩︎
  2. https://github.com/Sefaria/Sefaria-Mobile ↩︎
  3. https://github.com/alonsohs/clock-app ↩︎
  4. https://github.com/CondeNast/hackathon-10acious-frontend ↩︎
  5. https://github.com/AndryHolovchak/github-searcher ↩︎
  6. https://github.com/25LUKE/Routers ↩︎
  7. https://github.com/Supriya-Uppala/TodoList ↩︎
  8. https://bn.b-ok.com/book/5242235/20b89c ↩︎
  9. https://github.com/tsuyopon-xyz/redux_store_example ↩︎
  10. https://github.com/Aoshisen/React ↩︎
  11. https://github.com/2remote/yaopai-m ↩︎
  12. https://github.com/AyraStark870/redux-bases ↩︎
  13. https://github.com/saharabanu/redux-practise-projects ↩︎
  14. https://github.com/OneShiv/portal-style-new-window-poc ↩︎
  15. https://github.com/iamvishaldev/URJ-8 ↩︎
Avatar for Amit

Amit (7)

Hello! I'm Amit Kumar, a passionate teacher specializing in web development and digital marketing. With several years of experience in the industry, I am dedicated to sharing my knowledge and expertise to empower aspiring learners like you.

Teaching Philosophy:

I believe in creating a dynamic and engaging learning environment where students can thrive. My teaching approach focuses on hands-on experience, practical applications, and real-world examples. I strive to make complex concepts understandable and enjoyable, ensuring that you not only grasp the fundamentals but also develop the skills necessary to excel in web development and digital marketing.

Web Development Expertise:

As a web development instructor, I am well-versed in HTML, CSS, JavaScript, and various frameworks such as React and Angular. I will guide you through the process of building responsive and user-friendly websites, exploring topics like front-end development, back-end integration, database management, and more. Together, we will delve into the latest trends and best practices in web development, equipping you with the tools to create stunning online experiences.

Digital Marketing Mastery:

In the rapidly evolving digital landscape, digital marketing is crucial for businesses to thrive. Through my digital marketing courses, I will help you navigate the intricacies of search engine optimization (SEO), social media marketing, content strategy, email marketing, and analytics. Gain insights into effective marketing techniques, harness the power of data-driven decision-making, and learn how to craft compelling campaigns that drive results.

Passion for Teaching:

Teaching is not just a profession for me; it's my passion. I find great joy in witnessing my students grow and succeed. I am committed to fostering a supportive learning community where questions are encouraged, ideas are shared, and challenges are overcome together. I am here to guide you every step of the way and provide the necessary resources for your personal and professional development.

Join me on this exciting journey of learning and discovery. Let's unlock your potential in web development and digital marketing together. Enroll in my courses today and embark on a transformative educational experience that will propel your career forward.
Start Learning Today!

5 1 vote
Article Rating
Subscribe
Notify of

guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x

Discover more from Botboxai

Subscribe now to keep reading and get access to the full archive.

Continue reading

Scroll to Top