Common terms for a React and frontend development glossary

Last updated:
Posted:

Core Terms

JSX (JavaScript XML)

JSX is a syntax extension for JavaScript that lets you write HTML-like markup inside a JavaScript file.

JSX is not HTML, but it resembles it closely, making it easier to describe what the UI should look like.

Under the hood, JSX is transformed into JavaScript calls (e.g., React.createElement) by tools like Babel, so it can be interpreted by the JavaScript engine.

TYPESCRIPT
const element = <h1>JSX</h1>;

This JSX gets transformed into a function:

TYPESCRIPT
const element = React.createElement('h1', null, 'JSX');

The React.createElement function takes three main arguments:

  • Type: In this case, ‘h1’
  • Props or attributes: In this case, null because className or id wasn’t added
  • Children Here, it’s ‘JSX’

In laymen’s term, React.createElement builds an object (React element) that looks like:

TYPESCRIPT
const element = {
  type: 'h1',
  props: {
    children: 'JSX'
  }
};

This explains why multiple JSX tags need to be wrapped. You can’t return two objects from a function without wrapping them into an array.

Here are three rules of JSX taken from react.dev:

  1. Return a single root element

Use <div> </div> or <> </>. The empty tag is shorthand for <Fragment> </Fragment>.

Grouping elements with a Fragment has no effect on layout or styles.

Note that to pass key to a Fragment, you need to explicitly import Fragment from ‘react’ and render <Fragment key={key}>…</Fragment>.

  1. Close all the tags

JSX requires tags to be explicitly closed: self-closing tags like <img> must become <img />, and wrapping tags like <li>oranges must be written as <li>oranges</li>.

  1. camelCase most of the things

For instance, className.

JSX attributes inside quotes are passed as strings.

JSX is a special way of writing JavaScript, namely, it’s possible to use JavaScript inside it—with curly braces{ }.

Note that it only works as text directly inside a JSX tag or as attributes immediately following the = sign. src={avatar} will read the avatar variable, but src="{avatar}" will pass the string "{avatar}".

JSX allows React to separate concerns by containing both rendering logic and content in a component.

Component

React components are regular JavaScript functions except:

I’d like to think of it as a Lego brick, but a more complex and functional one. In a React app, every piece of UI is a component.

Note that Class components are still supported by React, but not recommended in new code.

A lot of concepts are highly cohesive here. They’ll come together eventually.

A file can have no more than one default export, but it can have as many named exports as you like.

1. Default Export:

• Use when exporting one main component.

• Import without curly braces and can rename freely.

2. Named Export:

• Use when exporting multiple components.

• Import with curly braces using the exact names.

There are two types of logic inside React components, which are,

Rendering code lives at the top level of your component. This is where you take the props and state, transform them, and return the JSX you want to see on the screen. Rendering code must be pure. Like a math formula, it should only calculate the result, but not do anything else.
Event handlers are nested functions inside your components that do things rather than just calculate them. An event handler might update an input field, submit an HTTP POST request to buy a product, or navigate the user to another screen. Event handlers contain “side effects” (they change the program’s state) caused by a specific user action (for example, a button click or typing).

Another thing that’s worth noticing is that,

React assumes that every component you write is a pure function. This means that React components you write must always return the same JSX given the same inputs.

A pure function is a function that 1) minds its own business and 2) returns the same result given the same inputs.

React’s rendering process must always be pure. Components should only return their JSX, and not change any objects or variables that existed before rendering.

Everything happens on the side instead of during rendering is called a side effect.

Props

Props are like parameters in a function, used to pass data from parent components to child components.

While HTML attributes are predefined (like class, id, src), props in React are more flexible and can accept any valid JavaScript value, such as strings, numbers, arrays, objects, functions, or even other components.

In a nutshell, props bring data into a component. They are read-only, static and immutable.

State

State is a component’s memory, namely, data that is local to a component and can change over time. When state changes, the component re-renders to reflect the new data.

State is mutable.

