Advance Javascript: Debouncing

When writing applications in javascript, we rarely consider performance, however as the user base or application usage increases performance becomes a huge bottleneck for growth. One powerful technique to manage the frequency of function execution is debouncing. In this post, we’ll explore what debouncing is, why it’s useful, and how to implement it with examples from both a console application and a web application.

What is Debouncing?

Debouncing is a technique used to ensure that a function is only executed after a certain amount of time has passed since it was last invoked. This is particularly useful in scenarios where an event fires multiple times in quick succession, like keystrokes, window resizing, or scroll events. By debouncing, we can limit the number of times the function is called, improving performance and user experience.

Why Use Debouncing?

Imagine a search input field that sends a request to a server every time a user types a character. Without debouncing, a request would be sent for every keystroke, potentially overwhelming the server with unnecessary requests. Debouncing ensures that the request is only sent after the user has stopped typing for a specified period, reducing the number of requests and improving efficiency.

Example 1: Console application

Let’s start with a simple example in a console application using Node.js. We’ll create a debounced function that logs a message only after the user stops typing for 1 second.

First, install the ‘readline‘ module if you haven’t already:

npm install @types/node

Here’s the TypeScript code:

import * as readline from 'readline';

// Declare debounce type separately
type Debounce = <T extends (...args: any[]) => void>(func: T, wait: number) => (this: unknown, ...args: Parameters<T>) => void;

const debounce: Debounce = (func, wait) => {
  let timeout: NodeJS.Timeout | undefined;
  return function (this: unknown, ...args: Parameters<typeof func>) {
    const context = this;
    if (timeout) clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

const handleInput = debounce((input: string) => {
  console.log('Executing command:', input.trim());
}, 1000);

console.log('Type a command and press Enter:');
rl.on('line', handleInput);

In the above example the the ‘handleInput’ function gets called only after 1 second after the user stops typing, allowing the application to reduce the unnecessary executions each time the user is typing.

Now, let’s look at an example in a web application. We’ll debounce a function that handles input events on a search field, ensuring the search function is only called after the user stops typing for 300 milliseconds.

HTML:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Debouncing Example</title>
</head>
<body>
  <input type="text" id="searchInput" placeholder="Type to search..." />
  <script src="app.js"></script>
</body>
</html>

JavaScript (app.js):

function debounce(func, wait) {
  let timeout;
  return function (...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

const handleInput = debounce((event) => {
  console.log('Search query:', event.target.value);
}, 300);

document.getElementById('searchInput').addEventListener('input', handleInput);

Conclusion:

Debouncing is a simple yet powerful technique to control the execution frequency of functions in JavaScript. By applying debouncing in both console and web applications, you can significantly improve performance and user experience. Whether you’re managing user input, scroll events, or resize events, debouncing ensures that your functions are executed efficiently and only when necessary. In my next article I will go into debouncing in a react application using useCallback hook and lodash ‘debounce’

For complete source – https://github.com/IndikaMaligaspe/advance-javascrips/tree/master/topics/performance-optimization

Authentication Middleware with JWT in Node.js

In any website or web application, ensuring that users are who they claim to be and protecting sensitive data from unauthorized access is crucial . One popular method for implementing authentication in Node.js is using JSON Web Tokens (JWT) and custom middleware. In this article, lets explore how to create a middleware that authenticates users using JWT.

What is JWT?

JWT (JSON Web Token) is an open standard for securely transmitting information between parties as a JSON object. This information is digitally signed, ensuring its integrity and authenticity. JWT is commonly used for authentication and authorization purposes.

A JWT is composed of three parts:

  • Header: Contains metadata about the token, such as the type of token (JWT) and the signing algorithm used (e.g., HMAC SHA256).
  • Payload: Contains the claims, which are statements about an entity (typically, the user) and additional data. There are three types of claims: registered, public, and private.
    • Registered claims: Predefined claims like iss (issuer), exp (expiration time), sub (subject), and aud (audience).
    • Public claims: Custom claims defined, such as user roles.
    • Private claims: Claims agreed upon between parties using the JWT.
  • Signature: Used to verify the token’s integrity and authenticity. It is created by encoding the header and payload using Base64Url, concatenating them with a dot, and signing the resulting string with a secret key using the specified algorithm.

A JWT typically looks like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Setting Up the Project

Before we dive into the middleware, let’s set up a basic Node.js project with Express.

  • Initialize a new Node.js project:
mkdir auth-middleware
cd auth-middleware
npm init -y
  • Install necessary packages:
npm install express jsonwebtoken body-parser
  • Create a basic Express server:
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');

const app = express();
app.use(bodyParser.json());

const PORT = process.env.PORT || 3000;

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

Creating the Middleware

Next lets create the middleware that will intercept and handle authentication in our application

  • Create the middleware:
// middleware/auth.js
const jwt = require('jsonwebtoken');

const authenticateToken = (req, res, next) => {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];

    if (token == null) return res.sendStatus(401); // If no token, unauthorized

    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        if (err) return res.sendStatus(403); // If invalid token, forbidden
        req.user = user;
        next(); // Proceed to the next middleware or route handler
    });
};

module.exports = authenticateToken;
  • Protecting routes using the middleware:
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const authenticateToken = require('./middleware/auth');

const app = express();
app.use(bodyParser.json());

const PORT = process.env.PORT || 3000;

// Dummy user for demonstration
const user = { id: 1, username: 'testuser' };

// Route to login and generate a token
app.post('/login', (req, res) => {
    const username = req.body.username;

    if (username !== user.username) {
        return res.status(403).send('User not found');
    }

    const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET);
    res.json({ accessToken });
});

