Post

Getting Started with React.js: A Beginner's Guide

Getting Started with React and TypeScript

React and TypeScript are a powerful combination for building robust and scalable web applications. In this guide, we’ll walk through the steps to get started with React using TypeScript, ensuring a more structured and type-safe development experience.

summary and extension of https://www.youtube.com/watch?v=SqcY0GlETPk

Prerequisites

Before diving into React with TypeScript, make sure you have the following prerequisites installed on your machine:

  • Node.js and npm: React applications are typically built using npm, the Node.js package manager. You can download and install Node.js from nodejs.org.

  • Create React App: We’ll use Create React App to set up a new React project quickly. Install it globally by running:

    1
    
    npm install -g create-react-app
    

Creating a New React App with TypeScript

Now that you have the prerequisites installed, let’s create a new React app with TypeScript.

1
2
npx create-react-app my-react-app --template typescript
cd my-react-app

This will generate a new React app with TypeScript configuration included.

Project Structure

The generated project structure will look like this:

1
2
3
4
5
6
7
8
9
my-react-app/
|-- node_modules/
|-- public/
|-- src/
|   |-- App.tsx
|   |-- index.tsx
|-- package.json
|-- tsconfig.json
|-- README.md
  • node_modules/: Contains project dependencies.
  • public/: Static assets and the HTML file where your app is rendered.
  • src/: Contains the source code of your React application.
  • package.json: Configuration file for npm packages and scripts.
  • tsconfig.json: TypeScript configuration file.
  • README.md: Markdown file containing information about your project.

Writing Your First Component

Open src/App.tsx in your preferred code editor and replace its content with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
// src/App.tsx
import React from 'react';

const App: React.FC = () => {
  return (
    <div>
      <h1>Hello, React with TypeScript!</h1>
    </div>
  );
};

export default App;

Running the App

To see your React app in action, run the following commands:

1
npm start

This will start the development server, and you can view your app at http://localhost:3000 in your web browser.

TypeScript in Action

One of the main benefits of using TypeScript with React is the ability to define types for your components. Let’s update our App.tsx file to demonstrate this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// src/App.tsx
import React from 'react';

interface AppProps {
  name: string;
}

const App: React.FC<AppProps> = ({ name }) => {
  return (
    <div>
      <h1>Hello, {name}!</h1>
    </div>
  );
};

export default App;

Now, the App component expects a prop named name of type string.

Conclusion

Congratulations! You’ve successfully created a new React app with TypeScript, written your first component, and seen TypeScript’s type-checking in action. This is just the beginning of your journey with React and TypeScript, and you can explore more features and best practices as you continue building your applications. Happy coding!

Understanding export default in JavaScript and TypeScript

When working with modules in JavaScript and TypeScript, the export default statement plays a crucial role in simplifying the import syntax and providing a convenient way to export a single value, function, or object as the default export from a module.

Basics of export default

Consider the following example in a JavaScript or TypeScript file:

1
2
3
4
5
6
7
// MyModule.js or MyModule.ts

const myValue = 42;

// Other module code...

export default myValue;

In this example, myValue is exported as the default export from the module. When another module wants to import this value, it can do so without needing curly braces:

1
2
3
4
5
// AnotherModule.js or AnotherModule.ts

import myValue from './MyModule';

console.log(myValue); // Output: 42

Notice that there are no curly braces around myValue during the import. This syntax is possible because myValue is the default export from the MyModule module.

Demystifying Default and Named Imports in TypeScript

Understanding import declarations in TypeScript is crucial for maintaining a clean and consistent codebase. The TypeScript specification, particularly in §11.3.2 Import Declarations, sheds light on the nuances between default and named imports.

Default Import without Curly Braces

The syntax:

1
import d from "mod";

is equivalent to:

1
import { default as d } from "mod";

This equivalence emphasizes that the default import (d in this case) is treated as an alias for the default export of the module. This form is appropriate when importing the sole entity exported with export default. Only one default export is allowed per module.

Named Imports with Curly Braces

Contrastingly, when importing anything other than the default export, even if it’s just a single entity, you should use curly braces:

1
import { namedExport } from "mod";

This convention ensures clarity and consistency in the import statements, making it evident that the imported entity is not the default export.

Practical Example: Calculator Functions

Consider the following example with a module named Calculator:

1
2
3
4
5
6
7
// Calculator.ts

export default function plus() { }    // Default export 

export function minus() { }           // Named export

export function multiply() { }        // Named export

Default Import: No Curly Braces

1
2
3
// CalculatorTest.ts

import plus from "./Calculator";

In this scenario, plus is the default import, and it should not be enclosed in curly braces.

Named Imports: With Curly Braces

1
2
3
// CalculatorTest.ts

import plus, { minus, multiply } from "./Calculator";

Here, minus and multiply are named imports and need to be within curly braces.

Alias for Default Import

You can provide an alias for the default import with/without curly braces:

1
2
3
// CalculatorTest.ts

import { default as add, minus, multiply } from "./Calculator";

OR

1
2
3
// CalculatorTest.ts

import add, { minus, multiply } from './Calculator';

In this case, the plus() function becomes add(), demonstrating the flexibility provided by TypeScript.

Understanding and adhering to these conventions enhances code readability and prevents potential confusion when working with default and named imports in TypeScript. Refer to the TypeScript handbook’s “Default exports” section for additional examples and insights. Happy coding!

Exporting Functions or Objects as Default

The export default syntax is not limited to exporting simple values; it can also be used to export functions or objects:

1
2
3
4
5
6
7
// MyModule.js or MyModule.ts

const myFunction = () => {
  // Function logic...
};

export default myFunction;
1
2
3
4
5
// AnotherModule.js or AnotherModule.ts

import myFunction from './MyModule';

myFunction(); // Call the exported function

Similarly, you can export an object as the default:

1
2
3
4
5
6
7
8
// MyModule.js or MyModule.ts

const myObject = {
  key: 'value',
  // Other object properties...
};

export default myObject;
1
2
3
4
5
// AnotherModule.js or AnotherModule.ts

import myObject from './MyModule';

console.log(myObject.key); // Output: 'value'

Combining Default and Named Exports

It’s worth noting that a module can have both a default export and named exports. For example:

1
2
3
4
5
6
7
8
9
// MyModule.js or MyModule.ts

const myValue = 42;

const myFunction = () => {
  // Function logic...
};

export { myValue, myFunction as default };

In this case, you can import the default export and named exports separately:

1
2
3
4
5
6
// AnotherModule.js or AnotherModule.ts

import myFunction, { myValue } from './MyModule';

console.log(myValue); // Output: 42
myFunction(); // Call the exported function

Conclusion

Understanding export default is fundamental when working with JavaScript and TypeScript modules. It provides a clean and concise way to export a single value or functionality from a module, simplifying the import syntax for other modules. Whether exporting values, functions, or objects, export default contributes to a more modular and readable codebase.

Styling React Components: A Comprehensive Guide

When it comes to styling React components, developers have a variety of options to choose from. In this article, we’ll explore different approaches to styling in React, ranging from traditional methods like Vanilla CSS to more modern solutions like CSS-in-JS.

Vanilla CSS

The most straightforward way to style React components is by using Vanilla CSS. You can create a separate CSS file and import it into your components. This approach is simple and follows the traditional way of styling web applications.

1
2
3
4
5
6
7
8
9
10
11
12
13
// MyComponent.jsx
import React from 'react';
import './MyComponent.css'; // Importing the CSS file

const MyComponent = () => {
  return (
    <div className="my-component">
      <h1>Hello, Styling with Vanilla CSS!</h1>
    </div>
  );
};

export default MyComponent;

CSS Modules

CSS Modules provide a way to locally scope your CSS class names, preventing naming collisions. Each component gets its unique class names, enhancing encapsulation and making styles more maintainable.