It is like a special variable in a React component, and it’s used when props aren’t enough to handle dynamic data that changes over time. Here’s a lengthy example:

TYPESCRIPT
"use client";
import { useEffect, useState } from "react";

interface CopyButtonProps {
    targetId: string;
    onCopy?: () => void;
}

const CopyButton: React.FC<CopyButtonProps> = ({ targetId, onCopy }) => {
    const [isClient, setIsClient] = useState(false);

    useEffect(() => {
        setIsClient(true);
    }, []);

    const handleCopy = () => {
        const codeBlock = document.getElementById(targetId);
        if (codeBlock) {
            navigator.clipboard.writeText(codeBlock.innerText).then(() => {
                if (onCopy) {
                    onCopy();
                }
            });
        }
    };

    if (!isClient) return null;

    return (
        <button
            onClick={handleCopy}
            className="px-3 py-1 font-press rounded-md text-xs transition-all duration-300 ease-in-out bg-gray-100 text-black border-2 border-gray-400 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-500"
        >
            COPY
        </button>
    );
};

export default CopyButton;
  1. State(isClient):
  • isClient is used as a state variable to track whether the component is being rendered on the client side.
  • Some browser APIs, like the navigator.clipboard here, are not available during server-side rendering.
  1. useState and useEffect:
  • useState(false) initializes isClient to false.
  • useEffect(() => { setIsClient(true); }, []); is used to update isClient to true when the component is mounted on the client side. This ensures that the copy functionality only runs in the browser.

Why useEffect? Because it runs after the component is mounted, ensuring the component knows it’s on the client side.

  1. Props:

Props (targetId and onCopy) are used to pass data from the parent component. However, props alone are not enough to handle changes in the component’s behavior, like knowing whether the component is rendering on the client or server.

That’s why state is used to track and manage this behavior.

To summarize it, props let me pass the target ID and optional callback, but state is necessary to handle the client-side-only behavior.

Virtual DOM

The virtual DOM is a lightweight copy of the actual DOM.

The process of re-rendering generates a new virtual DOM (Document Object Model) tree. The virtual DOM is a lightweight representation of the actual DOM that React uses to keep track of the current state of the UI. React then compares the new virtual DOM tree to the previous one and calculates the minimal set of changes needed to update the actual DOM. This is the reconciliation algorithm.

This is a deep rabbit hole. But the gist of it is whenever changes happen, React (or any other framework/libraries that use the virtual DOM approach) compares the new virtual DOM with the old one, and updates the real DOM with the actual differences.

By only updating what has changed, React minimizes the performance cost of manipulating the DOM.

Hooks

Hooks let you use different React features from your components. You can either use the built-in Hooks or combine them to build your own.

In layman’s terms, hooks are functions that allow you to “hook into” React features like state management and lifecycle methods in functional components.

Hooks were added to React in version 16.8. Because of them, class components are no longer needed and not recommended.

  • Before hooks:
TYPESCRIPT
import React, { Component } from "react";

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

  componentDidMount() {
    // Lifecycle method: Runs after the component is mounted
    console.log("Component mounted");
  }
  componentWillUnmount() {
    // Lifecycle method: Runs before the component is unmounted
    console.log("Component will unmount");
  }

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

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default Counter;
  • After hooks:
TYPESCRIPT
import React, { useState, useEffect } from "react";