// A protected route
app.get('/protected', authenticateToken, (req, res) => {
    res.send('This is a protected route');
});

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

Generating and Verifying JWTs

When a user logs in, for us to verify the user, we need to generate the JWT and verify that user is having a valid token

  • Generate a JWT:

In the /login route, we create a token using ‘jwt.sign()

const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET);
  • Verify a JWT:

In the middleware, we use ‘jwt.verify()‘ to check the token’s validity

jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
});

Environment Variables

In order to keep our security keys for access token generation hidden from public , lets store our key in a .env file in the server and load that in our application when needed

Create a ‘.env‘ file in the project root:

ACCESS_TOKEN_SECRET=secret_key

Load these variables in the application using ‘dotenv‘:

npm install dotenv
// server.js
require('dotenv').config();

Conclusion

By implementing custom middleware with JWT, we can secure our Node.js applications with a robust authentication mechanism. This approach ensures that only authenticated users can access protected routes, enhancing the overall security of our application.

Understanding How WebSockets Work: A Deep Dive

As a follow up to my previous project example of using Socket.io , I wanted to do a deep dove in to WebSockets and the core behind WebSockets. In today’s web-driven world, real-time communication is becoming increasingly essential. Whether it’s for live chat applications, real-time notifications, or collaborative tools, traditional HTTP protocols fall short due to their request-response nature. WebSockets, however, offer a solution by enabling full-duplex communication channels over a single TCP connection. This article explores how WebSockets work and the principles behind them, providing you with a comprehensive understanding of this powerful technology.

So What Are WebSockets?

WebSockets are a protocol providing full-duplex communication channels over a single, long-lived TCP connection. Unlike HTTP, which follows a request-response model, WebSockets allow for bidirectional communication between the client and server, enabling real-time data exchange with minimal latency.

The WebSocket Handshake

The WebSocket communication starts with a handshake, initiated by the client. The client sends an HTTP request to the server to upgrade the connection from HTTP to WebSocket. Here’s what the initial handshake looks like

Client Request

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13

Server Response

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
  • Upgrade Header: Indicates that the client wishes to upgrade the connection to a WebSocket.
  • Connection Header: Indicates that the connection should be upgraded.
  • Sec-WebSocket-Key: A base64-encoded random value generated by the client, used for security purposes.
  • Sec-WebSocket-Accept: A response from the server, which is a hashed value generated from the Sec-WebSocket-Key and ensures the handshake integrity.

If the server supports WebSockets, it responds with a ‘101 Switching Protocols‘ status code, and the connection is upgraded.

Establishing the Connection

Once the handshake is complete, the connection is established, and both the client and server can send messages to each other freely. Unlike HTTP, where the client must initiate every request, WebSockets allow for server-initiated messages, enabling real-time data push.

WebSocket Frames

Data sent over WebSocket is transmitted in frames. There are different types of frames, such as text frames, binary frames, ping frames, and pong frames. Each frame consists of a header and a payload

  • FIN: Indicates if this is the final fragment in a message.
  • Opcode: Defines the type of frame (e.g., text, binary, close, ping, pong).
  • Mask: Indicates if the payload data is masked (client-to-server frames must be masked).
  • Payload Length: Length of the payload data.
  • Payload Data: The actual message data being transmitted.

Example: Real-Time Chat Application

To illustrate how WebSockets work in practice, let’s build a simple real-time chat application using Node.js and the WebSocket library

Server Code

const WebSocket = require('ws');

const server = new WebSocket.Server({ port: 8080 });

server.on('connection', (socket) => {
    console.log('A user connected');

    socket.on('message', (message) => {
        console.log('Received:', message);
        // Broadcast the message to all connected clients
        server.clients.forEach((client) => {
            if (client !== socket && client.readyState === WebSocket.OPEN) {
                client.send(message);
            }
        });
    });

    socket.on('close', () => {
        console.log('A user disconnected');
    });
});

console.log('WebSocket server is running on ws://localhost:8080');

Client Code

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Chat</title>
</head>
<body>
    <input type="text" id="message" placeholder="Enter your message">
    <button onclick="sendMessage()">Send</button>
    <div id="chat"></div>

    <script>
        const socket = new WebSocket('ws://localhost:8080');

        socket.onopen = () => {
            console.log('Connected to the server');
        };

        socket.onmessage = (event) => {
            const chat = document.getElementById('chat');
            const message = document.createElement('div');
            message.textContent = event.data;
            chat.appendChild(message);
        };

        socket.onclose = () => {
            console.log('Disconnected from the server');
        };

        function sendMessage() {
            const input = document.getElementById('message');
            const message = input.value;
            socket.send(message);
            input.value = '';
        }
    </script>