1
2
3
4
// MyComponent.module.css
.myComponent {
  /* styles go here */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// MyComponent.jsx
import React from 'react';
import styles from './MyComponent.module.css'; // Importing CSS Modules

const MyComponent = () => {
  return (
    <div className={styles.myComponent}>
      <h1>Hello, Styling with CSS Modules!</h1>
    </div>
  );
};

export default MyComponent;

CSS-in-JS

CSS-in-JS libraries, like Styled Components or Emotion, allow you to write CSS directly in your JavaScript or TypeScript files. This approach promotes the co-location of styles with components, making it easier to manage and maintain.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MyComponent.jsx
import React from 'react';
import styled from 'styled-components';

const StyledComponent = styled.div`
  /* styles go here */
`;

const MyComponent = () => {
  return (
    <StyledComponent>
      <h1>Hello, Styling with Styled Components!</h1>
    </StyledComponent>
  );
};

export default MyComponent;

Separation of Concerns

It’s crucial to maintain a clear separation of concerns when styling React components. Keep your JavaScript/TypeScript code focused on functionality, and delegate styling to separate files or modules. This practice enhances code readability and maintainability.

Inline Styles

React supports inline styles using the style attribute. While not always the most preferred method, inline styles can be useful for dynamic styling based on component state or props.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MyComponent.jsx
import React from 'react';

const MyComponent = ({ isImportant }) => {
  const dynamicStyle = {
    color: isImportant ? 'red' : 'black',
  };

  return (
    <div style={dynamicStyle}>
      <h1>Hello, Inline Styles!</h1>
    </div>
  );
};

export default MyComponent;

To expedite development and maintain a consistent look and feel, consider using popular UI libraries like Material-UI, Ant Design, or Chakra UI. These libraries provide pre-designed components and styles that can be easily integrated into your React application.

Adding Icons

Enhance your React components by incorporating icons. Libraries like React Icons or SVG icons from FontAwesome can be seamlessly integrated into your application, providing an aesthetically pleasing user interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// MyComponent.jsx
import React from 'react';
import { FaReact } from 'react-icons/fa';

const MyComponent = () => {
  return (
    <div>
      <h1>Hello, React with Icons!</h1>
      <FaReact />
    </div>
  );
};

export default MyComponent;

In conclusion, styling React components involves various approaches, each with its advantages and use cases. Whether you prefer the simplicity of Vanilla CSS, the encapsulation of CSS Modules, or the power of CSS-in-JS libraries, choosing the right styling method depends on the requirements and complexity of your project. Additionally, adopting popular UI libraries and incorporating icons can significantly enhance the visual appeal and user experience of your React application.

Managing Component State in React

Introduction

State management is a fundamental aspect of React development, allowing components to handle dynamic data and respond to user interactions. In this article, we’ll explore the essentials of managing component state in React, focusing on the State Hook and best practices for maintaining a predictable and efficient state management system.

Understanding the State Hook

Introduced in React 16.8, the State Hook (useState) provides a simple and intuitive way to manage state in functional components. With the State Hook, you can add local state to your functional components, eliminating the need for class components to handle state.

1
2
3
4
5
6
7
8
9
10
11
12
import React, { useState } from 'react';

const Counter = () => {
  const [count, setCount] = useState(0);

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

In this example, count is the state variable, and setCount is the function to update the state. The initial state value is provided as an argument to useState.

Choosing the State Structure

When managing state, it’s essential to choose a structure that suits the complexity of your application. For simple cases, a single state variable may be sufficient. However, for more complex scenarios, you might consider using an object or multiple state variables.

1
const [user, setUser] = useState({ name: '', age: 0 });

By using an object, you can manage multiple pieces of state within a single variable, enhancing organization and maintainability.

Keeping Components Pure

To maintain component purity and prevent side effects, it’s crucial to avoid directly mutating the state. Always use the provided state-setting function to update the state, especially when dealing with asynchronous operations.

1
2
3
4
5
// Incorrect
setCount(count + 1);

// Correct
setCount((prevCount) => prevCount + 1);

By using the functional update form, you ensure that the state update is based on the previous state, preventing race conditions and unexpected behavior.

Understanding the Strict Mode

React’s Strict Mode (<React.StrictMode>) is a development mode feature that helps catch common mistakes and improve the overall quality of your application. When enabled, Strict Mode performs additional checks and warnings, highlighting potential issues such as unsafe lifecycle methods and legacy context API usage.

To enable Strict Mode, wrap your application or a specific component with <React.StrictMode> in your index.js or App.js:

1
2
3
4
5
6
7
8
9
10
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

Strict Mode is especially valuable during development, aiding in the identification of problematic patterns and encouraging best practices. However, keep in mind that it may log warnings to the console in development that won’t appear in production builds.

Conclusion

Effectively managing state is crucial for building robust and maintainable React applications. The State Hook simplifies state management in functional components, allowing for concise and readable code. By choosing an appropriate state structure, keeping components pure, and leveraging Strict Mode during development, you can enhance the reliability and performance of your React application. Happy coding!

Mastering State Updates in React

State updates are a fundamental aspect of React development, and understanding how to efficiently update objects, nested objects, arrays, and arrays of objects is crucial for building robust applications. In this article, we’ll explore various techniques for updating state in React, including simplifying the update logic with Immer and sharing state between components.

Updating Objects

Updating state in React often involves modifying properties of objects. Here’s an example of how to update an object in a functional component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { useState } from 'react';

const ObjectUpdateExample = () => {
  const [user, setUser] = useState({ name: 'John', age: 25 });

  const updateName = () => {
    setUser((prevUser) => ({ ...prevUser, name: 'Jane' }));
  };

  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <button onClick={updateName}>Update Name</button>
    </div>
  );
};

In this example, the updateName function uses the spread operator (...) to create a new object with the updated name property.

Updating Nested Objects

When dealing with nested objects, it’s essential to maintain immutability to prevent unintended side effects. Here’s an example of updating a nested object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const NestedObjectUpdateExample = () => {
  const [person, setPerson] = useState({ info: { name: 'John', age: 25 } });

  const updateName = () => {
    setPerson((prevPerson) => ({
      ...prevPerson,
      info: { ...prevPerson.info, name: 'Jane' }
    }));
  };

  return (
    <div>
      <p>Name: {person.info.name}, Age: {person.info.age}</p>
      <button onClick={updateName}>Update Name</button>
    </div>
  );
};

In this case, both the outer object (person) and the nested object (info) are cloned to ensure immutability.

Updating Arrays

Updating arrays involves creating a new array with the desired modifications. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ArrayUpdateExample = () => {
  const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);

  const doubleNumbers = () => {
    setNumbers((prevNumbers) => prevNumbers.map((num) => num * 2));
  };

  return (
    <div>
      <p>Numbers: {numbers.join(', ')}</p>
      <button onClick={doubleNumbers}>Double Numbers</button>
    </div>
  );
};

In this example, the doubleNumbers function uses the map method to create a new array where each number is doubled.

Updating Array of Objects

When dealing with an array of objects, maintaining immutability is crucial. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const ArrayOfObjectsUpdateExample = () => {
  const [people, setPeople] = useState([
    { name: 'John', age: 25 },
    { name: 'Jane', age: 30 },
    { name: 'Bob', age: 22 },
  ]);

  const updateAge = () => {
    setPeople((prevPeople) =>
      prevPeople.map((person) => ({ ...person, age: person.age + 1 }))
    );
  };

  return (
    <div>
      <ul>
        {people.map((person, index) => (
          <li key={index}>
            Name: {person.name}, Age: {person.age}
          </li>
        ))}
      </ul>
      <button onClick={updateAge}>Update Ages</button>
    </div>
  );
};

In this example, the updateAge function creates a new array where each person’s age is incremented.

Simplifying Update Logic with Immer

Immer is a library that simplifies the process of updating immutable data structures. It allows you to write code that looks like you’re modifying the state directly, but it automatically produces a new immutable state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import produce from 'immer';

const ImmerExample = () => {
  const [user, setUser] = useState({ name: 'John', age: 25 });

  const updateName = () => {
    setUser(
      produce((draftUser) => {
        draftUser.name = 'Jane';
      })
    );
  };

  return (
    <div>
      <p>Name: {user.name}, Age: {user.age}</p>
      <button onClick={updateName}>Update Name</button>
    </div>
  );
};

In this example, the produce function from Immer allows you to directly modify a draft state, and Immer takes care of producing the new immutable state.

Sharing State between Components

Sharing state between components is a common requirement in React applications. One way to achieve this is by lifting state up to a common ancestor component and passing the state and updating functions as props.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ParentComponent = () => {
  const [sharedState, setSharedState] = useState('Shared State');

  return (
    <div>
      <ChildComponent sharedState={sharedState} setSharedState={setSharedState} />
    </div>
  );
};

const ChildComponent = ({ sharedState, setSharedState }) => {
  return (
    <div>
      <p>Shared State: {sharedState}</p>
      <button onClick={() => setSharedState('Updated State')}>Update State</button>
    </div>
  );
};

In this example, the ParentComponent owns the state, and it passes both the state (sharedState) and the state updater function (setSharedState) as props to the ChildComponent.

Conclusion

Effectively updating state in React is a critical skill for building dynamic and responsive applications. Understanding how to update objects, nested objects, arrays, and arrays of objects, along with leveraging libraries like Immer, can significantly enhance your development experience. Additionally, sharing state between components provides a

Mastering Form Handling in React

Building and handling forms are fundamental tasks in web development, and React provides powerful tools and patterns to streamline this process. In this article, we’ll explore the process of building a form, handling form submission, accessing input fields, and leveraging controlled components. We’ll also delve into the benefits of using React Hook Form for efficient form management.

Building a Form

Creating a basic form in React involves using HTML form elements and React state to manage form data. Here’s a simple example of a login form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import React, { useState } from 'react';