const Counter = () => {
  const [count, setCount] = useState(0); // useState hook for state

  useEffect(() => {
    // useEffect hook for side effects
    console.log("Component mounted");

    return () => {
      console.log("Component will unmount"); // Cleanup function
    };
  }, []); // Empty dependency array: runs once on mount

  const increment = () => setCount(count + 1); // Update state

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default Counter;

Adiós, constructor! Lifecycle logic no longer spreads across methods.

Hooks also make it easy to share stateful logic between components using custom hooks without cumbersome patterns like higher-order components or render props.

Note that:

Hooks can only be called from the top level of a functional component.
Hooks can’t be called from inside loops or conditions.

Effect

Effects are an escape hatch from the React paradigm. They let you “step outside” of React and synchronize your components with some external system like a non-React widget, network, or the browser DOM. If there is no external system involved (for example, if you want to update a component’s state when some props or state change), you shouldn’t need an Effect. Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.
Effects let you specify side effects that are caused by rendering itself, rather than by a particular event.

In brief, side effects, or effects, are operations that synchronize the component with external systems or resources. React is primarily UI rendering focused, hence everything goes beyond rendering is considered a side effect.

Examples of Side Effects

  • Fetching Data from an API: Syncing a component with an external data source.
  • Setting Up Subscriptions: Listening to WebSocket events or setting up a timer.
  • Direct DOM Manipulation: Modifying the DOM outside of React’s control.
  • Logging or Analytics: Sending data to an external logging or analytics service.
  • Browser API Calls: Using APIs like localStorage, document, or window.

I’m putting something from the Odin Project here because I couldn’t have said it better myself.

The single question that you can ask yourself before you use an effect is if there are any such external systems that need to be synced with, apart from props or state. Unnecessary useEffect hooks are code-smell, error-prone, and cause unnecessary performance issues.

Context

Usually, you will pass information from a parent component to a child component via props. But passing props can become verbose and inconvenient if you have to pass them through many components in the middle, or if many components in your app need the same information. Context lets the parent component make some information available to any component in the tree below it—no matter how deep—without passing it explicitly through props.
Passing props can become verbose and inconvenient when you need to pass some prop deeply through the tree, or if many components need the same prop. The nearest common ancestor could be far removed from the components that need data, and lifting state up that high can lead to a situation called “prop drilling”.

In short, context is a way to pass data through the component tree without manually passing props at every level.

Higher-Order Component (HOC)

HOCs are functions that take a component and return a new component, adding additional behaviors or props.

They are not commonly used in modern React code. Most of the time, hooks are the better option. But in some use cases like withAuth or withErrorBoundary, they might come handy.

Render Props

A component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.

In other words, it’s a pattern where a component takes a function as a prop, which it uses to determine what to render. For instance,

TYPESCRIPT
<DataFetcher render={(data) => <div>{data}</div>} />

Render props are still used, but aren’t very common. Same as HOCs, most of the time hooks provide a more elegant solution.

React Features & Patterns

Key

Keys in React help identify which items in a list have changed, been added, or removed. They are used to optimize the rendering of lists by minimizing DOM operations.

Think about it, let’s say, you have a method that returns a list of elements, if any of them were to change, how would React know which one to update?

Apparently we have two options: 1) re-render the entire list; 2) catch the specific changed items and re-render them.

Option 2 is the reason why React needs keys.

Here’s part of my actual ToC:

TYPESCRIPT
{headings.map((heading, index) => (
    <li
      key={`${heading.id}-${index}`}
      data-id={heading.id}
      className={cn(
        "cursor-pointer transition-colors duration-200",
        activeId === heading.id
          ? "font-bold text-blue-600 dark:text-blue-400"
          : "text-gray-600 dark:text-gray-400",
        {
          "pl-4 text-sm": heading.level === 2,
          "pl-8 text-xs": heading.level === 3,
        }
      )}
      onClick={() => scrollToSection(heading.id)}
      aria-label={`Scroll to section ${heading.text}`}
    >
      {heading.text}
    </li>
  ));
}

Let’s ignore all the styles and focus on the logic. My ToC component here uses keys to identify each list item. Each <li> element in the map function has a key prop set to a combination of heading.id and index, ensuring uniqueness for each heading.

Another thing to keep in mind is that using index as part of the key is generally discouraged, especially if the list items can change order. However, combining heading.id with index is a practical way to ensure uniqueness when you have reliable IDs.

For instance, if my headings go like:

TYPESCRIPT
const headings = [
    { id: 'introduction', text: 'Introduction', level: 1 },
    { id: 'setup', text: 'Setup', level: 1 },
    { id: 'usage', text: 'Usage', level: 1 },
];