</body>
</html>

Why WebSockets

  • Low Latency: Provides real-time updates with minimal delay.
  • Efficient: Reduces overhead by maintaining a single, persistent connection.
  • Bidirectional Communication: Allows for server-initiated messages.
  • Scalability: Supports large-scale, real-time applications like chats, games, and collaborative tools.

Conclusion

WebSockets are a powerful tool for enabling real-time, bidirectional communication in web applications. By maintaining a persistent connection between the client and server, they allow for instant updates and efficient data exchange, which is crucial for modern interactive applications. Understanding the principles behind WebSockets and how they work will enable you to build more dynamic and responsive web applications, enhancing user experience and engagement.

The Power of WebSockets and Socket.io in Real-time Applications

In the age of real-time applications, our ability to provide instant updates to users is a significant advantage. Traditional HTTP protocols are not designed for persistent, real-time communication, which is where WebSockets come into play. WebSockets allow for full-duplex communication channels over a single TCP connection, making them ideal for applications requiring constant data exchange without the overhead of repeatedly opening and closing connections.

Socket.io, a JavaScript library, builds on top of WebSockets and offers additional capabilities like automatic reconnection, multiplexing, and broadcasting, making real-time application development more accessible and robust.

A Collaborative Google Sheets-Like Application

I recently worked on an exciting project where we developed a Google Sheets-like application for a logistic industry company, allowing multiple users to collaborate in real-time. The core feature was that any update to a cell would be instantly visible to all users. Moreover, when a user started editing a cell, it would be highlighted and locked for other users until the editing was complete.

Key Features Implemented:

  • Real-time Cell Updates: As one user updates a cell, all other users see the changes instantly.
  • Cell Locking Mechanism: The cell being edited is highlighted and locked for other users, preventing simultaneous edits.
  • Color Change Notification: The cell being edited changes color, indicating another user is working on it.

To achieve these features, we utilized WebSockets and Socket.io for real-time communication between the server and clients.

Usage of WebSockets and Socket.io

As WebSockets provide a persistent connection between the client and server, enabling real-time data exchange with minimal latency. This was crucial for an applications like ours where delays in updates could lead to conflicts or confusion among users.

Socket.io simplifies WebSocket implementation by handling the complexities of real-time communication, such as reconnection, event handling, and cross-browser compatibility. It also supports additional features like rooms and namespaces, which can be used to segment communication to specific groups of users.

Simplified Backend Code Implementation

Below is a simplified version of our backend code using Node.js and Socket.io

const express = require('express');
const http = require('http');
const socketIo = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);

let sheetsData = {}; // A placeholder for our sheet data storage, once completed this will be sent to our document db.

io.on('connection', (socket) => {
    console.log('A user connected');

    // Handling cell update
    socket.on('cellUpdate', (data) => {
        const { sheetId, cellId, newValue } = data;

        // Update the cell in our data store
        if (!sheetsData[sheetId]) {
            sheetsData[sheetId] = {};
        }
        sheetsData[sheetId][cellId] = newValue;

        // Notify all other users about the cell update
        socket.broadcast.to(sheetId).emit('cellUpdated', { cellId, newValue });
    });

    // Handling cell lock
    socket.on('lockCell', (data) => {
        const { sheetId, cellId } = data;
        
        // Notify all other users to lock the cell
        socket.broadcast.to(sheetId).emit('cellLocked', { cellId });
    });

    // Handling cell unlock
    socket.on('unlockCell', (data) => {
        const { sheetId, cellId } = data;
        
        // Notify all other users to unlock the cell
        socket.broadcast.to(sheetId).emit('cellUnlocked', { cellId });
    });

    // Joining a specific sheet room
    socket.on('joinSheet', (sheetId) => {
        socket.join(sheetId);
    });

    socket.on('disconnect', () => {
        console.log('A user disconnected');
    });
});

server.listen(3000, () => {
    console.log('Server is running on port 3000');
});

Conclusion

WebSockets and Socket.io have transformed the way we develop real-time applications. By enabling low-latency, persistent communication between clients and servers, these technologies allow us to build collaborative, interactive applications efficiently. The project described above demonstrates the power and flexibility of WebSockets and Socket.io in creating a seamless user experience for real-time collaboration.

By leveraging these tools, we were able to provide a robust solution that ensured all users remained synchronized, thus enhancing productivity and collaboration. Whether you’re developing a collaborative tool, a chat application, or any other real-time service, WebSockets and Socket.io are a good option and is worth mastering.

The frontend implementation for this will be given in a separate article with the full ReactJS code.

About Node.js Worker Threads and Multithreading Misconceptions

Introduction

