Getting Started with React: Creating Your Own Blog App - Part 3

Introduction

In this article of our React blog series, we will learn how to add and display a list of blogs. We will cover basic form handling and validation, the useState React Hook, Props in React, passing values between components, and rendering a list of blogs. This blog post will enhance your understanding of React and make learning it a more straightforward process.

A quick note before we begin: we are building a blog app, and this blog series focuses on learning React through hands-on projects. This will be a basic app without any high-level concepts in React, but in the future, I will introduce some advanced concepts to enhance your understanding.

If you are new to React, I suggest you read my last two blog posts where I explain and demonstrate how to install React with Vite, along with other project setup steps. I highly encourage you to read those blog posts before continuing with this one.

Prerequisites

What will we learn in this blog post?

Before we deep dive into the code, let me share what we will be learning in this blog post. We are going to explore the React useState Hook and I will explain why useState is the most important aspect of React. Additionally, we will install some essential packages such as react-draft-wysiwyg, draftjs, and prop-types.

We will also learn about props in React and how to pass data between components. When implementing the "add blog" feature, we will explore form validation. We will dive deeper into form validation and handling once we implement the profile section in our blog.

To simplify the process, we will not use any form validation packages for the "Add blog" feature. Additionally, I will create some reusable components to demonstrate how to break down the UI and make each element or section a reusable component.

Now, let's install the packages required for our blog app.

Installing Necessary Packages

Before we start writing our code, we need some packages that will help us create and format our blog content. Many blogging platforms use a What You See Is What You Get (WYSIWYG) editor. This type of editor allows users to format text and add elements, such as images, links, and lists, using familiar tools similar to word processors. The formatting applied in the editor is visually represented, allowing users to see how their content will appear when published.

We require a rich text editor for our blog application. To achieve this, we will utilize react-draft-wysiwyg and draft-js.

Open up your VSCode terminal and navigate to your project directory, and use npm to install the react-draft-wysiwyg, draft-js, draftjs-to-html, prop-types package:

npm install react-draft-wysiwyg draft-js draftjs-to-html prop-types

Now, let me explain the purpose of these packages, their functions, and the reasons for using them in our project.

  1. draft-js: draft-js is a low-level library developed by Facebook for building rich text editors in React. It provides a framework for managing the state of the editor content, handling user input, and rendering the content with various formatting options.

    Why use draft-js: We are creating a Blog App that requires extensive customization of the rich text editing experience, draft-js gives you fine-grained control over the editor's behavior and appearance. It's a powerful choice for building complex editors tailored to your specific requirements.

  2. react-draft-wysiwyg: This package provides a set of React components to build a rich text editor with a "What You See Is What You Get" (WYSIWYG) interface. It's built on top of draft-js and abstracts many of the complexities of working with draft-js, making it easier to create and manage rich text content in your application. It offers features like text formatting (bold, italics, etc.), lists, links, images, and more.

    Why use react-draft-wysiwyg: We need to allow users to compose and format rich text content in our application, using a package like react-draft-wysiwyg we can save a significant amount of development time compared to building a custom editor from scratch.

  3. draftjs-to-html: This package is used to convert the content generated by draft-js into HTML markup. It's especially useful when we need to display the rich text content created in our editor on a webpage.

    Why use draftjs-to-html: When we want to render the content generated by draft-js editor as HTML, draftjs-to-html provides a convenient way to perform the conversion.

  4. prop-types: prop-types is a package that helps us to document and validate the props that our React components expect. It ensures that the props we pass to a component match the expected types and provides helpful warnings during development if there's a mismatch.

    Why use prop-types: Using prop-types is a good practice for maintaining code quality and catching potential bugs early. It provides clear documentation for the expected props of your components and helps you avoid runtime errors due to incorrect prop usage.

You might be wondering what props are and why we use them in our React app components.

What are props in React and why do we use them in our app components?

In React, "props" is short for "properties." Props are an excellent method for sharing data between parent and child components. They allow us to pass values or even functions to child components, which can then be used for performing specific actions or rendering data.

Props are read-only and cannot be modified by the child component that receives them. They provide a way to make your components reusable and modular, as you can customize their behaviour or appearance by passing different data to them.

Allow me to clarify with a straightforward example.

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