And I make the smooth-brained decision to use index as the key,

TYPESCRIPT
{headings.map((heading, index) => (
    <li key={index}>{heading.text}</li>
))}

Namely, I have: Introduction (key=0), Setup (key=1), Usage (key=2)

And if I add a new section between “Introduction” and “Setup”, say, “Prerequisites”, the keys would change into:

Introduction (key=0), Prerequisites (key=1), Setup (key=2), Usage (key=3)

React will treat this new “Prerequisites” as if it’s the existing “Setup”. It might apply styles or classes to the wrong headings.

The problems with using the index as a key can get even more ugly when dealing with client-server interactions.

Lifecycle methods in class components

There are three stages to a component’s life: mounting, updating and unmounting. Lifecycle methods are special methods in class components that run at different stages of a component’s existence.

They are becoming less and less relevant for new projects but are still necessary for maintaining legacy class components.

Suspense

Suspense is a built-in React component that allows you to pause rendering while waiting for some asynchronous data to be loaded.

It provides a way to show a fallback UI (like a loading spinner or a skeleton) until the asynchronous operation is complete.

Controlled component

A controlled component is an input element whose value is controlled by React state, making it easier to validate, manipulate, or update based on user interactions.

Uncontrolled component

An uncontrolled component is an input element that manages its own state internally using the DOM.

Error boundary

An error boundary is a special component that lets you display some fallback UI instead of the part that crashed—for example, an error message.

There is currently no way to write an error boundary as a function component. However, react-error-boundary is out there waiting.

CSS & Styling

Responsive design

Responsive web design (RWD) is a web design approach to make web pages render well on all screen sizes and resolutions while ensuring good usability. It is the way to design for a multi-device web.

CSS-in-JS

CSS-in-JS is a styling approach where styles are written in JavaScript rather than in separate CSS files. While it’s great for dynamic and theme-based styling, it can be verbose and hard to manage for simple static styles.

I have this interesting feeling that CSS-in-JS resembles some aspects of Vue. My reasons are: 1) both Vue and CSS-in-JS provide  make styles local to the component and prevent them from leaking into the global scope; 2) while not technically single-file like Vue, CSS-in-JS encourages the practice of co-locating styles with the component logic; 3) both ecosystems allow for easy integration of dynamic styling based on the component’s data or props.

Here’s an extremely simple snippet to take a look at how it looks like:

TYPESCRIPT
import styled from 'styled-components';

const Button = styled.div`
  background-color: blue;
  color: white;
`;

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

CSS Modules

CSS Module is a CSS file where all class names and animation names are scoped locally by default. 

Suitable for small to medium projects. It comes handy regarding flexibility, but falls short when it comes to conditional styling and global styles.

Performance Optimization

Memoization

Memoization is an optimization technique used primarily to improve the performance of functions by caching the results of expensive function calls and returning the cached result when the same inputs occur again.

Use case in React:

  • React.memo: A higher-order component that memoizes the result of a functional component, preventing unnecessary re-renders.
  • useMemo Hook: Caches the result of a calculation between renders.
  • useCallback Hook: Memoizes a function so it doesn’t get recreated on every render.

Code splitting

Code splitting is the practice of splitting the code a web application depends on — including its own code and any third-party dependencies — into separate bundles that can be loaded independently of each other. This allows an application to load only the code it actually needs at a given point in time, and load other bundles on demand. This approach is used to improve application performance, especially on initial load.

It can be done automatically with tools like Webpack or manually using dynamic imports.

Use case in React:

  • React.lazy( )
  • Suspense

Throttling and debouncing

Throttling is a technique used to slow down a process such that an operation can only be performed at a certain rate.

Debouncing is a technique used to ensure a function is executed after a certain delay from the last time it was invoked.

Debouncing is very similar to throttling. The key difference is that throttling enforces limits on continuous operations, while debouncing waits for invocations to stop for a specific time to consolidate many noisy invocations into one single invocation.