Node.js is renowned for its single-threaded, non-blocking architecture, which is powered by the event loop and ‘libuv‘. However, this has led to a common misconception that Node.js cannot utilize multiple threads for concurrent operations. This article demystifies Node.js’s threading capabilities by introducing Worker Threads and explaining how they can be used for long-running tasks, such as database operations.

The Role of libuv in Node.js

Node.js uses ‘libuv‘, a multi-platform support library with a focus on asynchronous I/O. ‘libuv‘ manages a thread pool, which can handle file system operations, DNS resolution, and other non-JavaScript operations that require asynchronous execution. While JavaScript code runs on a single thread, ‘libuv‘ ensures that blocking operations are offloaded to these worker threads.

Node.js Worker Threads

Introduced in Node.js 10.5.0, Worker Threads provide a way to run JavaScript code in parallel threads. This is particularly useful for CPU-intensive tasks and operations that would otherwise block the event loop.

Example: Using Worker Threads for a Long-Running Database Operation

Let’s explore a practical example of using Worker Threads to handle a long-running database operation without blocking the main thread.

  • Initialize the project and install dependencies:
mkdir node-worker-example
cd node-worker-example
npm init -y
npm install express mysql2 
  • Create the project structure:
  • Worker Thread Implementation:

Create a worker.js file to define the worker:

// worker.js
const { parentPort } = require('worker_threads');
const mysql = require('mysql2/promise');

const dbConfig = {
    host: 'localhost',
    user: 'db_user',
    password: 'db_pw',
    database: 'db'
};

async function longRunningDatabaseOperation() {
    const connection = await mysql.createConnection(dbConfig);
    try {
        const [rows] = await connection.execute('SELECT SLEEP(10); SELECT * FROM test_table');
        return rows;
    } catch (error) {
        throw error;
    } finally {
        await connection.end();
    }
}

parentPort.on('message', async (message) => {
    if (message === 'start') {
        try {
            const result = await longRunningDatabaseOperation();
            parentPort.postMessage({ status: 'success', data: result });
        } catch (error) {
            parentPort.postMessage({ status: 'error', error: error.message });
        }
    }
});
  • Main Application Setup:

Create an index.js file for the main application:

// index.js
const express = require('express');
const { Worker } = require('worker_threads');

const app = express();
const port = 3000;