const LoginForm = () => {
  const [formData, setFormData] = useState({
    username: '',
    password: '',
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData((prevData) => ({ ...prevData, [name]: value }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    // Perform login logic with formData
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" name="username" value={formData.username} onChange={handleChange} />
      </label>
      <label>
        Password:
        <input type="password" name="password" value={formData.password} onChange={handleChange} />
      </label>
      <button type="submit">Login</button>
    </form>
  );
};

In this example, the form has two input fields for username and password. The handleChange function updates the form state as the user types, and the handleSubmit function prevents the default form submission and handles the login logic.

Handling Form Submission

Handling form submission typically involves preventing the default behavior of the form and then performing the necessary actions, such as making an API request or updating the application state. The onSubmit event is crucial for capturing the form submission.

1
2
3
4
const handleSubmit = (e) => {
  e.preventDefault();
  // Perform form submission logic
};

You can then call the handleSubmit function when the form is submitted, either by clicking a submit button or pressing Enter inside an input field.

Accessing Input Fields

Accessing input field values in React is done through controlled components. A controlled component ties the value of the input field to a piece of state and updates that state as the user interacts with the input.

1
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} />

In this example, value is the state variable, and the onChange event updates the state as the user types.

Controlled Components

Controlled components are a crucial concept in React form handling. In a controlled component, the form data is handled by the React component’s state. This ensures that React is the single source of truth for the form data.

1
2
3
4
5
const [username, setUsername] = useState('');

// ...

<input type="text" value={username} onChange={(e) => setUsername(e.target.value)} />

Here, username is a state variable, and the input field is controlled by React. Any changes to the input field will update the state, and any changes to the state will update the input field.

Managing Forms with React Hook Form

React Hook Form is a powerful library that simplifies and enhances form management in React applications. It offers a simple and declarative way to work with forms, reducing the amount of boilerplate code.

Installing React Hook Form

To use React Hook Form, first install it in your project:

1
npm install react-hook-form

Basic Usage

Here’s a simple example of using React Hook Form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';
import { useForm } from 'react-hook-form';

const HookFormExample = () => {
  const { register, handleSubmit } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        Username:
        <input type="text" {...register('username')} />
      </label>
      <label>
        Password:
        <input type="password" {...register('password')} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, useForm is a hook provided by React Hook Form that initializes the form state and provides methods like register and handleSubmit. The onSubmit function will receive the form data when the form is submitted.

React Hook Form offers additional features such as input validation, error handling, and more, making it a valuable tool for managing forms in React applications.

Conclusion

Building and handling forms in React involves a combination of HTML form elements, React state, and event handling. Understanding controlled components and leveraging libraries like React Hook Form can significantly streamline the process and enhance the maintainability of your code. Whether you choose to manage forms manually or utilize specialized libraries, mastering form handling is essential for creating dynamic and user-friendly React applications.

The {...register('fieldName')} syntax you see in the example code using React Hook Form is a technique in JavaScript called “object spreading” or “object destructuring.” Let’s break it down:

  1. register function: register is a function provided by React Hook Form. It is used to register input fields with the form. When you call register('fieldName'), you are telling React Hook Form to associate this input field with a specific field name in the form data.

  2. Object Spreading ({...}): The curly braces {} in JavaScript are used for creating objects. The ... inside the curly braces is the spread operator, which is used to spread the properties of an object or the elements of an array.

  3. Using ...register('fieldName'): This syntax is essentially taking the result of the register function, which is an object containing properties related to the registration of the input field, and spreading its properties into the <input> element. It’s a concise way to apply multiple properties to the input element at once.

Here’s a simplified example to illustrate:

1
2
3
4
5
const inputField = {
  type: 'text',
  placeholder: 'Enter your text',
  ...register('fieldName'),
};

In this example, inputField is an object that includes the properties from the result of register('fieldName'). This way, if register returns { required: true, maxLength: 10 }, the inputField object will have these properties applied.

So, when you see {...register('fieldName')} in the context of React Hook Form, it’s spreading the properties returned by register onto the <input> element, providing the necessary configuration for React Hook Form to manage that specific form field. It’s a concise way to apply the properties returned by register to the input field.

Mastering Validation and Expense Tracking in React

Managing forms in a React application often involves incorporating validation to ensure the integrity of user-inputted data. In this article, we’ll explore the application of validation techniques, including schema-based validation using Zod. We’ll also dive into building components for an Expense Tracker project, covering the Expense List, Expense Filter, Expense Form, and the integration of React Hook Form and Zod for a robust and controlled form experience.

Applying Validation

Validation is a critical aspect of form handling to guarantee that users provide correct and expected input. React Hook Form offers a straightforward way to apply validation rules to form fields.

Here’s an example of validating a text input field with React Hook Form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from 'react';
import { useForm, Controller } from 'react-hook-form';

const MyForm = () => {
  const { control, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        Username:
        <Controller
          name="username"
          control={control}
          rules=
          render={({ field }) => <input {...field} />}
        />
        {errors.username && <p>{errors.username.message}</p>}
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, the rules prop in the Controller component specifies that the username field is required. The error message is displayed if the validation fails.

Schema-based Validation with Zod

Zod is a powerful TypeScript-first schema declaration and validation library. Combining Zod with React Hook Form allows for schema-based validation, providing a clear and centralized way to define validation rules.

Here’s a simple example using Zod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { z, object, string } from 'zod';

const schema = object({
  username: string().min(3, 'Username must be at least 3 characters'),
});

type FormData = z.infer<typeof schema>;

const MyForm = () => {
  const { handleSubmit, register, formState: { errors } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit: SubmitHandler<FormData> = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        Username:
        <input {...register('username')} />
        {errors.username && <p>{errors.username.message}</p>}
      </label>
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, the Zod schema defines validation rules for the username field. The resolver option in useForm integrates Zod with React Hook Form to handle validation based on the specified schema.

Disabling the Submit Button

Disabling the submit button until the form is valid is a common practice to prevent users from submitting incomplete or incorrect data. React Hook Form makes this process straightforward.

Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { z, object, string } from 'zod';

const schema = object({
  username: string().min(3, 'Username must be at least 3 characters'),
});

type FormData = z.infer<typeof schema>;

const MyForm = () => {
  const { handleSubmit, register, formState: { errors, isValid } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit: SubmitHandler<FormData> = (data) => {
    console.log(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        Username:
        <input {...register('username')} />
        {errors.username && <p>{errors.username.message}</p>}
      </label>
      <button type="submit" disabled={!isValid}>Submit</button>
    </form>
  );
};

In this example, the isValid property from formState is used to conditionally disable the submit button. It becomes true only when the form is valid based on the defined validation rules.

Project - Expense Tracker

Let’s shift our focus to a practical project - building an Expense Tracker. We’ll cover the creation of key components like ExpenseList, ExpenseFilter, and ExpenseForm. Additionally, we’ll integrate React Hook Form and Zod for form management and validation.

Building ExpenseList

The ExpenseList component displays a list of expenses. Each expense has a name, amount, and a date.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';

const ExpenseList = ({ expenses }) => {
  return (
    <div>
      <h2>Expense List</h2>
      <ul>
        {expenses.map((expense) => (
          <li key={expense.id}>
            <span>{expense.name}</span>
            <span>${expense.amount}</span>
            <span>{expense.date}</span>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ExpenseList;

Building ExpenseFilter

The ExpenseFilter component allows users to filter expenses based on a selected year.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { useState } from 'react';

const ExpenseFilter = ({ onFilter }) => {
  const [selectedYear, setSelectedYear] = useState('');

  const handleYearChange = (e) => {
    const year = e.target.value;
    setSelectedYear(year);
    onFilter(year);
  };

  return (
    <div>
      <h2>Filter Expenses</h2>
      <label>
        Select Year:
        <select value={selectedYear} onChange={handleYearChange}>
          <option value="">All</option>
          <option value="2023">2023</option>
          <option value="2022">2022</option>
          <option value="2021">2021</option>
        </select

>
      </label>
    </div>
  );
};

export default ExpenseFilter;

Building the Expense Form

The ExpenseForm component allows users to add a new expense. It includes input fields for the expense name, amount, and date.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import React from 'react';
import { useForm, SubmitHandler } from 'react-hook-form';
import { z, object, string, number } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

const schema = object({
  name: string().min(3, 'Name must be at least 3 characters'),
  amount: number().positive('Amount must be a positive number'),
  date: string().pattern(/\d{4}-\d{2}-\d{2}/, 'Invalid date format (YYYY-MM-DD)'),
});

type FormData = z.infer<typeof schema>;

const ExpenseForm = ({ onAddExpense }) => {
  const { handleSubmit, register, formState: { errors, isValid } } = useForm<FormData>({
    resolver: zodResolver(schema),
  });

  const onSubmit: SubmitHandler<FormData> = (data) => {
    onAddExpense(data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <label>
        Name:
        <input {...register('name')} />
        {errors.name && <p>{errors.name.message}</p>}
      </label>
      <label>
        Amount:
        <input type="number" {...register('amount')} />
        {errors.amount && <p>{errors.amount.message}</p>}
      </label>
      <label>
        Date:
        <input type="date" {...register('date')} />
        {errors.date && <p>{errors.date.message}</p>}
      </label>
      <button type="submit" disabled={!isValid}>Add Expense</button>
    </form>
  );
};

export default ExpenseForm;

Integrating with React Hook Form and Zod

Now, let’s integrate these components into the main App component, leveraging React Hook Form and Zod for form management and validation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { useState } from 'react';
import ExpenseList from './ExpenseList';
import ExpenseFilter from './ExpenseFilter';
import ExpenseForm from './ExpenseForm';

const App = () => {
  const [expenses, setExpenses] = useState([]);
  const [filteredYear, setFilteredYear] = useState('');

  const handleFilter = (year) => {
    setFilteredYear(year);
  };

  const handleAddExpense = (expense) => {
    setExpenses((prevExpenses) => [
      ...prevExpenses,
      { id: Math.random().toString(), ...expense },
    ]);
  };

  const filteredExpenses = filteredYear
    ? expenses.filter((expense) => expense.date.includes(filteredYear))
    : expenses;

  return (
    <div>
      <h1>Expense Tracker</h1>
      <ExpenseFilter onFilter={handleFilter} />
      <ExpenseForm onAddExpense={handleAddExpense} />
      <ExpenseList expenses={filteredExpenses} />
    </div>
  );
};

export default App;

In this example, ExpenseFilter and ExpenseForm utilize React Hook Form and Zod for input validation. The ExpenseList component displays the list of expenses, and the App component manages the state and integrates all the components.

Adding an Expense

With the project set up, users can now add expenses using the ExpenseForm. The form ensures that input data adheres to the specified validation rules, providing a smooth and controlled user experience.

This Expense Tracker project demonstrates the practical application of form validation using React Hook Form and Zod, making the process of managing expenses more intuitive and error-resistant. Integrating these libraries enhances the development experience by promoting a declarative and structured approach to form handling in React applications.

Integrating React with Backend using Express.js

Integrating React with a backend server, such as Express.js, is a crucial step in building full-stack applications. This article will guide you through the process of connecting a React frontend with an Express.js backend. We’ll cover topics like connecting to the backend, utilizing the Effect Hook, managing effect dependencies, cleaning up effects, and fetching data via HTTP requests.

Connecting to the Backend

Connecting a React frontend to an Express.js backend involves making HTTP requests from the frontend to the backend server. Commonly used libraries for handling HTTP requests in React are fetch and third-party libraries like axios.

Here’s a basic example using fetch to make a GET request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useEffect, useState } from 'react';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    fetch('/api/data')
      .then(response => response.json())
      .then(data => setData(data))
      .catch(error => console.error('Error fetching data:', error));
  }, []);

  return (
    <div>
      {/* Render data from the backend */}
    </div>
  );
};

In this example, the useEffect hook is used to make a GET request to the /api/data endpoint on the backend. The fetched data is then stored in the component’s state.

Understanding the Effect Hook

The useEffect hook in React is used for handling side effects in functional components. It is commonly employed for tasks like data fetching, subscriptions, or manually changing the DOM.

Here’s a simple example of using useEffect:

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { useEffect } from 'react';

const MyComponent = () => {
  useEffect(() => {
    // Side effect code goes here
    console.log('Component is mounted');

    // Cleanup function (optional)
    return () => {
      console.log('Component will unmount or dependencies change');
    };
  }, []); // Dependencies array
};

In this example, the useEffect runs when the component is mounted. The cleanup function, returned from useEffect, is called when the component is about to unmount or when the dependencies change.

Effect Dependencies

The dependencies array in useEffect is crucial for controlling when the effect runs. If the dependencies array is empty, the effect runs once when the component mounts. If dependencies are specified, the effect will run when any of the dependencies change.

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { useEffect, useState } from 'react';

const MyComponent = ({ userId }) => {
  const [userData, setUserData] = useState({});

  useEffect(() => {
    // Fetch user data when userId changes
    fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(data => setUserData(data))
      .catch(error => console.error('Error fetching user data:', error));
  }, [userId]); // userId is a dependency
};

In this example, the effect runs whenever the userId prop changes.

Effect Clean Up

In some cases, it’s necessary to perform cleanup when a component unmounts or when dependencies change. The cleanup function returned from useEffect is the perfect place for this.

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { useEffect } from 'react';

const MyComponent = () => {
  useEffect(() => {
    // Side effect code goes here

    // Cleanup function
    return () => {
      // Clean up code goes here
      console.log('Component will unmount or dependencies change');
    };
  }, []); // Dependencies array
};

This cleanup function is essential for avoiding memory leaks and ensuring that resources are released when they are no longer needed.

Fetching Data

Fetching data from an Express.js backend involves making HTTP requests. As mentioned earlier, you can use the fetch function or third-party libraries like axios.

Here’s an example using axios:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, { useEffect, useState } from 'react';
import axios from 'axios';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    axios.get('/api/data')
      .then(response => setData(response.data))
      .catch(error => console.error('Error fetching data:', error));
  }, []);

  return (
    <div>
      {/* Render data from the backend */}
    </div>
  );
};

In this example, axios is used to make a GET request to the /api/data endpoint. The fetched data is then stored in the component’s state.

Understanding HTTP Requests

HTTP requests are fundamental in frontend-backend communication. They can be categorized into various types:

  • GET: Retrieve data from the server.
  • POST: Send data to the server to create a new resource.
  • PUT: Update an existing resource on the server.
  • DELETE: Remove a resource from the server.

Here’s an example of using the fetch function

to make a POST request:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { useState } from 'react';

const MyComponent = () => {
  const [formData, setFormData] = useState({});

  const handleFormSubmit = () => {
    fetch('/api/data', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(formData),
    })
    .then(response => response.json())
    .then(data => console.log('Data posted successfully:', data))
    .catch(error => console.error('Error posting data:', error));
  };

  return (
    <form onSubmit={handleFormSubmit}>
      {/* Form input fields */}
      <button type="submit">Submit</button>
    </form>
  );
};

In this example, a POST request is made to the /api/data endpoint with JSON-formatted data from the form.

By understanding and utilizing these concepts, you’ll be well-equipped to connect your React frontend to an Express.js backend, making your full-stack development journey more seamless and efficient.

Integrating React with Node.js and Express.js Backend: Best Practices

Integrating React with a Node.js and Express.js backend is a common scenario in modern full-stack development. In this article, we’ll explore best practices for handling errors, working with asynchronous operations, canceling fetch requests, displaying loading indicators, and managing CRUD operations (Creating, Reading, Updating, Deleting) on the frontend. Additionally, we’ll delve into creating reusable API clients, extracting services, and building a custom data fetching hook to streamline your development workflow.

Handling Errors

Handling errors effectively is crucial for a seamless user experience. In React, you can use try-catch blocks or leverage the .catch() method with Promises when making API requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { useState } from 'react';
import axios from 'axios';

const MyComponent = () => {
  const [error, setError] = useState(null);

  const fetchData = async () => {
    try {
      const response = await axios.get('/api/data');
      // Handle successful response
    } catch (error) {
      setError(error.message);
    }
  };

  return (
    <div>
      {error && <p>Error: {error}</p>}
      <button onClick={fetchData}>Fetch Data</button>
    </div>
  );
};

Here, an error state is used to handle and display errors, providing users with meaningful feedback.

Working with Async and Await

Working with asynchronous operations, such as fetching data from a backend, is simplified with async and await. This ensures that your code remains readable and maintains a synchronous-like structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get('/api/data');
        setData(response.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {/* Render data from the backend */}
    </div>
  );
};

In this example, the useEffect hook is used to fetch data when the component mounts, ensuring a clean separation of concerns.

Cancelling a Fetch Request

Cancelling fetch requests is essential to avoid unnecessary network traffic and potential race conditions. The AbortController can be used to achieve this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const MyComponent = () => {
  const [data, setData] = useState([]);
  const [error, setError] = useState(null);

  useEffect(() => {
    const abortController = new AbortController();
    const signal = abortController.signal;

    const fetchData = async () => {
      try {
        const response = await axios.get('/api/data', { signal });
        setData(response.data);
      } catch (err) {
        if (err.name === 'AbortError') {
          console.log('Request was aborted');
        } else {
          setError(err.message);
        }
      }
    };

    fetchData();

    return () => {
      abortController.abort(); // Cleanup on component unmount
    };
  }, []);

  return (
    <div>
      {error && <p>Error: {error}</p>}
      {/* Render data from the backend */}
    </div>
  );
};

By creating an AbortController and using its signal in the axios request, you can cancel the request when needed.

Showing a Loading Indicator

Displaying a loading indicator enhances the user experience, especially when fetching data asynchronously. You can use a state variable to manage the loading state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState, useEffect } from 'react';
import axios from 'axios';

const MyComponent = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get('/api/data');
        setData(response.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      } finally {
        setLoading(false); // Set loading to false regardless of success or failure
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {loading ? <p>Loading...</p> : null}
      {/* Render data from the backend */}
    </div>
  );
};

In this example, the loading state is set to true before making the request and set to false in the finally block, providing visual feedback to the user.

Deleting Data

Performing delete operations requires making a DELETE request to the backend. Here’s a simple example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { useState } from 'react';
import axios from 'axios';

const MyComponent = ({ itemId, onDelete }) => {
  const handleDelete = async () => {
    try {
      await axios.delete(`/api/items/${itemId}`);
      onDelete(itemId); // Update the UI after successful deletion
    } catch (error) {
      console.error('Error deleting item:', error);
    }
  };

  return (
    <div>
      <button onClick={handleDelete}>Delete Item</button>
    </div>
  );
};

In this example, the onDelete callback is used to update the UI after a successful deletion.

Creating Data

Creating data involves making a POST request to the backend. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { useState } from 'react';
import axios from 'axios';

const MyComponent = ({ onAdd }) => {
  const [newItem, setNewItem] = useState('');

  const handleAddItem = async () => {
    try {
      const response = await axios.post('/api/items', { name: newItem });
      onAdd(response.data); // Update the UI after successful creation
      setNewItem(''); // Clear the input field
    } catch (error) {
      console.error('Error adding item:', error);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={newItem}
        onChange={(e) => setNewItem(e.target.value)}
      />
      <button onClick={handleAddItem}>Add Item</button>
    </div>
  );
};

In this example, the onAdd callback is used to update the UI after a successful creation, and the input field is cleared.

Updating Data

Updating data requires making a PUT or PATCH request to the backend. Here’s a simplified example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { useState } from 'react';
import axios from 'axios';

const MyComponent = ({ itemId, currentName, onUpdate }) => {
  const [updatedName, setUpdatedName] = useState(currentName);

  const handleUpdateItem = async () => {
    try {
      const response = await axios.put(`/api/items/${itemId}`, {
        name: updatedName,
      });
      onUpdate(itemId, response.data.name); // Update the UI after successful update
    } catch (error) {
      console.error('Error updating item:', error);
    }
  };

  return (
    <div>
      <input
        type="text"
        value={updatedName}
        onChange={(e) =>

 setUpdatedName(e.target.value)}
      />
      <button onClick={handleUpdateItem}>Update Item</button>
    </div>
  );
};

In this example, the onUpdate callback is used to update the UI after a successful update.

Extracting a Reusable API Client

To maintain a clean codebase, you can create a reusable API client that encapsulates the logic for making requests. This makes it easier to manage endpoints, headers, and error handling consistently across your application.

1
2
3
4
5
6
7
8
9
10
11
12
// apiClient.js
import axios from 'axios';

const apiClient = axios.create({
  baseURL: '/api', // Set a base URL for your API
  headers: {
    'Content-Type': 'application/json',
    // Add any other common headers
  },
});

export default apiClient;

Now, you can use this client throughout your components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyComponent.js
import React, { useEffect, useState } from 'react';
import apiClient from './apiClient';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await apiClient.get('/data');
        setData(response.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {/* Render data from the backend */}
    </div>
  );
};

Extracting the User Service

To organize your code, you can create a service that encapsulates all the operations related to a specific resource, such as a user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// userService.js
import apiClient from './apiClient';

const userService = {
  getUsers: async () => {
    try {
      const response = await apiClient.get('/users');
      return response.data;
    } catch (error) {
      console.error('Error fetching users:', error);
      throw error;
    }
  },

  createUser: async (userData) => {
    try {
      const response = await apiClient.post('/users', userData);
      return response.data;
    } catch (error) {
      console.error('Error creating user:', error);
      throw error;
    }
  },
  
  // Additional user-related operations...
};

export default userService;

Now, you can use the userService in your components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyComponent.js
import React, { useEffect, useState } from 'react';
import userService from './userService';

const MyComponent = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const fetchedUsers = await userService.getUsers();
        setUsers(fetchedUsers);
      } catch (error) {
        console.error('Error fetching users:', error);
      }
    };

    fetchUsers();
  }, []);

  return (
    <div>
      {/* Render user data */}
    </div>
  );
};