function ParentComponent() {
  const message = 'This is cool Child!!';

  return (
    <div>
      <ChildComponent onPassMessagetoChild={message} />
    </div>
  );
}

export default ParentComponent;
import React from 'react';

function ChildComponent(props) {
  return (
    <div>
      <p>Hey, {props.onPassMessagetoChild}!</p>
    </div>
  );
}

export default ChildComponent;

In this example, the ParentComponent passes the onPassMessagetoChild prop to the ChildComponent. The ChildComponent receives the prop through its props parameter and uses it to display a message.

Additionally, remember that props can be used to pass not only data but also functions. This enables communication between components and allows the implementation of actions triggered by user interactions.

There is another concept, called props destructuring, that we learn when creating a reusable component.

I hope you now understand what props are. If you don't fully grasp the concept, don't worry; we will be using props multiple times in our blog app. Just remember that props are an excellent way to share data between parent and child components.

What is useState in React?

Let's understand what is meant by state, State is a way to manage and store data that can change over time, like user input, API responses, or any dynamic data in your application And useState is a hook that allows you to add state to functional components.

Here are the steps on how to implement useState in React

  1. Import useState: Import the useState function from the react library at the top of your component file.

  2. Use useState: Call the useState function and pass an initial value as its argument. This returns an array with two elements: the current state value and a function to update the state.

  3. Access State: You can access the current state value just like any other variable.

  4. Update State: To update the state, call the function returned by useState, passing the new value. React will re-render the component and update the state.

This might confuse you let me help you understand with a simple example:

import React, { useState } from 'react';