app.get('/non-blocking', (req, res) => {
    res.json({ status: 'non blocking finished...' });
}
app.get('/start-operation', (req, res) => {
    const worker = new Worker('./worker.js');

    worker.on('message', (message) => {
        if (message.status === 'success') {
            res.json({ status: 'completed', data: message.data });
        } else {
            res.status(500).json({ status: 'error', error: message.error });
        }
    });

    worker.on('error', (error) => {
        res.status(500).json({ status: 'error', error: error.message });
    });

    worker.on('exit', (code) => {
        if (code !== 0) {
            console.error(`Worker stopped with exit code ${code}`);
        }
    });

    worker.postMessage('start');
    res.json({ status: 'processing' });
});

app.get('/', (req, res) => {
    res.send('Hello, world!');
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

This example demonstrates how to leverage Worker Threads for a long-running database operation, ensuring that other requests continue to be processed efficiently. Happy coding!

Caveats

While Worker Threads in Node.js offer significant advantages for handling CPU-intensive and long-running tasks, they also come with some drawbacks.

  • Increased Complexity
    • Concurrency Issues: Introducing multiple threads can lead to issues such as race conditions, deadlocks, and other concurrency-related bugs, making the code harder to debug and maintain.
    • Communication Overhead: Communicating between the main thread and worker threads requires serialization and deserialization of messages, which can introduce overhead and complexity.
  • Performance Considerations
    • Thread Creation Overhead: Creating and managing worker threads incurs some overhead, which might negate the performance benefits for small or simple tasks.
    • Resource Consumption: Each worker thread consumes additional memory and CPU resources. For tasks that are not CPU-bound or do not benefit significantly from parallel execution, this can lead to inefficiencies.
  • Debugging and Profiling
    • Complex Debugging: Debugging issues in a multithreaded environment is generally more complex than in a single-threaded one. Tools and techniques for debugging need to account for the parallel execution context.
    • Profiling Challenges: Performance profiling in a multithreaded application can be more challenging, as it requires analyzing multiple execution contexts simultaneously.
  • Compatibility and Ecosystem
    • Module Compatibility: Not all Node.js modules are thread-safe or designed to work in a multithreaded environment. This can limit the choice of modules or require additional effort to ensure compatibility.
    • Library Support: While many libraries are compatible with Worker Threads, some may not be, or they may require additional configuration to work correctly in a multithreaded context.
  • Development Overhead
    • Learning Curve: Developers need to understand the nuances of working with threads, including thread synchronization, message passing, and potential pitfalls of concurrent execution.
    • Increased Code Complexity: Managing worker threads and ensuring proper communication and synchronization can increase the overall complexity of the application codebase.
  • Use Case Suitability
    • Not Always Necessary: For many I/O-bound tasks, Node.js’s non-blocking, asynchronous nature provides sufficient performance without the need for Worker Threads. Using Worker Threads for such tasks may not provide significant benefits and can complicate the architecture unnecessarily.

Conclusion

Node.js’s Worker Threads offer a powerful way to handle CPU-intensive and long-running tasks without blocking the main thread. This capability, coupled with libuv‘s asynchronous I/O operations, debunks the myth that Node.js cannot handle multithreading. By using Worker Threads, developers can build more efficient and responsive applications.

While Worker Threads provide powerful capabilities for handling CPU-intensive and long-running tasks in Node.js, they also introduce complexity and potential performance issues. It is essential to evaluate whether the benefits of using Worker Threads outweigh the drawbacks for your specific use case. For many applications, Node.js’s asynchronous, event-driven model may be sufficient, and Worker Threads should be used judiciously to avoid unnecessary complexity and resource consumption.

Understanding Redux: The Power of State Management in React Applications

State management is a crucial aspect of building scalable and maintainable applications. In React, managing state can become complex as your application grows. Redux is a powerful library that helps manage state efficiently and predictably. In this article, we will explore the advantages of a state management system, introduce Redux, discuss its pros and cons, and provide a real-world example of setting up and using Redux in a React app for user authentication.

Why Do We Need a State Management System?

As our application grows, managing state across various components becomes challenging. A state management system helps by:

  • Centralizing State: It provides a single source of truth for your application’s state.
  • Predictable State Updates: State updates are predictable due to strict rules on how state changes.
  • Easier Debugging: Tools like Redux DevTools make it easier to track state changes and debug issues.
  • Improved Maintainability: Centralized state management improves code organization and maintainability.

Introduction to Redux

Redux is a popular state management library for JavaScript applications, often used with React. It follows three core principles:

  • Single Source of Truth: The global state of your application is stored in an object tree within a single store.
  • State is Read-Only: The only way to change the state is by dispatching an action, an object that describes what happened.
  • Changes are Made with Pure Functions: To specify how the state tree is transformed by actions, you write pure reducers.

Advantages of Redux

  • Predictability: With a single source of truth and pure functions, state changes are predictable and easy to debug.
  • Maintainability: Clear separation of concerns makes the codebase easier to maintain.
  • Developer Tools: Redux DevTools provide powerful tools for debugging and time-traveling through state changes.
  • Community and Ecosystem: Redux has a large community and a rich ecosystem of middleware and extensions.

Disadvantages of Redux

  • Boilerplate Code: Setting up Redux requires writing a significant amount of boilerplate code.
  • Learning Curve: Understanding Redux concepts like actions, reducers, and middleware can be challenging for beginners.
  • Complexity for Small Apps: For small applications, Redux might be overkill and add unnecessary complexity.

Setting Up Redux in a React Application for User Authentication

Let’s walk through setting up Redux in a React application with a real-world example: a user authentication app.

  • Install Redux and React-Redux
npm install redux react-redux
  • Create Redux Store

Create a store.js file to set up the Redux store:

import { createStore } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);

export default store;
  • Create Reducers

Create a reducers folder with an index.js file and an auth.js file:

‘reducers/index.js’:

import { combineReducers } from 'redux';
import authReducer from './auth';

const rootReducer = combineReducers({
  auth: authReducer
});

export default rootReducer;

‘reducers/auth.js’:

const initialState = {
  isAuthenticated: false,
  user: null,
};

const authReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'LOGIN_SUCCESS':
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload,
      };
    case 'LOGOUT':
      return {
        ...state,
        isAuthenticated: false,
        user: null,
      };
    default:
      return state;
  }
};

export default authReducer;
  • Create Actions

Create an 'actions' folder with' authActions.js':

export const loginSuccess = (user) => ({
  type: 'LOGIN_SUCCESS',
  payload: user,
});

export const logout = () => ({
  type: 'LOGOUT',
});
  • Setup Provider

Wrap your app with the Provider component from 'react-redux in index.js':

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

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

Use the 'connect' function to connect your components to the Redux store. Create an 'Auth.js' component:

import React from 'react';
import { connect } from 'react-redux';
import { loginSuccess, logout } from './actions/authActions';

function Auth({ isAuthenticated, user, loginSuccess, logout }) {
  const handleLogin = () => {
    const user = { name: 'John Doe', email: 'john.doe@example.com' };
    loginSuccess(user);
  };

  const handleLogout = () => {
    logout();
  };

  return (
    <div>
      {isAuthenticated ? (
        <div>
          <h1>Welcome, {user.name}</h1>
          <button onClick={handleLogout}>Logout</button>
        </div>
      ) : (
        <div>
          <h1>Please log in</h1>
          <button onClick={handleLogin}>Login</button>
        </div>
      )}
    </div>
  );
}

const mapStateToProps = (state) => ({
  isAuthenticated: state.auth.isAuthenticated,
  user: state.auth.user,
});

const mapDispatchToProps = {
  loginSuccess,
  logout,
};

export default connect(mapStateToProps, mapDispatchToProps)(Auth);
  • Create App Component