Creating a Generic HTTP Service

For even greater abstraction, you can create a generic HTTP service that can handle various HTTP methods and endpoints.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// httpService.js
import axios from 'axios';

const httpService = axios.create({
  baseURL: '/api', // Set a base URL for your API
  headers: {
    'Content-Type': 'application/json',
    // Add any other common headers
  },
});

const makeRequest = async (method, endpoint, data = null) => {
  try {
    const response = await httpService({
      method,
      url: endpoint,
      data,
    });

    return response.data;
  } catch (error) {
    console.error(`Error ${method} request to ${endpoint}:`, error);
    throw error;
  }
};

export const get = async (endpoint) => makeRequest('get', endpoint);

export const post = async (endpoint, data) => makeRequest('post', endpoint, data);

export const put = async (endpoint, data) => makeRequest('put', endpoint, data);

export const del = async (endpoint) => makeRequest('delete', endpoint);

// Additional methods as needed...

Now, you can use this generic HTTP service in your components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyComponent.js
import React, { useEffect, useState } from 'react';
import { get } from './httpService';

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await get('/data');
        setData(response);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, []);

  return (
    <div>
      {/* Render data from the backend */}
    </div>
  );
};

Creating a Custom Data Fetching Hook

To further abstract and simplify your code, you can create a custom hook for data fetching that encapsulates common patterns.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// useDataFetching.js
import { useState, useEffect } from 'react';
import { get } from './httpService';