Throttling does magic for events like scrolling or resizing, while debouncing works well for events like input fields.

Take another part out of my ToC component for example,

TYPESCRIPT
useEffect(() => {
    if (!isLargeViewport) return;

    const createdAtElement = document.getElementById('createdAt');
    const handleScroll = throttle(() => {
        if (createdAtElement) {
            const rect = createdAtElement.getBoundingClientRect();
            setIsVisible(rect.top <= 0);
        }
    }, 100); // Throttling the scroll event

    window.addEventListener('scroll', handleScroll);
    handleScroll(); // Run the handler immediately to check visibility

    return () => window.removeEventListener('scroll', handleScroll);
}, [isLargeViewport]);

if (!isLargeViewport) return null;
  1. Checks Visibility: The handleScroll function calculates the position of the createdAtElement and updates the isVisible state if the element’s top position is less than or equal to 0 (i.e., it has scrolled out of view).
  1. Throttle Implementation: The throttle function limits how often handleScroll can run, ensuring that updates are not overly frequent.
  1. Cleanup: The event listener is removed when the component unmounts or when isLargeViewport changes, preventing memory leaks. (Because I intend to make ToC visible in large viewports only.)

Throttling ensures that handleScroll only runs once every 100 milliseconds, reducing the number of calculations and updates to the DOM.

A good use case example for debouncing is implementing real-time search suggestions. which I’ve considered, but eventually decided that I want the search to be triggered only when the user explicitly submits it. Because I want to minimize unnecessary operations or API calls.

Here’s a key snippet for debouncing search with excessive comments:

TYPESCRIPT
import { useState, useEffect } from "react";
import { debounce } from "lodash"; // Import lodash debounce

const [query, setQuery] = useState(initialQuery);
const [debouncedQuery, setDebouncedQuery] = useState(initialQuery);

// Debounce the search function
const debouncedSearch = debounce((value) => {
  setDebouncedQuery(value); // Update the debounced query
}, 300); // Delay of 300 milliseconds

// Handle input change and trigger debounced search
const handleQueryChange = (e) => {
  const value = e.target.value;
  setQuery(value); // Update the query immediately
  debouncedSearch(value); // Trigger the debounced search
};

// UseEffect to perform the search when debouncedQuery changes
useEffect(() => {
  if (debouncedQuery) {
    // Perform search logic or API call here
    console.log(`Searching for: ${debouncedQuery}`);
  }
}, [debouncedQuery]);

The search is triggered automatically after the user stops typing for 300 milliseconds, which is useful for real-time search features.

Modern JavaScript and Frontend Concepts

Arrow function

  • Arrow Functions: A shorter syntax for writing functions that automatically inherit this from the surrounding lexical scope.
  • Key Differences:

No this binding of their own.

No arguments object.

Cannot be used as constructors.

  • Best Use Cases: Callbacks, methods in classes, functional components in React, and when avoiding this confusion.

Template literal

Template literals are literals delimited with backtick (``) characters, allowing for multi-line stringsstring interpolation with embedded expressions, and special constructs called tagged templates.
TYPESCRIPT
// Variables for demonstration
const name = 'Dummy';
const age = 5;
const hobby = 'eatting';

// 1. String Interpolation
const introduction = `Hi, my name is ${name}, and I am ${age} years old.`;
console.log(introduction);
// Output: Hi, my name is Dummy, and I am 5 years old.

// 2. Multi-line Strings
const bio = `I love ${hobby}.
It's something I enjoy doing in my entire life,
especially when my human puts me on a diet.`;
console.log(bio);
// Output:
// I love eatting.
// It's something I enjoy doing in my entire life,
// especially when my human puts me on a diet.

// 3. Embedded Expressions
const nextYearAge = `Next year, I will be ${age + 1} years old.`;
console.log(nextYearAge);
// Output: Next year, I will be 6 years old.

Destructuring

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

To elaborate on it with less fancy words,