Finally, create an 'App.js' file:

import React from 'react';
import Auth from './Auth';

function App() {
  return (
    <div className="App">
      <Auth />
    </div>
  );
}

export default App;

Folder Structure

Conclusion

Redux is a powerful state management tool that can significantly improve the predictability, maintainability, and scalability of your React applications. While it comes with a learning curve and some boilerplate, its advantages often outweigh the drawbacks, especially in large applications. By following the step-by-step guide provided, you can set up Redux in your React app and start managing state more effectively, as demonstrated with our user authentication example.

Solving jQuery Version Conflict in the Same Project

In a recent project I was leading, we faced an interesting challenge that I believe is worth sharing. Our legacy codebase heavily relied on jQuery 1.1.8, but we needed to integrate Google Maps into our application, which required a newer version of jQuery (at least 1.2). Rewriting or upgrading the entire application to use a newer jQuery version was not feasible due to time constraints and potential risk of breaking existing functionality. Here’s how we managed to use two different versions of jQuery on the same page without any conflicts.

The Problem

Our existing application used jQuery 1.1.8, and changing this version was not an option due to the extensive usage throughout the codebase. However, the Google Maps API required at least jQuery 1.2. This version incompatibility posed a significant challenge, as loading multiple versions of jQuery can lead to conflicts.

The Solution

The solution involved using jQuery’s noConflict mode to manage multiple versions on the same page. Here’s a step-by-step guide on how we implemented this.

  • Load the Older Version of jQuery

First, we ensured that the older version of jQuery (1.1.8) was loaded as usual for our existing application functionality.

<!DOCTYPE html>
<html>
<head>
  <title>Multiple jQuery Versions Example</title>
  <!-- Load the older version of jQuery (1.1.8) -->
  <script src="path/to/jquery-1.1.8.min.js"></script>
</head>
<body>
  <!-- Our existing code and scripts that depends on jQuery 1.1.8 -->

  <!-- Google Maps section -->
  <div id="map-canvas" style="width: 100%; height: 400px;"></div>

  <!-- Load the newer version of jQuery -->
  
  <!-- Load the Google Maps API -->
  <script src="https://maps.googleapis.com/maps/api/js?key=<OUR API KEY>"></script>
</body>
</html>
  • Load the Newer Version of jQuery

Next, we loaded the newer version of jQuery required for the Google Maps API. We used the noConflict method to ensure it did not interfere with the older version.

<!-- Load the newer version of jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
  var jQueryNew = $.noConflict(true);
</script>

The true parameter in the noConflict method call ensures that the new jQuery version is completely isolated, preventing it from overriding the older version.

  • Initialize Google Maps Using the Newer jQuery Version

We then wrote the Google Maps initialization code using the new jQuery version (jQueryNew).

<script>
  jQueryNew(document).ready(function($) {
    function initialize() {
      var mapOptions = {
        zoom: 8,
        center: new google.maps.LatLng(-34.397, 150.644)
      };
      var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
    }
    
    google.maps.event.addDomListener(window, 'load', initialize);
  });
</script>

Complete solution looked like

<!DOCTYPE html>
<html>
<head>
  <title>Multiple jQuery Versions Example</title>
  <!-- Load the older version of jQuery (1.1.8) -->
  <script src="path/to/jquery-1.1.8.min.js"></script>
</head>
<body>
  <!-- Your existing content that depends on jQuery 1.1.8 -->

  <!-- Google Maps section -->
  <div id="map-canvas" style="width: 100%; height: 400px;"></div>

  <!-- Load the newer version of jQuery -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <script>
    var jQueryNew = $.noConflict(true);

    jQueryNew(document).ready(function($) {
      function initialize() {
        var mapOptions = {
          zoom: 8,
          center: new google.maps.LatLng(-34.397, 150.644)
        };
        var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
      }
      
      google.maps.event.addDomListener(window, 'load', initialize);
    });
  </script>

  <!-- Load the Google Maps API -->
  <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY"></script>
</body>
</html>

Conclusion

By using jQuery’s noConflict mode, we successfully integrated Google Maps into our project without upgrading the entire codebase to a newer jQuery version. This approach allowed us to meet the project requirements while minimizing risks and maintaining the stability of our existing application.

I hope this solution helps others facing similar challenges. If you have any questions or need further clarification, feel free to leave a comment!

Understanding Higher-Order Components (HOCs) in React with a Real-World Example

Higher-Order Components (HOCs) in React can seem like a complex concept, especially if you’re not deeply embedded in the React ecosystem. However, they are a powerful tool for managing component logic and reusability. In this article, I’ll try to demystify HOCs and illustrate their usage with a relatable real-world example.

What is a Higher-Order Component?

In simple terms, a Higher-Order Component (HOC) is a function that takes a component and returns a new component with added functionality. It’s a pattern used to share common logic between multiple components without repeating code.

Think of HOCs as decorators in a coffee shop. You have a basic coffee, and you can enhance it by adding milk, sugar, or flavors, making it a cappuccino, latte, or vanilla coffee. The basic coffee remains the same, but the enhancements (HOCs) provide additional features.