const useDataFetching = (endpoint) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await get(endpoint);
        setData(response);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [endpoint]);

  return { data, loading, error };
};

export default useDataFetching

Bundling in Node.js with React and TypeScript

In modern web development, bundling plays a crucial role in optimizing the performance of your applications. It involves the process of combining and minifying your code and assets, making them more efficient for deployment. In the context of Node.js, React, and TypeScript, bundling becomes even more essential to ensure a smooth and efficient development workflow. In this article, we’ll explore the concepts of bundling and how it relates to a tech stack involving Node.js, React, and TypeScript.

What is Bundling?

Bundling is the process of taking multiple files and dependencies and merging them into a single file (or a few files). The primary goals of bundling are to reduce the number of HTTP requests made by the browser and to minimize the size of the transferred files, ultimately improving the loading speed of your web application.

Why Bundle in Node.js with React and TypeScript?

1. Dependency Management:

  • Node.js: Node.js is a server-side JavaScript runtime, but it’s also commonly used for building applications on the client side. It provides a powerful package manager called npm (Node Package Manager), allowing developers to easily manage and install project dependencies.

  • React: React is a popular JavaScript library for building user interfaces. It’s component-based and encourages the creation of reusable UI components. When building React applications, you often have a complex dependency tree that needs to be efficiently managed.

  • TypeScript: TypeScript is a superset of JavaScript that adds static typing to the language. It helps catch errors during development and provides better tooling for code navigation and refactoring.

2. Performance Optimization:

  • Reduced File Size: Bundling involves combining multiple files into one, reducing the number of HTTP requests. This results in a smaller total file size, which is crucial for faster loading times.

  • Minification: Bundlers often include a process called minification, where unnecessary characters (like whitespace and comments) are removed from the code. This further reduces the file size and improves load times.

3. Code Splitting:

  • React and Dynamic Imports: React supports dynamic imports, allowing you to load components only when they are needed. Bundlers can help with code splitting, creating separate bundles for different parts of your application. This can lead to faster initial page loads and better resource utilization.

Tools for Bundling in Node.js with React and TypeScript

1. Bundler: Webpack

  • Webpack: Webpack is a popular module bundler for JavaScript applications. It can handle not only JavaScript files but also assets like stylesheets and images. Webpack supports a rich ecosystem of plugins and loaders, making it suitable for various project setups.

  • Webpack Dev Server: For local development, Webpack provides a development server that offers features like hot module replacement (HMR) for a faster development workflow.

2. Package Manager: npm or Yarn

  • npm (Node Package Manager): npm comes bundled with Node.js and is the default package manager for JavaScript projects. It simplifies the process of installing, managing, and updating project dependencies.

  • Yarn: Yarn is an alternative package manager that aims to be faster and more reliable than npm. It also provides a consistent and deterministic dependency resolution algorithm.

3. TypeScript Compiler: tsc

  • TypeScript Compiler (tsc): TypeScript code needs to be transpiled to JavaScript before it can be executed in the browser. The TypeScript compiler (tsc) takes care of this process, and you can integrate it into your bundling setup.

Setting Up Bundling for a Node.js, React, and TypeScript Project

Let’s walk through a basic setup using Webpack, npm, and TypeScript for bundling in a Node.js and React project.

Step 1: Install Dependencies

1
2
3
4
5
6
# Initialize a new Node.js project
npm init -y

# Install necessary dependencies
npm install react react-dom typescript @types/react @types/react-dom
npm install webpack webpack-cli webpack-dev-server ts-loader

Step 2: Create Project Structure

1
2
3
4
5
6
project-root/
|-- src/
|   |-- index.tsx
|-- webpack.config.js
|-- tsconfig.json
|-- package.json

Step 3: Configure TypeScript

1
2
3
4
5
6
7
8
9
10
// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "jsx": "react",
    "strict": true,
    "esModuleInterop": true
  }
}

Step 4: Configure Webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.tsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
    ],
  },
  devServer: {
    contentBase: './dist',
    port: 3000,
  },
};

Step 5: Create a React Component

1
2
3
4
5
6
7
8
9
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';

const App: React.FC = () => {
  return <div>Hello, React with TypeScript!</div>;
};

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

Step 6: Run the Development Server

1
npx webpack serve --mode development

Visit http://localhost:3000 in your browser to see the bundled React application.

Conclusion

Bundling is a crucial step in optimizing and organizing your Node.js, React, and TypeScript projects. Leveraging tools like Webpack, npm, and TypeScript Compiler allows you to efficiently manage dependencies, optimize performance, and create a streamlined development workflow. By understanding the fundamentals of bundling and configuring the right tools, you can enhance the overall performance and maintainability of your web applications.

https://www.innoq.com/en/articles/2021/12/what-does-a-bundler-actually-do/

Understanding the <script> Tag and Modules in JavaScript

When it comes to building dynamic and interactive web applications, JavaScript is an indispensable language. Traditionally, JavaScript code was included in HTML files using the <script> tag. However, with the introduction of ECMAScript 6 (ES6), a new feature called “modules” was added to JavaScript, providing a more organized and modular approach to code.

The <script> Tag:

The <script> tag is used to embed or reference JavaScript code within an HTML document. It allows developers to include scripts directly in the HTML file or reference external script files. Here’s a basic example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Script Tag Example</title>
</head>
<body>

  <!-- Inline JavaScript -->
  <script>
    console.log("Hello, Script Tag!");
  </script>

  <!-- External JavaScript File -->
  <script src="myscript.js"></script>

</body>
</html>

In the example above, the first <script> tag contains inline JavaScript code, while the second one references an external script file named “myscript.js.”

ECMAScript Modules:

ECMAScript modules, often referred to as ES6 modules, bring a modular structure to JavaScript, allowing developers to break their code into smaller, reusable pieces. Each module encapsulates its own functionality, variables, and functions, making it easier to manage and maintain code.

To use modules, the type attribute of the <script> tag is set to “module”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Module Example</title>
</head>
<body>

  <!-- Module Script -->
  <script type="module">
    import { greet } from './greetings.js';

    greet("Hello, Module!");
  </script>

</body>
</html>

In this example, the script tag with type="module" is importing a greet function from an external module file named “greetings.js.” The import statement is a key feature of ECMAScript modules, allowing us to use functions and variables from other modules.

Creating a Module:

Let’s take a look at the content of the “greetings.js” module:

1
2
3
4
// greetings.js
export function greet(message) {
  console.log(message);
}

Here, we define a simple greet function and use the export keyword to make it available for use in other modules.

Benefits of Modules:

  1. Encapsulation: Modules encapsulate code, preventing variable and function name conflicts.
  2. Reusability: Modules can be reused across different parts of an application or in other projects.
  3. Maintainability: Breaking code into modules makes it easier to maintain and update.

In conclusion, while the traditional <script> tag is still widely used, ECMAScript modules provide a more modern and modular approach to structuring JavaScript code, offering benefits in terms of organization, maintainability, and reusability. As web development continues to evolve, understanding and incorporating modules into your JavaScript workflow becomes increasingly important.

https://usefulangle.com/post/252/script-type-module

Webpack and type="module" serve different purposes in the context of web development, and they are not mutually exclusive. Let’s discuss the advantages of each:

Webpack:

  1. Bundling and Code Splitting:
    • Advantage: Webpack excels in bundling multiple JavaScript files into a single bundle. This reduces the number of HTTP requests, making the application load faster. Additionally, Webpack supports code splitting, enabling efficient loading of only the necessary parts of the code.
  2. Asset Handling:
    • Advantage: Webpack is not limited to handling JavaScript files; it can process and bundle various types of assets, such as stylesheets, images, and fonts. This makes it a versatile tool for managing the entire application’s assets.
  3. Module System Compatibility:
    • Advantage: Webpack supports CommonJS, AMD, and ES6 module systems. This flexibility allows developers to use different module formats and dependencies in their projects.
  4. Loaders and Plugins:
    • Advantage: Webpack provides a powerful ecosystem of loaders and plugins. Loaders transform files before bundling (e.g., transpiling TypeScript to JavaScript), and plugins offer additional functionalities like minification, code splitting configuration, and more.
  5. Integration with Build Process:
    • Advantage: Webpack can be integrated into the build process, allowing developers to define complex build configurations. This is particularly useful for large and sophisticated projects.

