![]() |
Google keep clone |
How to build Google Keep Clone with React JS and Tailwindcss
Hello friends, As you know Google Keep is a top-rated application of Google. Everyone knows it. We keep our little notes in this application. So in this article, we will learn how to build a google keep clone with React and Tailwind. This project will clarify core topics such as react hooks, props, components, and ES6 features. This project contains pure React js and instead of traditional CSS, we will use Tailwind. You can also give your custom style to the application. For beginners, it is the best practice to improve their skills. You can also mention this project in your portfolio. You can check my previous tutorial on how to make a Pokemon App in React | source code.
Now let's take a look at the application. This is a simple google keep clone with a dark theme that contains an expandable input box. This input box has two input sections first is for the title and the second one is for the description. It also contains a close button that saves the text you write in the input box. The notes in the application have a custom delete button. And this notes section contains a beautiful masonry layout that increases its user experience.
We understood what this app looked like. Now let's start the project.
Folder structure
To create a good project its folder structure should be good. So let's take a look at the structure of the project. This app has a folder named "src" with files and folders shown below.
The src folder contains the following:-
- components -(folder)
- styles -(folder)
- App.jsx -(file)
- Mainjsx -(file)
Files inside the "components" folder
- CreateNote.jsx
- Header.jsx
- MyKeep.jsx
- Notes.jsx
The file inside the "styles" folder
- tailwind.css
Create these files and folders in your project. Then your folder structure looks like this. You can also see the project structure in the given image.
![]() |
Structure of src folder |
Project set up
If you read our previous article, you know how to set up the project. But if you don't, you just need to create react application. Then install Tailwind and connect it to the app. You can also check our article on how to build a react app using Vite js and tailwind CSS.
Libraries used in the project
This app contains only one external library. This library is used to add icons to the application.
- React icons
All set, Now it is time to build our google keep clone.
Building our project
Source code of "main.jsx" file
The primary purpose of giving the code of this file is to connect the "tailwind.css" file.
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './styles/tailwind.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
Source code of "App.jsx" file
import React from "react";
import MyKeep from "./components/MyKeep";
function App() {
return <MyKeep />
}
export default App;
Code of "MyKeep.jsx" file
import React from "react";
import Header from "./Header";
import CreateNote from "./CreateNote";
import Notes from "./Notes";
import { useState } from "react";
// import { FaAddressCard } from 'react-icons/fa'
function MyKeep() {
const [addItem, setAddItem] = useState([]);
const addNote = (Note) => {
setAddItem((prev) => {
return [...prev, Note];
});
// console.log(addItem);
};
const onDelete = (id) => {
setAddItem((oldData) =>
oldData.filter((currentData, index) => {
return index !== id;
})
);
};
return (
<>
<div className="w-full min-h-screen bg-gray-50 dark:bg-gray-800 font-sans relative">
<Header></Header>
<CreateNote passNote={addNote}></CreateNote>
<div className="columns-3xs gap-3 w-full min-h-full box-border mx-auto p-4">
{addItem.map((val, index) => {
return (
<Notes
key={index}
id={index}
title={val.title}
content={val.content}
deleteItem={onDelete}
></Notes>
);
})}
</div>
</div>
</>
);
}
export default MyKeep;
Understanding the react source code given above
- addNote function:- It takes input data from the "CreateNote" and adds data to the new state variable called "addItem". This all data is passed to the "Notes" component.
- onDelete function:- It takes the id of the individual note and removes it from the array stored in the new state variable.
Code of "Header.jsx" file
import React from 'react'
function Header() {
return (
<div className='w-full h-10 bg-amber-400 flex items-center px-5'>
<div className="font-mono text-white">MyKeep</div>
</div>
)
}
export default Header
Code of "CreateNote.jsx" file
import React from "react";
import { useState } from "react";
import { FaBell } from "react-icons/fa";
import { MdColorLens, MdDelete, MdPictureInPicture } from "react-icons/md";
function CreateNote(props) {
const [currentData, setCurrentData] = useState({
title: "",
content: "",
});
const [expand, setExpand] = useState(false);
const handleInputs = (e) => {
const value = e.target.value;
const name = e.target.name;
setCurrentData((pre) => {
return { ...pre, [name]: value };
});
};
const addEvent = (e) => {
e.preventDefault();
if (currentData.title || currentData.content) {
props.passNote(currentData);
}
setCurrentData({
title: "",
content: "",
});
setExpand(false);
};
return (
<div className="border border-gray-500 shadow-md shadow-gray-900 w-[28rem] min-h-36 rounded-md mx-auto my-6">
<form className="w-full h-full p-2.5">
{expand ? (
<input
type="text"
value={currentData.title}
name="title"
onChange={handleInputs}
placeholder="Title"
className="w-full font-medium text-lg dark:text-gray-300 bg-transparent outline-none"
autoComplete="false"
/>
) : null}
<textarea
name="content"
value={currentData.content}
onChange={handleInputs}
onClick={() => {
setExpand(true);
}}
cols="auto"
rows="auto"
className="w-full resize-x-none bg-transparent text-base font-medium dark:text-gray-300 border-none outline-none transition"
style={
expand
? { height: "3rem", marginTop: "0.75rem" }
: { height: "1.5rem", marginTop: 0 }
}
placeholder="Take a note..."
></textarea>
{expand ? (
<div className="w-full flex justify-between items-center">
<div className="flex dark:text-gray-400 gap-5">
<FaBell className="hover:bg-gray-500 hover:bg-opacity-40 w-6 h-6 rounded-full p-1" />
<MdColorLens className="hover:bg-gray-500 hover:bg-opacity-40 w-6 h-6 rounded-full p-1" />
<MdPictureInPicture className="hover:bg-gray-500 hover:bg-opacity-40 w-6 h-6 rounded-full p-1" />
<MdDelete className="hover:bg-gray-500 hover:bg-opacity-40 w-6 h-6 rounded-full p-1" />
</div>
<button
onClick={addEvent}
className=" px-8 dark:text-gray-300 font-medium"
>
Close
</button>
</div>
) : null}
</form>
</div>
);
}
export default CreateNote;
Understanding the code
This component has a box. This box has two input fields and one close button. The one input field takes the title as input and the second one takes content as input. At starting this box contract. And when you click on the title, it will expand and trigger a function named "handleInputs". This function stores the title and content's value in the new state variable named "currenData"
On clicking the "close" button, it also triggers a function named "addEvent". This function will pass all the input data to the main file. Then it removes all the text from the "currentData" state variable and contracts the box.
Code of "Notes.jsx" file
import React from "react";
import { MdColorLens, MdDelete } from "react-icons/md";
function Notes({ id, title, content, deleteItem }) {
const deleteNote = () => {
deleteItem(id);
};
return (
<div className="border border-gray-400 w-auto rounded-md min-h-[6rem] max-h-fit p-2 hover:shadow-lg hover:shadow-gray-900 mb-3 break-inside-avoid">
<div className="w-full font-medium text-lg dark:text-gray-300 bg-transparent">
{title}
</div>
<div className="w-full h-15 resize-x-none bg-transparent text-base font-medium dark:text-gray-400">
{content}
</div>
<div className="w-full flex justify-end items-center">
<div className="flex dark:text-gray-400 gap-1">
<MdColorLens className="hover:bg-gray-500 hover:bg-opacity-40 w-6 h-6 rounded-full p-1" />
<MdDelete
onClick={deleteNote}
className="hover:bg-gray-500 hover:bg-opacity-40 w-6 h-6 rounded-full p-1 cursor-pointer"
/>
</div>
</div>
</div>
);
}
export default Notes;
Understanding the code
It contains a note box. We fetch data in it from the state variable and return the note box with the data. It has also a delete button that passes the box id to the main file. And main file code will delete the targeted note. In the main file, we stored all the data in an array. And we call this component for every data by using the array map method.
Conclusion
If you have any queries tell us in the comment section. We do our best to remove your queries. You can also request any project for yourself.
Thanks !!!