Real-World Example: Access Control in a Web Application

Imagine a web application where certain pages are restricted to users with specific roles, such as admin or manager. We need a way to enforce this access control across various components without duplicating the logic. This is where HOCs come in handy.

Step-by-Step Implementation

  • Basic Component

First, let’s create a basic component that displays a dashboard.

import React from 'react';

function Dashboard() {
  return <div>Welcome to Admin Dashboard</div>;
}

export default Dashboard;
  • HOC for Access Control

Next, we’ll create an HOC that checks if the user has the required role to view the component.

import React from 'react';

function withAuthorization(WrappedComponent, allowedRoles) {
  return function(props) {
    const { user } = props;

    if (allowedRoles.includes(user.role)) {
      return <WrappedComponent {...props} />;
    } else {
      return <div>Access Denied</div>;
    }
  };
}

export default withAuthorization;

This ‘withAuthorization ‘HOC takes two arguments: the ‘WrappedComponent‘ (the component to be enhanced) and ‘allowedRoles‘ (an array of roles permitted to view the component). It returns a new component that either renders the WrappedComponent or displays an “Access Denied” message based on the user’s role.

  • Using the HOC

Now, let’s use the ‘withAuthorization‘ HOC to protect the ‘Dashboard‘ component.

import React from 'react';
import Dashboard from './Dashboard';
import withAuthorization from './withAuthorization';

const user = { role: 'admin' }; // Example user object

const AuthorizedDashboard = withAuthorization(Dashboard, ['admin', 'manager']);

function App() {
  return (
    <div>
      <AuthorizedDashboard user={user} />
    </div>
  );
}

export default App;

In this example, we create an ‘AuthorizedDashboard‘ by wrapping the Dashboard component with the ‘withAuthorization‘ HOC. We specify that only users with the role of ‘admin’ or ‘manager’ can access this component.

  • Rendering the Application

When the App component is rendered, the ‘AuthorizedDashboard‘ will check the user’s role. If the user’s role is included in the allowed roles, the Dashboard will be displayed. Otherwise, an “Access Denied” message will appear.

Benefits of Using HOCs

  1. Code Reusability: HOCs allow you to encapsulate reusable logic in a single place. This makes your code more modular and maintainable.
  2. Separation of Concerns: HOCs help separate the logic of enhancing components from the components themselves. This keeps components focused on their primary purpose: rendering UI.
  3. Consistency: By using HOCs, you ensure consistent behavior across your application. For instance, access control logic implemented in an HOC will be consistently applied to all components that use it.

Conclusion

Higher-Order Components (HOCs) are a powerful pattern in React for enhancing components with reusable logic. By abstracting common functionality, such as access control, into HOCs, you can keep your code DRY (Don’t Repeat Yourself) and maintainable.

In this real-world example, it’s demonstrated how to use an HOC to manage access control in a web application. This pattern can be extended to various scenarios, such as logging, error handling, and more.

Understanding and leveraging HOCs can significantly improve your React development process, leading to cleaner, more efficient, and scalable code.

ReactJS Class Components vs Functional Components – a quick tour

Recently my team was tasked with a project that was developed in ReactJS to be modernized and made more performance optimized. The project was developed using a mix of class components and functional components, without any state management nor any performance considerations for large amounts of data payloads from server. We decided to move the project from class components to functional components, add redux for state management and bring in several performance optimizations. When working with the project I realized that the original development was done without understanding subtle difference and similarities between class components and functional components as well as about the usage of life cycle hooks.

Hence, I thought of sharing some key and fundamental difference between the two so that this could be used as a reference in the future.

  • Component Definition

Functional Components

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

Class Components

import React, { Component } from 'react';

class MyComponent extends Component {
  render() {
    return <div>Hello, {this.props.name}!</div>;
  }
}
  • State Management

Functional Component with ‘useState

import React, { useState } from 'react';

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

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

Class Component with ‘this.state

import React, { Component } from 'react';

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

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}
  • Lifecycle Methods

Functional Component with ‘useEffect

import React, { useEffect } from 'react';

function Timer() {
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Tick');
    }, 1000);

    return () => clearInterval(timer);
  }, []);

  return <div>Check the console</div>;
}

Class Component Lifecycle Methods

import React, { Component } from 'react';

class Timer extends Component {
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('Tick');
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  render() {
    return <div>Check the console</div>;
  }
}
  • Handling Events

Functional Component

import React from 'react';

function ClickHandler() {
  const handleClick = () => {
    console.log('Button clicked');
  };

  return <button onClick={handleClick}>Click me</button>;
}

Class Component

import React, { Component } from 'react';

class ClickHandler extends Component {
  handleClick = () => {
    console.log('Button clicked');
  };

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}
  • Props and State Comparison

Functional Component

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

function ParentComponent() {
  const [name, setName] = useState('World');

  return (
    <div>
      <Greeting name={name} />
      <button onClick={() => setName('React')}>Change Name</button>
    </div>
  );
}