type="module":

  1. Native Browser Support:
    • Advantage: type="module" is a native browser feature that enables ES6 module loading without the need for bundlers. This is advantageous for small to medium-sized projects where a full-fledged bundler like Webpack might be overkill.
  2. Simplified Setup:
    • Advantage: For simple projects or cases where developers want a minimal setup, using type="module" can be beneficial. It eliminates the need for configuring and setting up a build tool, reducing the overall complexity.
  3. Synchronous Loading:
    • Advantage: Modules loaded via type="module" are fetched asynchronously, allowing browsers to continue parsing and rendering the HTML while fetching scripts in the background. This can enhance the initial page load performance.
  4. Caching:
    • Advantage: The browser can cache modules loaded with type="module", improving subsequent page loads. This is because modules can be retrieved from the cache instead of making additional network requests.

Considerations:

  • Use Cases:
    • Webpack: Well-suited for large, complex projects with diverse asset types, dependencies, and build requirements.
    • type="module": Suitable for smaller projects or scenarios where a minimalistic approach is preferred, and the native browser module system is sufficient.
  • Complexity:
    • Webpack: Offers a comprehensive solution for various build and optimization tasks but might introduce complexity, especially for beginners.
    • type="module": Simpler to set up and use but lacks some advanced features provided by bundlers like Webpack.
  • Browser Compatibility:
    • Webpack: Compatible with a wide range of browsers, including older ones, with appropriate configuration and polyfills.
    • type="module": Supported in modern browsers; may require fallbacks or transpilation for older browsers.

In many cases, developers use both approaches based on the project’s requirements and complexity. For example, Webpack can be used for larger projects, while simpler projects or individual modules may leverage the native type="module" support.

Importing CSS into a JavaScript file is a technique often used in modern web development, especially with the advent of bundlers like Webpack and tools like CSS-in-JS libraries. The idea is to encapsulate styles within the JavaScript code, allowing for better modularity and scoping. Here are a few examples:

1. Using CSS Modules:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// styles.css
.myClass {
  color: red;
}

// component.js
import React from 'react';
import styles from './styles.css';

const MyComponent = () => (
  <div className={styles.myClass}>
    This text is styled using CSS Modules!
  </div>
);

export default MyComponent;

In this example, the CSS file (styles.css) defines a class named .myClass. The JavaScript file (component.js) then imports this CSS module, and the styles are applied locally using the styles.myClass notation.

2. Using Styled Components:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// styled-components package is required
import styled from 'styled-components';

const StyledDiv = styled.div`
  color: blue;
`;

const MyComponent = () => (
  <StyledDiv>
    This text is styled using Styled Components!
  </StyledDiv>
);

export default MyComponent;

Here, the styled-components library is used to create styled components. The styles are defined directly within the JavaScript file using a tagged template literal.

3. Using CSS-in-JS with Emotion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// emotion package is required
import { css } from '@emotion/react';

const myStyles = css`
  font-size: 16px;
  color: green;
`;

const MyComponent = () => (
  <div css={myStyles}>
    This text is styled using Emotion CSS-in-JS!
  </div>
);

export default MyComponent;

Here, the @emotion/react package is used for CSS-in-JS. The styles are defined using the css function, and then applied to the JSX element using the css prop.

4. Using Inline Styles:

1
2
3
4
5
6
7
8
9
10
11
12
const inlineStyles = {
  color: 'purple',
  fontSize: '18px',
};

const MyComponent = () => (
  <div style={inlineStyles}>
    This text is styled using inline styles in JavaScript!
  </div>
);

export default MyComponent;

While not the same as importing a separate CSS file, you can also define styles directly within the JavaScript file using inline styles.

Note:

  • Make sure to have the required packages installed based on the chosen approach (e.g., styled-components, @emotion/react).
  • The examples showcase different ways to handle styles in JavaScript files, each with its own advantages and use cases. The choice often depends on personal preference, project requirements, and the specific features provided by the libraries or tools being used.

Vite

Vite is a new-generation frontend tooling that provides a faster and more streamlined development experience for web applications. It is based on the concept of native ES modules and uses Rollup to bundle code for production. Vite is still under active development, but it has already gained popularity among developers due to its impressive performance and ease of use. [Image of Vite logo]

Here are some of the key features of Vite:

  • Instant server start: Vite uses native ES modules, so there is no need to bundle code before serving it. This means that the development server can start up in just a few milliseconds, no matter how large the application is.
  • Lightning-fast HMR: Vite’s Hot Module Replacement (HMR) is incredibly fast and efficient. It can update your application in milliseconds, even if you make changes to large files.
  • Rich features: Vite has out-of-the-box support for TypeScript, JSX, CSS, and more. It also has a plugin system that allows you to extend its functionality.
  • Optimized build: Vite uses Rollup to bundle code for production. Rollup is a powerful bundler that can create highly optimized code.
  • Universal plugin interface: Vite’s plugin interface is shared between development and build, so you can use the same plugins for both.
  • Fully typed APIs: Vite’s APIs are fully typed, which makes it easy to use and integrate with TypeScript.

Vite is a great choice for developers who want a faster, more streamlined, and more enjoyable development experience. It is particularly well-suited for large applications, as it can provide significant performance improvements over traditional bundlers.

Here are some of the things you can use Vite for:

  • Developing single-page applications (SPAs): Vite is a great choice for developing SPAs, as it can provide a fast and seamless development experience.
  • Developing web components: Vite can also be used to develop web components. Its support for native ES modules makes it easy to create and share web components.
  • Building libraries and frameworks: Vite can also be used to build libraries and frameworks. Its plugin system makes it easy to extend Vite with custom functionality.

Overall, Vite is a powerful and versatile tool that can be used for a wide variety of frontend development tasks. If you are looking for a new frontend tooling solution, I highly recommend checking out Vite.

Introduction to Vite: A Lightning-Fast Development Server

Vite, pronounced [veet], is not just a French word for “fast”; it’s also a lightning-fast local development server designed by Evan You, the creator of Vue.js. Vite serves as the default development server for both Vue and React project templates. In this article, we’ll explore the key features and functionalities that make Vite a powerful tool for modern web development.

Under the Hood: Rollup and esbuild

Vite leverages the strengths of two powerful bundlers, Rollup and esbuild, internally. Rollup is known for its tree-shaking capabilities and efficient code splitting, while esbuild excels in rapid bundling through its native Go implementation. This dynamic duo forms the backbone of Vite’s bundling mechanism, ensuring optimized and performant builds.

Hot Module Replacement (HMR) Magic

One of Vite’s standout features is its implementation of Hot Module Replacement (HMR). Unlike traditional full-page reloads, HMR allows Vite to update only the specific file being edited, thanks to the use of ES6 modules (ESM). This results in a quicker development feedback loop as changes are instantly reflected in the browser upon file save. Vite’s HMR mechanism enhances developer productivity by eliminating the need for full application recompilation.

TypeScript and JSX Support

Vite extends its compatibility to TypeScript and JSX, catering to a broader audience of developers. Whether you’re building a Vue or React application, Vite’s seamless integration with these popular technologies ensures a smooth development experience with robust type checking and JSX syntax support.

Server-Side Rendering (SSR) Capabilities

Vite goes beyond a simple development server; it provides built-in support for Server-Side Rendering (SSR). Listening on TCP port 5173 by default, Vite enables developers to render pages on the server, enhancing performance and SEO. Additionally, developers have the flexibility to configure Vite for HTTPS, making it suitable for a wide range of deployment scenarios.

Configuration Flexibility: HTTPS and Proxy Support

Vite understands the diverse needs of web developers and offers configuration options for various deployment scenarios. Developers can configure Vite to serve content over HTTPS, ensuring secure communication during development. Additionally, Vite supports proxying requests, including WebSocket requests, to a backend web server like Apache HTTP Server or lighttpd. This makes it easy to integrate Vite into existing projects and infrastructure seamlessly.

Conclusion

In conclusion, Vite stands out as a development server that prioritizes speed, efficiency, and flexibility. With support for TypeScript, JSX, HMR, SSR, and powerful bundlers under the hood, Vite empowers developers to build modern web applications with a delightful development experience. Whether you are a Vue.js enthusiast or a React aficionado, Vite deserves a place in your toolkit for its ability to accelerate the development workflow and streamline the creation of high-performance web applications.

(Source: https://en.wikipedia.org/wiki/Vite_(software))

JSX

“JSX (JavaScript Syntax Extension and occasionally referred as JavaScript XML) is a JavaScript extension that allows creation of DOM trees using an XML-like syntax.[1] Initially created by Facebook for use with React, JSX has been adopted by multiple web frameworks.[2]: 5 [3]: 11  Being a syntactic sugar, JSX is generally transpiled into nested JavaScript function calls structurally similar to the original JSX.” (Source: https://en.wikipedia.org/wiki/JSX_(JavaScript))

Example structure of project

Certainly! If you want to include TypeScript in your React apps, you can modify the structure accordingly. Below is an updated project structure with TypeScript for the React apps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
project-root/
|-- config/
|   |-- config.js
|-- controllers/
|   |-- mainController.js
|-- middleware/
|   |-- authentication.js
|-- public/
|   |-- css/
|       |-- style.css
|       |-- component1.css
|       |-- component2.css
|   |-- images/
|       |-- image1.jpg
|       |-- image2.png
|-- routes/
|   |-- index.js
|   |-- apiRoutes.js
|-- tests/
|   |-- test1.js
|   |-- test2.js
|-- views/
|   |-- layouts/
|       |-- mainLayout.ejs
|   |-- pages/
|       |-- home.ejs
|       |-- about.ejs
|-- client/
|   |-- react-app-1/
|       |-- src/
|           |-- components/
|               |-- Component1.tsx  // TypeScript files
|               |-- Component2.tsx
|           |-- index.tsx
|   |-- react-app-2/
|       |-- src/
|           |-- components/
|               |-- Component3.tsx
|               |-- Component4.tsx
|           |-- index.tsx
|-- app.js
|-- package.json
|-- tsconfig.json  // TypeScript configuration

In this structure, I’ve changed the file extensions for React components in the client/ directory to use .tsx for TypeScript. You’ll also need a tsconfig.json file at the root of your project to configure TypeScript settings.

Here’s a basic example of a tsconfig.json file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react"
  },
  "include": ["client/**/*"]
}