function CounterApp() {
  // Using useState to manage the count state
  const [count, setCount] = useState(0);

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

export default CounterApp;

In this example, useState(0) initializes the state variable count to 0. When the "Increment" button is clicked, the setCount function is called with the new value of count + 1, causing the component to re-render with the updated state.

I hope you understand what useState is. If you don't understand it fully, don't worry; we'll be using useState extensively in our blog app.

Now, let's write some actual React code for our blog app.

Creating Reusable UI Components

As we know reactJs is famous for its component base architecture which means it can break UI into smaller and reusable components. So lets break down our add blog app into smaller components.

This is our simple blog form. As you can see, we have an input field for adding the blog title, a text editor for writing and managing blog content, and a button.

Now, let's consider how to break down our UI components. First, examine the design to identify common elements in our app and use those elements/designs as reusable components. Upon inspecting our blog app design, we can see that we need an input field on the add blog page, edit blog page, and profile page. Additionally, we need to use buttons throughout our app, so let's break these two elements into reusable components.

Creating a Custom InputText Field

Let's create a reusable input field component.

CustomInputText.jsx

import { Form } from "react-bootstrap";
import PropTypes from "prop-types";

function CustomInputText({
  inputLabel,
  inputType,
  inputPlaceholder,
  inputValidationMsg,
  inputOnChange,
  inputValue,
  inputIsValid,
}) {
  return (
    <Form.Group className="mb-3" controlId="exampleForm.ControlInput1">
      <Form.Label>{inputLabel}</Form.Label>
      <Form.Control
        type={inputType}
        placeholder={inputPlaceholder}
        onChange={inputOnChange}
        value={inputValue}
        isInvalid={inputIsValid}
      />
      <Form.Control.Feedback className="" type="invalid">
        {inputValidationMsg}
      </Form.Control.Feedback>
    </Form.Group>
  );
}

CustomInputText.propTypes = {
  inputLabel: PropTypes.string.isRequired,
  inputType: PropTypes.oneOf(["text", "number", "email", "password"]),
  inputPlaceholder: PropTypes.string.isRequired,
  inputValidationMsg: PropTypes.string.isRequired,
  inputOnChange: PropTypes.func.isRequired,
  inputValue: PropTypes.string,
  inputIsValid: PropTypes.bool.isRequired,
};

export default CustomInputText;

Let me explain this code

  • Form from "react-bootstrap": This imports the Form component from the react-bootstrap library.

  • PropTypes from "prop-types": This imports the PropTypes object from the prop-types library.

  • It takes in several props as its parameters: inputLabel, inputType, inputPlaceholder, inputValidationMsg, inputOnChange, inputValue, and inputIsValid.

  • Form.Label: Displays the label text for the input field. The label text is taken from the inputLabel prop.

  • Form.Control: This is an input control from react-bootstrap. It has several attributes like:- type: The type of input, taken from the inputType prop. placeholder: The placeholder text for the input field, taken from the inputPlaceholder prop. onChange: The function to be called when the input value changes, taken from the inputOnChange prop. value: The current value of the input field, taken from the inputValue prop. isInvalid: A boolean that indicates whether the input is invalid, taken from the inputIsValid prop.

  • Form.Control.Feedback: This displays the validation feedback message when the input is invalid. The message is taken from the inputValidationMsg prop.

  • After the component definition, the propTypes object is defined.

  • propTypes define the expected types and requirements for each prop that the component receives. For example, inputLabel is expected to be a required string, inputType is expected to be one of the specified types, and so on.

  • The CustomInputText component is exported as the default export of the module.

This code defines a reusable CustomInputText component that can be used to render a form input field with a label, placeholder, validation message, and other attributes. It also includes PropTypes validation to ensure that the component receives the expected props with the correct types.

You might be wondering why we aren't using props here. Instead, we are utilizing prop destructuring.

Let me help you understand by providing an example.

Prop destructuring is a technique in React that simplifies working with props by extracting their values directly into the function parameter list. This approach is particularly beneficial when managing multiple props.

Prop destructuring enhances code readability and minimizes redundancy, resulting in more concise and easily maintainable component code.

Here's a simple breakdown of prop restructuring:

Before using Prop destructuring:

function Person(props) {
  return (
    <div>
      <p>Name: {props.name}</p>
      <p>Age: {props.age}</p>
    </div>
  );
}

After using Prop destructuring:

function Person({ name, age }) {
  return (
    <div>
      <p>Name: {name}</p>
      <p>Age: {age}</p>
    </div>
  );
}

instead of using props.name and props.age, we're directly destructuring the name and age props from the props object. This makes the code shorter, cleaner, and easier to understand.

Now you understand what prop destructuring is and why we use it.

Creating a Custom Button

Lets create a reusable button component

CustomButton.jsx

import { Button } from "react-bootstrap";
import PropTypes from "prop-types";

function CustomButton({
  buttonType,
  buttonText,
  buttonClassName,
  buttonOnClick,
  isDisabled,
}) {
  return (
    <Button
      className={buttonClassName}
      type={buttonType}
      onClick={buttonOnClick}
      disabled={isDisabled}
    >
      {buttonText}
    </Button>
  );
}

CustomButton.propTypes = {
  buttonType: PropTypes.oneOf(["button", "submit", "reset"]),
  buttonText: PropTypes.string.isRequired,
  buttonClassName: PropTypes.string,
  buttonOnClick: PropTypes.func.isRequired,
  isDisabled: PropTypes.bool,
};

CustomButton.defaultProps = {
  buttonType: "button",
  buttonClassName: "",
};

export default CustomButton;

Let me explain this code

  • Button from "react-bootstrap": This imports the Button component from the react-bootstrap library.

  • PropTypes from "prop-types": This imports the PropTypes object from the prop-types library.

  • CustomButton component takes in several props as its parameters: buttonType, buttonText, buttonClassName, buttonOnClick, and isDisabled

  • className: The class name for the button, taken from the buttonClassName prop.

  • type: The type of the button (e.g., submit, reset), taken from the buttonType prop.

  • onClick: The function to be called when the button is clicked, taken from the buttonOnClick prop.

  • disabled: A boolean indicating whether the button is disabled, taken from the isDisabled prop.

  • The content of the button is set using the buttonText prop. This is the text that will be displayed on the button.

  • propTypes define the expected types and requirements for each prop that the component receives.

  • For example, buttonType is expected to be one of the specified types, buttonText is expected to be a required string, and so on.

  • The defaultProps object is defined to provide default values for props that are not specified.

  • For example, buttonType is set to "button" by default, and buttonClassName is an empty string by default.

  • The CustomButton component is exported as the default export of the module.

this code defines a reusable CustomButton component that creates a styled button using the Button component from react-bootstrap. It allows you to customize the button's type, text, class name, and behavior using props.

The PropTypes validation ensures that the component receives the expected props with the correct types. Default props provide fallback values if certain props are not provided.

Now you understand how to create a reusable UI component. Your task is to create each reusable component that will be used in this app.

Creating an "Add Blog" Form

We have created some reusable UI components, and you now know how to create them. Let's put this knowledge into action and create our Add Blog form.

blog.jsx

// Import necessary components and libraries
import { Row, Container, Form } from "react-bootstrap";
import { useState } from "react";
import { convertToRaw, EditorState } from "draft-js";
import { Editor } from "react-draft-wysiwyg";
import draftToHtml from "draftjs-to-html";
import PropTypes from "prop-types";

// Import local utility and component files
import CustomInputText from "../components/customInputText";
import CustomButton from "../components/customButton";
import CustomAlert from "../components/customAlert";
import formatDateTime from "../utils/format_date";
import isEditorEmpty from "../utils/editor_empty";
import "../css/button.css";
import "../css/blog.css";

// Create a functional component named "Blog"
function Blog({ onPassDatatoAppComponent }) {
  // UseState Hooks to manage various states
  const [blogTitle, setBlogTitle] = useState("");
  const [blogContent, setBlogContent] = useState(EditorState.createEmpty());
  const [formSubmitted, setFormSubmitted] = useState(false);
  const [alert, setAlert] = useState(false);
  const currentDate = new Date();

  // Function triggered when the "Publish Blog" button is clicked
  const onSubmitBtnClick = (e) => {
    e.preventDefault(); // Prevent the default form submission behavior

    // Handling form submission
    if (blogTitle && !isEditorEmpty(blogContent)) {
      // If both title and content are provided
      // Call the prop function to pass data
      onPassDatatoAppComponent({
        title: blogTitle,
        content: draftToHtml(convertToRaw(blogContent.getCurrentContent())),
        date: formatDateTime(currentDate),
      });
      // Reset form submission status
      setFormSubmitted(false);
      // Show success alert
      setAlert(true);
      // Clear title and content fields
      setBlogTitle("");
      setBlogContent(EditorState.createEmpty());
      // Hide the alert after a delay
      setTimeout(() => {
        setAlert(false);
      }, 3000);
    } else {
      // If title or content is missing
      // Set form submission status and show a warning alert
      setFormSubmitted(true);
      setAlert(true);
      // Hide the alert after a delay
      setTimeout(() => {
        setAlert(false);
      }, 3000);
    }
  };

  // Return JSX for rendering
  return (
    <Container>
      <Row style={{ paddingTop: 90 }}>
        <Form className="mt-3">
          {/* Render the custom input text component */}
          <CustomInputText
            inputLabel={"Blog Title"}
            inputPlaceholder={"Enter your Blog Title"}
            inputType={"text"}
            inputValidationMsg={"Please add Blog Title"}
            inputValue={blogTitle}
            inputOnChange={(e) => setBlogTitle(e.target.value)}
            inputIsValid={formSubmitted && blogTitle === ""}
          />

          {/* Render the editor component */}
          <Form.Group className="mb-3" controlId="exampleForm.ControlTextarea1">
            <Form.Label>Write your blog</Form.Label>
            <Editor
              defaultEditorState={blogContent}
              onEditorStateChange={setBlogContent}
            />
            <Form.Control.Feedback
              style={{
                display:
                  formSubmitted && isEditorEmpty(blogContent)
                    ? "block"
                    : "none",
              }}
              type="invalid"
            >
              Please add blog content.
            </Form.Control.Feedback>
          </Form.Group>

          {/* Render the alert component based on conditions */}
          {isEditorEmpty(blogContent) && blogTitle === "" && formSubmitted ? (
            <CustomAlert
              isVisible={alert}
              alertMessage="Please fill all required forms โŒ"
              alertClass="danger"
            />
          ) : (
            <CustomAlert
              isVisible={alert}
              alertMessage="Your blog publish successfully ๐ŸŽ‰"
              alertClass="success"
            />
          )}

          {/* Render the custom button component */}
          <CustomButton
            buttonText={"Publish Blog"}
            buttonType={"submit"}
            buttonClassName={"btn btn-custom w-100"}
            buttonOnClick={(e) => onSubmitBtnClick(e)}
          />
        </Form>
      </Row>
    </Container>
  );
}

// Prop type validation for the "onPassDatatoAppComponent" prop
Blog.propTypes = {
  onPassDatatoAppComponent: PropTypes.any,
};

// Export the "Blog" component as the default export
export default Blog;

Let's break it down this code step by step:

  • Various components and utilities are imported from different libraries and local files.

  • Functional Component - Blog:

    • This is a functional component named Blog.

    • It accepts a prop called onPassDatatoAppComponent.

  • UseState Hooks:

    • Multiple useState hooks are used to manage component state. These hooks store values for blog title, blog content, form submission status, alert status, and current date.
  • onSubmitBtnClick Function:

    • This function is called when the "Publish Blog" button is clicked.

    • It prevents the default form submission behaviour.

    • It handles form submission:

      • If both blog title and content are filled, it:

        • Calls the onPassDatatoAppComponent prop function with data.

        • Resets form submission status, shows a success alert, clears title and content, and hides the alert after a 3-second delay.

      • If either the blog title or content is missing, it:

        • Sets form submission status, shows a warning alert and hides the alert after a 3-second delay.
  • Component JSX:

    • The component returns JSX, which defines the structure and content of the blog form.

    • It uses the CustomInputText, Editor, and CustomButton components.

  • CustomInputText Component:

    • It's a custom component that provides an input field for the blog title.

    • It takes various props for customization.

  • Editor Component:

    • It's a rich text editor from the react-draft-wysiwyg library for composing blog content.

    • It uses the EditorState from draft-js to manage the editor's state.

  • Alerts:

    • Depending on form submission status and content, different alerts are displayed using the CustomAlert component.
  • CustomButton Component:

    • It's a custom button component for submitting the blog.

    • It triggers the onSubmitBtnClick function on click.

  • PropTypes Validation:

    • The propTypes object is defined to specify the expected types for the onPassDatatoAppComponent prop.
  • Export:

    • The Blog component is exported as the default export of the module.

This code creates a blog creation form using various libraries, custom components, and custom utility functions. It handles state management, form submission, and validation. The form includes input fields for the blog title and content, and a rich text editor is used to compose the blog content.

By using custom components for different parts of the form, enhancing code readability and maintainability.

The code draftToHtml(convertToRaw(blogContent.getCurrentContent())) is used to convert the content from the EditorState (blogContent) of the rich text editor into HTML format. This HTML content can then be used, for example, to display the blog content in a formatted way on a webpage or to save it as HTML data in a database. It's a common approach when you want to handle rich text content created with draft-js in a web application.

Note: The editor dropdown will not function properly due to React Strict Mode. To resolve this issue, remove React Strict Mode from the main.jsx file.

Creating a "Blog List" Page

homepage.jsx

import { Stack, Container, Row, Col, Form } from "react-bootstrap";
import PropTypes from "prop-types";

// Local Imports
import BlogItem from "../components/blogItem";
import "../css/homepage.css";

function Homepage({ blogListData }) {
  return (
    <div className="main-content">
      <Container>
        <section id="search-section">
          <Row>
            <Col>
              <div className="searchfield">
                <Form.Control type="text" placeholder="Search Blog" />
                <i className="fa fa-search" aria-hidden="true"></i>
              </div>
            </Col>
          </Row>
        </section>
        <section id="bloglist-title-section">
          <div className="bloglist-title">
            <h2>All Blogs</h2>
            <i className="fa fa-file-text-o" aria-hidden="true"></i>
          </div>
        </section>
        <section id="bloglist-section">
          <Stack gap={3}>
            {blogListData.length === 0 ? (
              <p style={{ textAlign: "center" }}>No Blogs are available ๐Ÿ˜”</p>
            ) : (
              blogListData.map((blog, i) => (
                <BlogItem
                  key={i}
                  blogTitle={blog.title}
                  blogAuthor={"Pradhuman Padhiyar"}
                  blogTimeAndDate={blog.date}
                  blogAuthorImage={
                    "https://api.dicebear.com/6.x/open-peeps/svg?face=angryWithFang,calm,blank"
                  }
                />
              ))
            )}
          </Stack>
        </section>
      </Container>
    </div>
  );
}

Homepage.propTypes = {
  blogListData: PropTypes.any,
};

export default Homepage;
  • The necessary components and libraries are imported, including Stack, Container, Row, Col, Form from react-bootstrap and PropTypes for prop type validation.

  • The local component BlogItem and a stylesheet homepage.css are also imported.

  • Functional Component - Homepage:

    • This is a functional component named Homepage.

    • It accepts a single prop called blogListData.

  • Component JSX:

    • The component returns JSX, which defines the structure and content of the homepage.
  • Search Section:

    • This section contains a search field using Form.Control from react-bootstrap. An icon from the FontAwesome library (fa fa-search) is also included.
  • Blog List Title Section:

    • This section displays the title "All Blogs" along with an icon (fa fa-file-text-o).
  • Blog List Section:

    • This section uses the Stack component from react-bootstrap to create a gap between blog items.

    • The content inside the Stack component is conditionally rendered based on the length of blogListData.

    • If blogListData is empty, a message "No Blogs are available ๐Ÿ˜”" is displayed.

    • If blogListData has items, the blogListData array is mapped over, and for each blog item, the BlogItem component is rendered with specific props.

  • BlogItem Component:

    • This component is rendered for each blog item.

    • It receives props like blogTitle, blogAuthor, blogTimeAndDate, and blogAuthorImage.

  • Prop Type Validation:

    • The propTypes object is defined to specify the expected type for the blogListData prop.
  • Export:

    • The Homepage component is exported as the default export of the module.

app.jsx

import React, { useState } from "react";
import { Routes, Route } from "react-router-dom";

//Local Imports
import Header from "./components/header";
import Footer from "./components/footer";
import Homepage from "./pages/homepage";
import Blog from "./pages/blog";
import Profile from "./pages/profile";
import NoPageFound from "./pages/no_page_found";
import "./App.css";

function App() {
  const [blogListData, setBlogListData] = useState([]);

  const onGetDataFromBlogComponent = (blogData) => {
    setBlogListData((prevDataArray) => [blogData, ...prevDataArray]);
    console.log(blogData);
  };

  return (
    <React.Fragment>
      <Header />
      <Routes>
        <Route path="/" element={<Homepage blogListData={blogListData} />} />
        <Route
          path="/blog"
          element={
            <Blog onPassDatatoAppComponent={onGetDataFromBlogComponent} />
          }
        />
        <Route path="/profile" element={<Profile />} />
        <Route path="*" element={<NoPageFound />} />
      </Routes>
      <Footer />
    </React.Fragment>
  );
}

export default App;

In our previous blog, I explain about react-router-dom so im not going to deep dive ino this, im goign to explain rest other code which is replated to showing blog list.

  • UseState Hook - blogListData:

    • The blogListData state is created using the useState hook to manage the list of blog data.

    • The initial state is an empty array.

  • onGetDataFromBlogComponent Function:

    • This function receives data from the Blog component via a prop.

    • It updates the blogListData state by adding the new blogData to the beginning of the array using the spread operator.

    • It logs the received blogData to the console.

  • Homepage Route:

    • The Homepage component is rendered when the path is /.

    • The blogListData state is passed as a prop to the Homepage component.

  • Blog Route:

    • The Blog component is rendered when the path is /blog.

    • The onPassDatatoAppComponent function is passed as a prop to the Blog component. This function will be used to update the blogListData state when new blog data is submitted.

You can get all this code from github repository

Run your APP

Simply run the following command in your terminal:

npm run dev

Now give yourself a pat on the back, as you've just implemented useState, Props, reusable components, and added as well as listed blogs in our React app.

So, that concludes this blog post.

What's Next?

In the next blog, we will implement a feature that allows us to edit and update the blog, as well as delete it. We will learn more about dynamic routes and how to navigate to different pages programmatically.

Conclusion

In conclusion, this article helps you build a React blog application, covering key concepts like useState, props, reusable UI components, and routing. Following this guide will enhance your understanding of React and its best practices, enabling you to create more efficient and maintainable applications in the future.

Stay tuned for the next blog, where we will cover editing, updating, and deleting blog posts, as well as dynamic routes and programmatic navigation.

Also, Follow me on Twitter or Linkedin I share about Cloud, System Design, Code, and many more tech stuff ๐Ÿ˜Š

Resources

0
Subscribe to my newsletter

Read articles from Pradhumansinh Padhiyar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pradhumansinh Padhiyar
Pradhumansinh Padhiyar

Sharing what I learn about Cloud, System design, and Code โ€ข On my journey to becoming a certified AWS Dev โ€ข Writing ebook on System Design