Class Component

import React, { Component } from 'react';

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

class ParentComponent extends Component {
  constructor(props) {
    super(props);
    this.state = { name: 'World' };
  }

  render() {
    return (
      <div>
        <Greeting name={this.state.name} />
        <button onClick={() => this.setState({ name: 'React' })}>Change Name</button>
      </div>
    );
  }
}
  • Context API

Functional Component with ‘useContext

import React, { createContext, useContext } from 'react';

const MyContext = createContext();

function ChildComponent() {
  const value = useContext(MyContext);
  return <div>{value}</div>;
}

function ParentComponent() {
  return (
    <MyContext.Provider value="Hello from Context">
      <ChildComponent />
    </MyContext.Provider>
  );
}

Class Component with ‘Context.Consumer

import React, { Component, createContext } from 'react';

const MyContext = createContext();

class ChildComponent extends Component {
  render() {
    return (
      <MyContext.Consumer>
        {value => <div>{value}</div>}
      </MyContext.Consumer>
    );
  }
}

class ParentComponent extends Component {
  render() {
    return (
      <MyContext.Provider value="Hello from Context">
        <ChildComponent />
      </MyContext.Provider>
    );
  }
}
  • Higher-Order Components (HOCs)

Functional Component (HOC)

import React from 'react';

function withLogging(WrappedComponent) {
  return function(props) {
    console.log('Component rendered with props:', props);
    return <WrappedComponent {...props} />;
  };
}

function MyComponent(props) {
  return <div>{props.message}</div>;
}

const MyComponentWithLogging = withLogging(MyComponent);

Class Component (HOC)

import React, { Component } from 'react';

function withLogging(WrappedComponent) {
  return class extends Component {
    componentDidMount() {
      console.log('Component rendered with props:', this.props);
    }

    render() {
      return <WrappedComponent {...this.props} />;
    }
  };
}

class MyComponent extends Component {
  render() {
    return <div>{this.props.message}</div>;
  }
}

const MyComponentWithLogging = withLogging(MyComponent);

Conclusion

  • State Management: Use useState in functional components and this.state in class components.
  • Side Effects: Use useEffect in functional components and lifecycle methods (componentDidMount, componentWillUnmount, etc.) in class components.
  • Event Handling: Both use similar syntax, but class components use this to refer to class methods.
  • Context API: Use useContext in functional components and Context.Consumer in class components.
  • HOCs: Higher-Order Components work similarly in both, but syntax differs slightly.

In conclusion, understanding the above key differences and similarities will help to navigate and work between functional and class components effectively.

Building a Strategic Security Roadmap – What I learnt over the years…

Several years back I had my first stint at building a strategic security road map to a client I was working with. I must say I was not a practiced and seasoned expert who knew “a-z” of building security strategies and road maps. So as usual, I just rolled up my sleeves and started digging in to finding out all I can about what needs to be done, who are involved , what should be my approach and thousand other questions I had. This did lead me down many rabbit holes, and after several years of researching, discussing with experts, working with security architects and many implementations later, I have come a little bit further down the road of implementing security strategies and I assume I am bit wiser (so I would like to think).

In this blog series I will try to impart what I have learnt so far in my journey in helping multiple clients implement security road maps from undersetting the need, planning, documenting, getting the buy in, to implementing security road maps.

So the first thing I needed to understand was the big “WHY”

Why do we need implement a security strategy in the first place? my take on it and as I see it, was for any organization to ensure that their “valuables”, such as information technology or systems or data are kept safe from unwanted access and usage.

Well, what does that mean then ?

Well, any organization has things that they value , such as data, systems, devices, or any other components that has business value to the organization, which are called “Assets“. Through a weakness in the organizations environment called “Vulnerabilities“, assets can be exploited by intruders called “Threats“. Hence these are the “Risks” that any organization will face. When building our strategy we are suppose to identify these risks and come up with plans, designs, and actions to mitigate these risks. We have to do this by putting relevant controls in place

When defining risks and their corresponding controls, we will need to consider

  1. The opportunity for threats to exploit vulnerabilities with controls for mitigation.
  2. The chance that a vulnerability will lead to a compromise by using controls for detection.
  3. The effect that a compromise has, in terms of impact by using controls to response.

In order to implement a strategy we will need to come up with a strategy that allows an organization continuously look at,

  • Planning – studying and then designing a resistant security architecture for various IT projects in line with business needs and risk acceptance
  • Developing – prerequisites for networksfirewallsrouters, and other network devices
  • Performing – vulnerability assessmentsecurity testing, and risk analysis
  • Researching – the updated security standards, systems, regulatory frameworks and best practices
  • Communicating – relevant risks, vulnerabilities, and mitigation strategies to senior leadership

So how do we go about doing this…

Well, this is just the start on my approach to enable an organization to implement a security road map. In my next articles I dive a bit deeper, by taking a case study on an actual implementation of a strategic security roadmap, what was learnt, what went right , what failed, and some tips and tricks .