This TypeScript configuration is a basic setup. You might need to adjust it based on your specific requirements.

Remember to install the necessary TypeScript packages and React types in your client/react-app-1/ and client/react-app-2/ directories:

1
2
3
cd client/react-app-1/
npm install react react-dom @types/react @types/react-dom
npm install --save-dev typescript @types/node @types/react @types/react-dom @types/jest

Repeat the same process for client/react-app-2/.

Let’s integrate the React apps into your Express app and show you how the project can be built and started. I’ll provide a basic example for the main entry point (app.js) and one of the routes that includes a React app.

First, let’s modify the directory structure to better organize the React apps within the Express project:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
project-root/
|-- ...
|-- views/
|   |-- layouts/
|       |-- mainLayout.ejs
|   |-- pages/
|       |-- home.ejs
|       |-- about.ejs
|-- client/
|   |-- react-app-1/
|       |-- src/
|           |-- components/
|               |-- Component1.tsx
|               |-- Component2.tsx
|           |-- index.tsx
|       |-- build/  // Compiled React app (output directory)
|-- app.js
|-- package.json
|-- tsconfig.json

Now, let’s update the app.js to include the React app in one of the routes. I’ll assume you want to include it in the home route.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const express = require('express');
const path = require('path');
const config = require('./config/config');
const mainController = require('./controllers/mainController');

const app = express();

// Set up middleware
app.use(express.static('public'));

// Set up routes
app.get('/', mainController.home);

// React app route
app.get('/react-app-1', (req, res) => {
  res.sendFile(path.join(__dirname, 'client/react-app-1/build/index.html'));
});

// Other routes...
// app.get('/about', mainController.about);

// Start the server
const port = config.port || 3000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

Now, let’s modify mainController.js to render the home.ejs view and pass the necessary data to it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// controllers/mainController.js

const mainController = {
  home: (req, res) => {
    // Assume you want to pass some data to the view
    const data = {
      message: 'Hello from Express!',
    };

    res.render('pages/home', { data });
  },
  // Other controller methods...
};

module.exports = mainController;

Finally, let’s update the home.ejs view to include the React app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- views/pages/home.ejs -->

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Home Page</title>
</head>
<body>
  <h1><%= data.message %></h1>

  <!-- Include the React app -->
  <div id="react-app-1"></div>
  <script src="/react-app-1/static/js/main.js"></script>
</body>
</html>

Make sure to replace the script source (/react-app-1/static/js/main.js) with the actual path to your React app’s JavaScript bundle.

Now, you can start your Express app:

1
node app.js

Visit http://localhost:3000 in your browser to see the home page with the integrated React app.

Remember to build your React app before starting the Express server:

1
2
3
cd client/react-app-1
npm install   # Install dependencies
npm run build  # Build the React app

Repeat a similar process for other React apps if needed. Adjust the paths and configurations based on your project structure and requirements.

Certainly! Here’s the translation of the steps into English:

Steps:

  1. Install Webpack and Required Plugins:

    Navigate to the directory of each React app (client/react-app-1) and install Webpack and its related modules:

    1
    2
    
    cd client/react-app-1
    npm install --save-dev webpack webpack-cli webpack-dev-server
    

    Repeat this step for each React app.

  2. Create a Webpack Configuration File:

    Create a file named webpack.config.js in each React app’s directory:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    // client/react-app-1/webpack.config.js
    
    const path = require('path');
    
    module.exports = {
      entry: './src/index.tsx',
      output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'build'),
      },
      // Add loaders, plugins, and other configurations as needed
      // For TypeScript, you might need 'ts-loader'
    };
    

    Repeat this step for each React app.

  3. Create a Build Script for Each React App:

    Open the package.json file of each React app and add a build script:

    1
    2
    3
    4
    5
    
    // client/react-app-1/package.json
    
    "scripts": {
      "build": "webpack --config webpack.config.js"
    },
    

    Repeat this step for each React app.

  4. Change the Entry Point in the Express Server:

    Modify the entry point in your Express server (app.js) to point to the bundled files:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // app.js
    
    // ...
    
    // React app route
    app.get('/react-app-1', (req, res) => {
      res.sendFile(path.join(__dirname, 'client/react-app-1/build/index.html'));
    });
    
    // ...
    
    

    Repeat this step for each React app.

  5. Create a Global Build Script in the Main Directory:

    Open the main package.json file and add a global build script that bundles all React apps at once:

    1
    2
    3
    4
    5
    
    // package.json
    
    "scripts": {
      "build-all": "cd client/react-app-1 && npm run build && cd ../react-app-2 && npm run build && cd ../react-app-3 && npm run build && cd ../.."
    },
    

    Adjust this according to the number and names of your React apps.

Run the Build Process:

Run the build process for all React apps with a single command in the main directory:

1
npm run build-all

This command navigates to each React app directory and executes the build script. If everything is successful, your Express routes can now access the bundled files of the React apps. Keep in mind that this is a basic configuration, and you can further customize the Webpack configuration and build scripts to meet the specific requirements of your project.

To transpile TypeScript code by yourself, you can use the TypeScript Compiler (tsc) provided by the TypeScript package. Here are the steps to transpile TypeScript code manually:

Step 1: Install TypeScript

If you haven’t installed TypeScript globally, you can do so using npm:

1
npm install -g typescript

This installs the TypeScript compiler globally on your machine.

Step 2: Create a TypeScript Configuration File (Optional)

Create a tsconfig.json file in the root of your project to configure TypeScript settings. This step is optional, but it allows you to specify compiler options and manage your TypeScript configuration more effectively.

Here’s a simple example of a tsconfig.json file:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

Step 3: Transpile TypeScript Code

Run the TypeScript compiler (tsc) in your terminal to transpile your TypeScript code. If you have a tsconfig.json file, the compiler will use the settings specified in that file.

1
tsc

If you don’t have a tsconfig.json file, you can pass the file names directly to tsc:

1
tsc file1.ts file2.ts

Step 4: Run the Transpiled JavaScript Code

After transpilation, you will get JavaScript files in the specified output directory (or in the same directory as your TypeScript files if no output directory is specified). You can then run your JavaScript files using Node.js or another JavaScript runtime.

1
node dist/file1.js

Notes:

  • Adjust the tsconfig.json file according to your project’s needs. Refer to the TypeScript documentation for more configuration options: tsconfig.json documentation

  • You can integrate TypeScript compilation into your build process or use task runners like Gulp or Grunt to automate the transpilation.

  • Many developers use bundlers like Webpack along with TypeScript to handle modules, dependencies, and other aspects of their projects more efficiently. If your project grows, consider incorporating a bundler into your workflow.

Remember that using a task runner, build tool, or bundler can simplify the development process, especially for larger projects, as they often include additional features like watching for changes, optimizing code, and handling various module formats.

CORS

CORS, or Cross-Origin Resource Sharing, is a security feature implemented by web browsers to control how web pages in one domain can request and interact with resources hosted on another domain. It is a security mechanism to prevent unauthorized cross-origin requests, which can help protect users from potential security vulnerabilities.

When you make a cross-origin HTTP request (e.g., an AJAX request from a web page served from one domain to a server in another domain), the browser enforces the Same-Origin Policy by default, which restricts such requests. CORS is a set of HTTP headers that the server can include in its response to inform the browser that it is permitted to make requests from a different origin.

To use CORS with Express.js, you can use the cors middleware, which simplifies the process of handling CORS headers in your application.

Installing the cors middleware:

1
npm install cors

Using cors in your Express.js application:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const express = require('express');
const cors = require('cors');

const app = express();

// Enable CORS for all routes
app.use(cors());

// Your routes and other middleware go here

const port = 3000;
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

This simple example enables CORS for all routes by using app.use(cors()). You can also configure cors with specific options to control which origins are allowed, which headers are accepted, and more. For example:

1
2
3
4
5
6
7
8
const corsOptions = {
  origin: 'http://example.com', // only allow requests from this origin
  methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
  credentials: true, // include cookies
  optionsSuccessStatus: 204, // respond to preflight requests with 204 (No Content)
};

app.use(cors(corsOptions));

Make sure to adjust the origin property to match the origin of your frontend application.

By using the cors middleware, you ensure that the necessary headers are included in your responses to handle cross-origin requests properly. This helps prevent CORS-related errors in your web applications.

