Getting Started with React: Creating Your Own Blog App - Part 3
Table of contents
- Introduction
- Prerequisites
- What will we learn in this blog post?
- Installing Necessary Packages
- What are props in React and why do we use them in our app components?
- What is useState in React?
- Creating Reusable UI Components
- Creating an "Add Blog" Form
- Creating a "Blog List" Page
- What's Next?
- Conclusion
- Resources
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
Familiar with HTML & CSS
Proficiency in Vanilla JavaScript
Any Code Editor
Nodejs Installed on the system
Read my previous blog posts to understand where we are in this series.
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.
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.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 ofdraft-js
and abstracts many of the complexities of working withdraft-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 likereact-draft-wysiwyg
we can save a significant amount of development time compared to building a custom editor from scratch.draftjs-to-html
: This package is used to convert the content generated bydraft-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 bydraft-js
editor as HTML,draftjs-to-html
provides a convenient way to perform the conversion.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
: Usingprop-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
Import
useState
: Import theuseState
function from thereact
library at the top of your component file.Use
useState
: Call theuseState
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.Access State: You can access the current state value just like any other variable.
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 theForm
component from thereact-bootstrap
library.PropTypes
from"prop-types"
: This imports thePropTypes
object from theprop-types
library.It takes in several props as its parameters:
inputLabel
,inputType
,inputPlaceholder
,inputValidationMsg
,inputOnChange
,inputValue
, andinputIsValid
.Form.Label
: Displays the label text for the input field. The label text is taken from theinputLabel
prop.Form.Control
: This is an input control fromreact-bootstrap
. It has several attributes like:-type
: The type of input, taken from theinputType
prop.placeholder
: The placeholder text for the input field, taken from theinputPlaceholder
prop.onChange
: The function to be called when the input value changes, taken from theinputOnChange
prop.value
: The current value of the input field, taken from theinputValue
prop.isInvalid
: A boolean that indicates whether the input is invalid, taken from theinputIsValid
prop.Form.Control.Feedback
: This displays the validation feedback message when the input is invalid. The message is taken from theinputValidationMsg
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 theButton
component from thereact-bootstrap
library.PropTypes
from"prop-types"
: This imports thePropTypes
object from theprop-types
library.CustomButton
component takes in several props as its parameters:buttonType
,buttonText
,buttonClassName
,buttonOnClick
, andisDisabled
className
: The class name for the button, taken from thebuttonClassName
prop.type
: The type of the button (e.g., submit, reset), taken from thebuttonType
prop.onClick
: The function to be called when the button is clicked, taken from thebuttonOnClick
prop.disabled
: A boolean indicating whether the button is disabled, taken from theisDisabled
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, andbuttonClassName
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.
- Multiple
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
, andCustomButton
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
fromdraft-js
to manage the editor's state.
Alerts:
- Depending on form submission status and content, different alerts are displayed using the
CustomAlert
component.
- Depending on form submission status and content, different alerts are displayed using the
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 theonPassDatatoAppComponent
prop.
- The
Export:
- The
Blog
component is exported as the default export of the module.
- The
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
fromreact-bootstrap
andPropTypes
for prop type validation.The local component
BlogItem
and a stylesheethomepage.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
fromreact-bootstrap
. An icon from the FontAwesome library (fa fa-search
) is also included.
- This section contains a search field using
Blog List Title Section:
- This section displays the title "All Blogs" along with an icon (
fa fa-file-text-o
).
- This section displays the title "All Blogs" along with an icon (
Blog List Section:
This section uses the
Stack
component fromreact-bootstrap
to create a gap between blog items.The content inside the
Stack
component is conditionally rendered based on the length ofblogListData
.If
blogListData
is empty, a message "No Blogs are available ๐" is displayed.If
blogListData
has items, theblogListData
array is mapped over, and for each blog item, theBlogItem
component is rendered with specific props.
BlogItem
Component:This component is rendered for each blog item.
It receives props like
blogTitle
,blogAuthor
,blogTimeAndDate
, andblogAuthorImage
.
Prop Type Validation:
- The
propTypes
object is defined to specify the expected type for theblogListData
prop.
- The
Export:
- The
Homepage
component is exported as the default export of the module.
- The
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 theuseState
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 newblogData
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 theHomepage
component.
Blog Route:
The
Blog
component is rendered when the path is/blog
.The
onPassDatatoAppComponent
function is passed as a prop to theBlog
component. This function will be used to update theblogListData
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
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