TYPESCRIPT
// 1. Array Destructuring
const animals = ['cat', 'dog', 'tanuki'];
const [firstAnimal, , thirdAnimal] = animals;
console.log(firstAnimal); // "cat"
console.log(thirdAnimal); // "tanuki"

// 2. Object Destructuring with Renaming and Defaults
const cat = { brand: 'Domestic Shorthair', model: 'SIC' };
const { brand, model, year = 2019 } = cat;
console.log(brand); // "Domestic Shorthair"
console.log(model); // "SIC"
console.log(year); // 2019 (default value)

// 3. Destructuring in Function Parameters
const cat = { name: 'Dummy', age: 5 };
function greet({ name, age }) {
  console.log(`Hi, ${name}. You are ${age} years old.`);
}
greet(cat); // "Hi, Dummy. You are 5 years old."

Promise

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

It’s a powerful way to handle asynchronous operations, like fetching data from an API or reading a file.

States: pending, fulfilled, rejected.

Methods:

  • .then(): Runs on success.
  • .catch(): Runs on error.
  • .finally(): Runs in both cases.

Use Cases: Fetching data from an API, reading files, or any operation that takes time.

A promise is said to be settled if it is either fulfilled or rejected, but not pending.

See the example in the Async/Await part.

Async/Await

Async keyword: when declaring a function with async, it automatically returns a Promise. The result of the async function is wrapped in a Promise that resolves to the value you return from the function. If an error is thrown inside an async function, the returned Promise is rejected with that error.

Await keyword: pauses the execution of the async function until the Promise settles (is either resolved or rejected). It allows you to write asynchronous code that looks and behaves more like synchronous code.

In layman’s terms,

  • async: Marks a function as asynchronous, telling JavaScript, “This function will have some tasks that take time, so be prepared to handle them differently.”
  • await: Pauses the execution of the function until the task.

Here’s a another snippet of mine,

TYPESCRIPT
export const getAllPosts = async (): Promise<Post[]> => {
  const databaseId = BLOG_DATABASE_ID;
  if (!databaseId)
    throw new Error(
      "Notion BLOG database ID is undefined. Check .env variables.",
    );

  try {
    const response = await notion.databases.query({
      database_id: databaseId,
      filter: {
        property: "Public",
        checkbox: { equals: true },
      },
      sorts: [{ property: "Created At", direction: "descending" }],
    });
    const posts = await Promise.all(response.results.map(mapNotionPageToPost));
    return posts;
  } catch (error) {
    console.error("Error fetching post from Notion:", error);
    return [];
  }
};
  1. await is used to pause the execution of getAllPosts until notion.databases.query resolves.
  1. Promise.all is used to execute all mapNotionPageToPost calls in parallel. Why Promise.all? If any of the Promises are rejected, Promise.all will reject with that error.
  1. If any part of the try block throws an error (either due to a rejected Promise or another issue), the try…catch block handles it.
  1. Returning an empty array [] ensures that the application can handle the failure without crashing or throwing an unhandled exception.

Think of async/await as having a helper who waits for tasks to finish so that your instructions are simpler and easier to follow. You don’t have to worry about juggling multiple timers or callbacks; everything is handled in a straightforward, step-by-step way.

Spread and rest operators

Spread and rest have exactly the same syntax (). They serve different purposes based on how they’re used.

  • Spread Operator (): Expands elements of an array or object.

Use Cases: Copying arrays/objects, merging arrays/objects, spreading elements as function arguments.

  • Rest Operator (): Collects multiple elements into an array or object.

Use Cases: Handling variable number of function arguments, array/object destructuring.

TYPESCRIPT
// 1. Using Spread Operator with Arrays
const cats = ['Whiskers', 'Shadow', 'Mittens'];
const moreCats = [...cats, 'Fluffy', 'Butter'];

console.log('All Cats:', moreCats);
// Output: All Cats: ["Whiskers", "Shadow", "Mittens", "Fluffy", "Butter"]