CommonJS, ES2015 (ES6) modules, and AMD (Asynchronous Module Definition) are different module systems in JavaScript, each with its own syntax and purpose.

  1. CommonJS:
    • CommonJS is a module system designed for server-side JavaScript environments, notably Node.js.
    • It uses require to import modules and module.exports to export them.
    • It’s a synchronous module system, meaning modules are loaded and executed in a blocking manner.
    • Example:

      1
      2
      3
      4
      5
      
      // Import module
      const otherModule = require('./otherModule');
      
      // Export module
      module.exports = { someFunction };
      
  2. ES2015 (ES6) Modules:
    • ES6 modules are the standard module system introduced in ECMAScript 2015 (ES6).
    • They use import to bring in modules and export to expose functionality.
    • ES6 modules are designed for both server-side and client-side JavaScript environments.
    • It supports asynchronous module loading and dynamic imports.
    • Example:

      1
      2
      3
      4
      5
      
      // Import module
      import { someFunction } from './otherModule';
      
      // Export module
      export { someFunction };
      
  3. AMD (Asynchronous Module Definition):
    • AMD is a module system designed for asynchronous loading of modules in the browser.
    • It uses functions like define and require to manage module dependencies.
    • AMD is used with libraries like RequireJS.
    • It’s particularly useful for optimizing browser performance by loading modules asynchronously.
    • Example:

      1
      2
      3
      4
      5
      
      // Define module
      define(['dependency'], function (dependency) {
        // Module code
        return { someFunction };
      });
      

Regarding using npm modules in the browser and Node.js:

  • Node.js: You can use CommonJS modules in Node.js, and many npm packages are written in CommonJS format and are compatible with Node.js.

  • Browser: While the browser supports both CommonJS and ES6 modules, the actual compatibility depends on the npm package itself. Some npm packages are designed to work in both environments (server and browser), while others might be more tailored to Node.js. Tools like Webpack or Parcel can help bundle and transpile modules to make them compatible with the browser.

In general, many npm packages are designed to be used in both environments, but it’s essential to check the package documentation and consider using tools like Webpack or Rollup to handle module compatibility and bundling for the browser.

Certainly! Let’s create a simple example to illustrate code that could work in both the browser and Node.js, and another example that is more Node.js-specific.

Example 1: CommonJS Module (Works in Both)

Module (module.js):

1
2
3
4
5
// CommonJS module

const add = (a, b) => a + b;

module.exports = { add };

Usage in Browser (index.html):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CommonJS in Browser</title>
</head>
<body>
  <script type="module">
    // Import CommonJS module
    import { add } from './module.js';

    // Use the module
    const result = add(3, 5);
    console.log(result); // Output: 8
  </script>
</body>
</html>

Usage in Node.js (app.js):

1
2
3
4
5
6
// Node.js

const { add } = require('./module');

const result = add(3, 5);
console.log(result); // Output: 8

This example demonstrates a simple CommonJS module (module.js) with a function to add two numbers. The module is imported and used both in a basic HTML file for the browser and in a Node.js script.

Example 2: Node.js-Specific (Probably Won’t Work in the Browser)

Node.js Module (nodeModule.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
// Node.js-specific module

const fs = require('fs');

const readFileSync = (filePath) => {
  try {
    return fs.readFileSync(filePath, 'utf-8');
  } catch (error) {
    return `Error reading file: ${error.message}`;
  }
};

module.exports = { readFileSync };

Usage in Node.js (nodeApp.js):

1
2
3
4
5
6
// Node.js

const { readFileSync } = require('./nodeModule');

const content = readFileSync('example.txt');
console.log(content);

This example demonstrates a Node.js-specific module (nodeModule.js) that reads the content of a file using the fs (File System) module. This module and its functionality are more tailored to a Node.js environment and might not work seamlessly in the browser. If you tried to use this module in the browser without the appropriate browser APIs, it would likely result in an error.

Determining whether an npm package works in the browser involves checking several factors. Here are some steps you can take to assess whether an npm package is suitable for browser usage:

  1. Package Documentation:
    • Check the official documentation of the npm package. Most package maintainers explicitly mention whether their package is intended for browser usage or if it is specific to Node.js.
  2. Browser Field in package.json:
    • Examine the package.json file of the npm package. Some packages include a browser field that specifies which files should be loaded in a browser environment. If this field is present, it’s a good indicator that the package is designed to work in the browser.
  3. ES Modules Support:
    • Check if the package provides ES modules (.mjs files) or has a "module" field in its package.json. ES modules are natively supported in modern browsers, and a package designed for the browser might include them.
  4. Minified and UMD Builds:
    • Some packages provide minified and Universal Module Definition (UMD) builds specifically for browser usage. Look for files like dist/package.min.js or dist/package.umd.js. These builds are often suitable for use in browser environments.
  5. Check Dependencies:
    • Review the package’s dependencies. If it relies on Node.js-specific modules that have no browser equivalent, it might not be suitable for the browser.
  6. GitHub Repository:
    • Check the package’s GitHub repository and browse through issues and discussions. This can give you insights into how the package is used in different environments, including the browser.
  7. Community Feedback:
    • Look for community feedback, blog posts, or articles related to using the package in a browser. Community discussions can provide valuable insights and solutions to potential issues.
  8. Browserify/webpack Compatibility:
    • If you plan to use the package in a frontend project, check if it is compatible with bundlers like Browserify or webpack. Many Node.js-specific packages can be bundled for the browser using these tools.

Remember that not all npm packages are designed to work in the browser, especially those that rely on Node.js-specific features. Always check the package’s documentation and the considerations mentioned above to determine its compatibility with browser environments. Additionally, testing the package in a browser environment as part of your development process is a good practice.

The UI frameworks you mentioned—Ant Design, Material-UI, Semantic UI, and RFC-E (assuming RFCE refers to React Functional Component Extension)—are popular libraries that provide pre-designed components and styles to help developers create consistent and visually appealing user interfaces. Each framework has its own design principles, styling, and set of components. Let’s briefly explore the differences:

Ant Design:

  • Description: Ant Design is a UI framework developed by Alibaba Group. It is a comprehensive design system with a set of high-quality React components.
  • Design Principles: Ant Design follows a design system based on the principles of “Natural Language,” “Grammar,” and “Composition.”
  • Component Style: Ant Design components have a distinctive look with a focus on simplicity, clarity, and a modern design language.
  • Use Cases: It is often used for enterprise-level applications and is popular in the Chinese development community.

Material-UI:

  • Description: Material-UI is a React UI framework that implements Google’s Material Design principles. It provides a set of components that follow the Material Design guidelines.
  • Design Principles: Material-UI follows the Material Design principles, which include concepts like “Material is the metaphor” and “Bold, graphic, intentional.”
  • Component Style: Material-UI components have a clean, colorful, and visually engaging design that aligns with the Material Design language.
  • Use Cases: It is widely used for creating modern and responsive user interfaces, and it’s well-suited for applications that benefit from a material design aesthetic.

Semantic UI:

  • Description: Semantic UI is a CSS framework that provides a set of pre-designed components and styles. It also has integrations with various JavaScript frameworks, including React.
  • Design Principles: Semantic UI aims to have a semantic and human-friendly HTML structure, making it easier for developers to understand the code.
  • Component Style: Semantic UI components have a modern and visually appealing design. It emphasizes clear and concise HTML while providing a wide range of customization options.
  • Use Cases: It is suitable for projects that want a stylish and customizable UI while maintaining a semantic HTML structure.

RFC-E (React Functional Component Extension):

  • Description: “RFC-E” is not a widely recognized UI framework. However, “RFC” commonly stands for “React Functional Component.”
  • Usage: RFC-E could refer to a personal or project-specific naming convention for React functional components. React functional components are a fundamental building block in React applications, providing a way to create UI elements using functions.
  • Common Practice: Developers often create functional components with the “RFC” or “FC” prefix to distinguish them from class components.

When choosing a UI framework, consider factors such as the design principles, component customization capabilities, ease of integration, and the specific requirements of your project. Each framework has its strengths, and the choice often depends on the design language you prefer, the target audience, and the overall project goals.

UseEffect

In React, useEffect is a Hook that allows you to perform side effects in your functional components. Side effects can include data fetching, subscriptions, manual DOM manipulations, and other operations that need to happen outside the regular flow of a React component’s render cycle.

The useEffect Hook is called after the component has been rendered to the screen. It takes two arguments:

  1. Effect Function: The first argument is a function that contains the code for the side effect.

  2. Dependency Array (optional): The second argument is an optional array of dependencies. It determines when the effect function should run. If the dependencies array is empty, the effect runs only once after the initial render. If there are dependencies, the effect runs whenever any of those dependencies change between renders.

Here’s a basic example of using useEffect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useState, useEffect } from 'react';

function ExampleComponent() {
  // State variable
  const [count, setCount] = useState(0);

  // Effect to run after each render
  useEffect(() => {
    // This code will run after the component renders

    // Example: Update the document title
    document.title = `Count: ${count}`;

    // Cleanup function (optional)
    return () => {
      // This code will run when the component unmounts or when dependencies change
      // It's useful for cleaning up resources, like canceling subscriptions
    };
  }, [count]); // Dependencies array

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

export default ExampleComponent;

In this example:

  • The useEffect Hook updates the document title based on the current count.

  • The count state variable is a dependency specified in the dependencies array. This means that the effect will re-run whenever the count value changes.

  • The return statement in the effect function is optional and is used for cleanup. If present, it specifies a function to run when the component is unmounted or when the dependencies change.

Common use cases for useEffect include data fetching, subscribing to external data sources, updating the DOM, and managing side effects in response to component lifecycle events. It helps you organize side effects in a declarative way within functional components.

Fruther Reading:

https://www.w3schools.com/react/react_useeffect.asp https://www.freecodecamp.org/news/create-a-react-frontend-a-node-express-backend-and-connect-them-together-c5798926047c/ https://medium.com/geekculture/build-and-deploy-a-web-application-with-react-and-node-js-express-bce2c3cfec32 https://www.educative.io/answers/what-is-vitejs https://blog.logrocket.com/build-react-typescript-app-vite/ https://dev.to/dirk94/how-i-structure-my-express-typescript-react-applications-g3e

This post is licensed under CC BY 4.0 by the author.

Impressum  | 

Datenschutz  | 

Manage cookie settings  | 

Using the Chirpy theme for Jekyll

© 2024 CodingTarik. Some rights reserved.