// 2. Using Spread Operator to Pass Arguments to a Function
function introduceCats(cat1, cat2, cat3) {
  console.log(`Meet my cats: ${cat1}, ${cat2}, and ${cat3}!`);
}

const catNames = ['Luna', 'Simba', 'Oscar'];
introduceCats(...catNames);
// Output: Meet my cats: Luna, Simba, and Oscar!

// 3. Using Rest Operator with Arrays to Collect the Remaining Cats
const allCats = ['Bella', 'Chloe', 'Loki', 'Nala', 'Simba'];
const [firstCat, secondCat, ...otherCats] = allCats;

console.log('First Cat:', firstCat); // Output: First Cat: Bella
console.log('Second Cat:', secondCat); // Output: Second Cat: Chloe
console.log('Other Cats:', otherCats); // Output: Other Cats: ["Loki", "Nala", "Simba"]

// 4. Using Spread Operator with Objects to Copy and Expand
const catDetails = { name: 'Mochi', age: 3 };
const updatedCatDetails = { ...catDetails, color: 'white' };

console.log('Updated Cat Details:', updatedCatDetails);
// Output: Updated Cat Details: { name: "Mochi", age: 3, color: "white" }

// 5. Combining Cat Objects with Spread
const cat1 = { name: 'Oreo', breed: 'Shorthair' };
const cat2 = { age: 2, favoriteToy: 'Feather wand' };

const combinedCat = { ...cat1, ...cat2 };
console.log('Combined Cat:', combinedCat);
// Output: Combined Cat: { name: "Oreo", breed: "Shorthair", age: 2, favoriteToy: "Feather wand" }

// 6. Using Rest Operator with Objects to Extract and Collect Properties
const catProfile = { name: 'Ziggy', age: 5, breed: 'Longhair', color: 'gray' };
const { name, ...otherDetails } = catProfile;

console.log('Cat Name:', name); // Output: Cat Name: Ziggy
console.log('Other Cat Details:', otherDetails);
// Output: Other Cat Details: { age: 5, breed: "Longhair", color: "gray" }

Nope, I don’t have multiple cats.

Router

In the context of frontend development, a router is a component or library that handles the navigation and routing of an application, allowing users to move between different pages or views without reloading the entire page. It controls the URL and determines what content to display based on the URL path.

This is another rabbit hole, I’ll just leave the basic concept here.

Server-Side Rendering (SSR)

Server-Side Rendering (SSR) is a method of rendering a web page on the server rather than on the client. The server generates the complete HTML content for a page and sends it to the client, which means the page is fully rendered when it arrives in the browser.

Hydration

Hydration is the process where a server-rendered HTML page becomes fully interactive in the browser. During hydration, JavaScript code runs on the client side to “attach” event listeners and make the page dynamic.

In brief, rendering builds the component, and hydration makes them functional.

The biggest challenge in hydration is hydration mismatch, which occurs when the HTML generated on the server doesn’t match what the client-side JavaScript expects.

Common Causes of Hydration Mismatch

  • Non-Deterministic Rendering: If a server-rendered output depends on things that vary between server and client, like Date, window, document, or browser-specific features, it may end up rendering different HTML.
  • Async Data Fetching: If data fetched on the client differs from what was used to render on the server, the content may not align.
  • Conditional Rendering: Differences in rendering logic, like content that depends on browser state (e.g., screen size), can lead to mismatches.
  • Use of Random Values: Generating random numbers or IDs on both the server and client can lead to differences. (I learnt this the hard way when I was building my ToC.)

I had a hell of an experience with this particular topic. I’ll need to elaborate on it anytime soon.

Reference

  1. The Odin Project: https://www.theodinproject.com/paths/full-stack-javascript
  1. React docs: https://react.dev/learn
  1. MDN web docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript
  1. CSS Modules: https://github.com/css-modules/css-modules
  1. Next.js docs: https://nextjs.org/docs/app
  1. Free React 101 course on Scrimba: https://v1.scrimba.com/learn/learnreact