loading...

Learn React Hook by building a Simple Blog App

kingdavid profile image Temitope ・20 min read

What is React?

React is a popular JavaScript library developed by Facebook for building user interfaces. It uses the concept of Virtual DOM to render Elements into the browser DOM because it is a popular belief that manipulating the browser DOM directly can be very slow and costly.
React developers often manipulate the virtual DOM and let React take care of updating the Browser DOM.

What are Hooks in React?

Well according to React, Hooks are functions that let you “hook into” React state and lifecycle features from function components.
Before the arrival of Hook, state and React lifecycles can only be used in a class component. Starting from version 16.8, React rolled out a lot of features that enable developers to hook into a React state without having to write a single class component.

What we’re building

We are building a simple CRUD blog app where a user can create a post, read the post, update post, and delete the post without making any API request to the server. You can view the final project here: https://react-simple-blog.now.sh or download the source code here: https://github.com/tope-olajide/react-simple-blog

The Setup

To follow up with this tutorial and get our app running, we are going to download and install the latest version of Node.js. (I am currently using version 12.13.1 for this tutorial)
Next, we'll launch our Command-Line Interface, install React and create a new project by typing in the following command:

npx create-react-app react-simple-blog

The above command will create a new directory called react-simple-blog and install React and its dependencies on it.
Also, you will be needing a code editor for this tutorial (I use VS Code).
To make sure React is working, launch your Command-Line Interface, navigate to the react-simple-blog folder (or whatever you named the folder) and run :
npm start to start your React development server.
Once the server is running, React will automatically launch your browser and navigate to http://localhost:3000/ in it, which is the default homepage for our React app. If all goes well, you should see the create-react-app splash screen.
alt text

Building Our App

Before we proceed it is good to have some basic knowledge of JavaScript, HTML, and CSS.
Let's update our App to display a welcoming message instead of the React flash screen.
Navigate to react-simple-blog/src on your computer
Launch App.js in your editor, and replace everything in it with the following code:

import React from "react";

const App = ( ) => {
  return (
    <div>
      <h1>Hello World</h1>
    </div>
  );
};
export default App;

Here, we modified our App component to display Hello World. Your browser should automatically refresh and display a similar output like this:
alt text

The first line imports React from our node-modules. In the third line, we created a functional component called App, using the JavaScript fat arrow function.
Then we render the following JSX elements:

 return (
    <div>
      <h1>Hello World</h1>
    </div>
  );

So in the last line, we exported our App component so that it can be used later.

JSX

JSX stands for JavaScript Syntax Extension. It has a familiar syntax with plain HTML and it can also be used directly in our JavaScript file but no browser can read it without transpiling it first. JSX can be transpiled into JavaScript code by using a preprocessor build-tool like babel.
Babel has already been pre-installed with create-React-app, so we don't have to be worried about configuring our app to transform our JSX code into javascript.
You can read more about JSX here

Navigate to React-simple-blog/src and open index.js in your editor.
The index.js file renders our App component into <div id="root"> </div> element (which can be located inside my-simple-blog/public/index.html )
Line 4 imports our App component and it is rendered into the DOM using the React.render method ( line 7 ).
Next, we are going to delete some files we are not using but came bundled with create-React-app. Navigate to react-simple-blog/src and delete the following files:
App.css,
App.test.js,
index.css,
logo.svg, and
setupTests.js

After that, we'll open our index.js file and delete the third line:
alt text
Since we have removed the index.css file, there is no reason to import it again in our index.js, else we might end up with a "failed to compile" error.
By now, we should have just 3 files left in our src folder (i.e App.js, index.js and serviceWorker.js).

We'll create a new folder called Components inside our src folder. This folder will house the remaining components we'll be building for this app.
react-simple-blog/src/Components

Inside our Components folder, we'll create a new file called CreateNewPost.jsx. From its name, you can easily guess what this new file will be used for.
Let us add the following code into our newly CreateNewPost.jsx file:

import React from "react";
const CreateNewPost = () => {
  return (
    <>
<form>
      <h1>Create New Post</h1>
      <input type ="text" placeHolder="title" size="39" required></input>
      <br />
      <br />
      <textarea placeHolder="contents" rows="8" cols="41"required></textarea>
      <br />
      <br />
      <button>Save Post</button>
</form>
    </>
  );
};
export default CreateNewPost;

If you have been following up with this tutorial from the beginning and you're familiar with HTML, there should be nothing strange to you here except for this opening and closing empty tag: <> </> which is a short syntax for <React.Fragment> </React.Fragment>. Using fragments instead of <div></div> is a little bit faster and has less memory usage.
Also, it is good to know that React component name starts with an uppercase letter.
To display our CreateNewPost component, we need to import it first in our App component and render it.
To do that, we'll navigate to our react-simple-blog/src/App.js and add the following code below the import React statement :
import CreateNewPost from './components/CreateNewPost'
To render CreateNewPost component, we'll replace
<h1>Hello World </h1>.
with
<CreateNewPost />
So that our App component will look like this:

import React from "react";

import CreateNewPost from './Components/CreateNewPost'
const App = ( ) => {
  return (
    <div>
      <CreateNewPost />
    </div>
  );
};
export default App;

You can now refresh your browser if React hasn't done that already.
If everything went well, we should have a similar output that looks like this:
alt text

We are not adding any CSS for now. Everything styling will be done towards the end of this app.
The 'Save Post' button does nothing for now, we'll add some functionalities to it once we're done with creating our components.
The next component we are going to build is the Post component. The Post component will be used to render each post. If you are feeling a little bit puzzled about all these components don't worry yet, everything will make more sense later on when you see all the components in action.
We'll create a new file inside our Components folder called Post.jsx and add the following code:

import React from 'react';

const Post = () => {
    return (
    <>
    <section>
    <h3>Post title will appear here</h3>
    <p> Post contents will appear here</p>
    <button>Edit</button>
    <button>Delete</button>
    </section>
    </>
    )
}
export default Post

Again, if you're familiar with HTML, and have been following along with this tutorial, there should be nothing strange to you here. We added two buttons to our Post component, Edit and Delete button. The Edit button will be used to modify the selected post while the Delete button will be used to remove the post. These buttons are not working for now, we'll make them work later once we're done with building the remaining components.
To display our Post component, we'll navigate to React-simple-blog/src/App.js and update it with the following code:

import React from "react";
import Posts from './Components/Post'
const App = ( ) => {
  return (
    <>
      <Posts />
    </>
  );
};
export default App;

After refreshing our browser, we should have a typical output like this:
alt text

Let's create another component called ModifyPost. From its name, you can easily guess that this component will be used to modify the selected blog post. We want React to render this component only when a user clicks on the Edit button. Let's navigate to our Components directory and create a new file called ModifyPost.jsx.
Next, we'll add the following code into our newly created ModifyPost.jsx file:

import React from "react";
const ModifyPost = () => {
  return (
    <>
      <form>
        <h1>Modify Post</h1>
        <input type="text" placeholder="title" size="39" required></input>
        <br />
        <br />
        <textarea placeholder="contents" rows="8" cols="41" required></textarea>
        <br />
        <br />
        <button>Update Post</button>
      </form>
    </>
  );
};
export default ModifyPost;

The Update Post button is not working for now, we'll make it work later.
The next and probably the last component we'll be building for this tutorial is the DisplayAllPosts component.
This component will serve as the parent component to CreatePost, ModifyPost, and Post component because we are going to render these components inside it. Let's navigate to React-simple-blog/src/Components and create a new file called DisplayAllPosts.jsx.
Let's add the following code to our newly created component:

import React from 'React';
import CreateNewPost from './CreateNewPost'
const DisplayAllPosts = () => {
    return (
    <>
    <CreateNewPost />
    </>
    )
}
export default DisplayAllPosts

Here we created a new component called DisplayAllPost and rendered the CreateNewPost component in it.

Now that we are done building our components, it's time to bring them to life. Like I said earlier, not adding CSS to all the components now was completely intentional, every styling will be done once we are done implementing all the functionalities of this app.

The next thing we want to do now is to capture our user input as they type into the text field and save it straight into the component state variable. To do this, we'll be using our first React hook called useState.

Here are few things about State in general in React:

  • States are changeable.
  • States holds information about the component it was declared in also, the component that declares a state is the owner of the state.
  • When the state of a component changes, the component re-renders itself.

The example below shows how to declare a state variable using our first React hook in this app, useState :

const [state] = useState("Hello World");
console.log(state);// returns Hello World

To update a state variable:

const [state, setState] = useState("Hello World") ;//initial state = Hello World
setState("Hello Again");//  new state value will be Hello Again

When we declare a state variable with useState, it returns an array with two items. The first item is the current value(state), and the second item is its updater function(setState) that is used to update the state. The array items returned from the useState function in the example above are destructured into state and setState variables respectively.
Now that we have a glimpse of what useState is all about, let's make the following changes to our newly created DisplayAllPosts component:

import React, {useState} from 'React';
import CreateNewPost from './CreateNewPost'
const DisplayAllPosts = () => {
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const savePostTitleToState = event => {
  setTitle(event.target.value);
  console.log(title)
};
const savePostContentToState = event => {
  setContent(event.target.value);
  console.log(content)
};
    return (
    <>
    <CreateNewPost 
    savePostTitleToState = {savePostTitleToState}
    savePostContentToState = {savePostContentToState}
    />
    </>
    )
}
export default DisplayAllPosts

Here we created two state variables title and content and set their updater functions setTitle and setContent. Then we created two functions: savePostTitleToState and savePostContentToState. These functions will be used for saving the user inputs value into the state. We also added a console.log() statement to each function to view the input value as the user type in their input. Then we pass the two functions down as props into CreateNewPost Component.
Props is the way data flows from the parent component (DisplayAllPosts in this case) to child component (CreateNewPost). Props can be used to send functions or state from a parent component down to its to child components.
Next, we are going to make our CreateNewPost component receive the props data passed down from its parent, DisplayAllPosts.
Open React-simple-blog/src/Components/CreateNewPost.jsx and update the CreateNewPost component to look like this:

import React from "react";
const CreateNewPost = props => {
  return (
    <>
      <form>
        <h1>Create New Post</h1>
        <input
          type="text"
          onChange={props.savePostTitleToState}
          placeholder="title"
          size="39"
          required
        ></input>
        <br />
        <br />
        <textarea
          onChange={props.savePostContentToState}
          placeholder="contents"
          rows="8"
          cols="41"
        required
        ></textarea>
        <br />
        <br />
        <button>Save Post</button>
      </form>
    </>
  );
};
export default CreateNewPost;

To preview your changes, refresh your browser and launch your browser console(ctrl+shift+i if you are using Chrome) to view the data we captured. You can go ahead and type something into the input fields, if everything goes right, you should have a similar output that looks like this:

alt text

Next, we want to save our captured post title and content into another state variable called allPosts once a user clicks on the 'Save Post' button.
In our DisplayAllPosts.jsx, we'll create a new state variable like so:

const [allPosts, setAllPosts] = useState([]);

After that, we'll create a new function called savePost:

const savePost = () => {
    const id = Date.now();
    setAllPost([...allPost, {title, content, id}]);
    console.log(allPost);
  };

This function will be responsible for saving the captured input data into allPosts state variables.
Here, we assigned a unique id to each post by using Date.now() then we used the spread operator to append our newly captured data to our allPosts state.
Also, we destructured our title and content objects to give us title, content instead of title: title, content: content.
We added a console.log statement to view the allPost values.
After the data has been captured successfully, we want to clear our state and all the input field value so that the user can add another post. To do that, we will have to clear our title and content state variables.
Let's update out savePost function like so:

const savePost = () => {
    setAllPost([...allPost, { title, content }]);
    setTitle("");
    setContent("");
    console.log(allPost);
  };

Clearing the state value will not affect our input field value on the DOM. To locate our input fields on the DOM and clear their value, we are going to use another React hook called useRef.
We are going to import useRef by updating our React import statement like this:
import React, { useState, useRef } from "react";
Next, we are going to initialize our useRef like so:

  const getTitle = useRef();
  const getContent = useRef();

Then we'll passed down the refs to CreateNewPost component as props like so:

      <CreateNewPost
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
        getTitle={getTitle}
        getContent={getContent}
      />

After that, we'll navigate to our CreateNewPost.jsx and make it use the new props data we passed down to it.
Our CreateNewPost component will now look like this:

import React from "react";
const CreateNewPost = props => {
  return (
    <>
      <form>
        <h1>Create New Post</h1>
        <input
          type="text"
          onChange={props.savePostTitleToState}
          placeholder="title"
          size="39"
          required
          ref={props.getTitle}
        ></input>
        <br />
        <br />
        <textarea
          onChange={props.savePostContentToState}
          placeholder="contents"
          rows="8"
          cols="41"
         required
          ref={props.getContent}
        ></textarea>
        <br />
        <br />
        <button>Save Post</button>
      </form>
    </>
  );
};
export default CreateNewPost;

Now that we have used useRef to locate our input field on the DOM, we need to clear input field value once we have saved our post.
To do that, we'll go back to DisplayAllPosts.jsx and update our savePost function to look like this:

const savePost = (event) => {
    event.preventDefault();
    setAllPosts([...allPosts, {title, content}]);
    console.log(allPosts);
    getTitle.current.value = "";
    getContent.current.value = "";
  };

We called event.preventDefault() to prevent the default refreshing behaviour of HTML form when a user clicks on the submit button.
To use our savePost function, we'll pass it down as props to the CreateNewPost component. Let's update our return statement in DisplayAllPosts.jsx to look like this:

  return (
    <>
      <CreateNewPost
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
        getTitle={getTitle}
        getContent={getContent}
        savePost={savePost}
      />
    </>
  );

Now we can launch our CreateNewPost component and make it use the savePost function we passed down to it like this:

import React from "react";
const CreateNewPost = props => {
  return (
    <>
      <form onSubmit={props.savePost}>
        <h1>Create New Post</h1>
        <input
          type="text"
          onChange={props.savePostTitleToState}
          placeholder="title"
          size="39"
          required
          ref={props.getTitle}
        ></input>
        <br />
        <br />
        <textarea
          onChange={props.savePostContentToState}
          placeholder="contents"
          rows="8"
          cols="41"
          required
          ref={props.getContent}
        ></textarea>
        <br />
        <br />
        <button>Save Post</button>
      </form>
    </>
  );
};
export default CreateNewPost;

Each time a user submits a post by clicking on the Save Post button, the onSubmit() event will trigger the savePost function we created earlier.
Our DisplayAllPosts component should look like this right now:

import React, { useState, useRef } from "react";
import CreateNewPost from "./CreateNewPost";
const DisplayAllPosts = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [allPosts, setAllPosts] = useState([]);
  // Initialize useRef
  const getTitle = useRef();
  const getContent = useRef();

  const savePostTitleToState = event => {
    setTitle(event.target.value);
  };
  const savePostContentToState = event => {
    setContent(event.target.value);
  };

  const savePost = event => {
    event.preventDefault();
    setAllPosts([...allPosts, { title, content }]);
    console.log(allPosts);
    getTitle.current.value = "";
    getContent.current.value = "";
  };

  return (
    <>
      <CreateNewPost
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
        getTitle={getTitle}
        getContent={getContent}
        savePost={savePost}
      />
    </>
  );
};
export default DisplayAllPosts;

We can now refresh our browser and launch the browser console to see if our captured data is being saved correctly into our AllPosts state variable.
We should have a similar output look this:
alt text
Now that our post data is being saved successfully, it's time to display them in our DisplayAllPost component. But before then, we want to render our CreateNewPost component only when a user clicks on the Add New button and remove the component once the user clicks on the Save Post button. To do that, let's update our DisplayAllPost component to look like this:

import React, { useState, useRef } from "react";
import CreateNewPost from "./CreateNewPost";
const DisplayAllPosts = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [allPosts, setAllPosts] = useState([]);
  const [isCreateNewPost, setIsCreateNewPost] = useState(false);
  // Initialize useRef
  const getTitle = useRef();
  const getContent = useRef();

  const savePostTitleToState = event => {
    setTitle(event.target.value);
  };
  const savePostContentToState = event => {
    setContent(event.target.value);
  };
const toggleCreateNewPost =()=>{
    setIsCreateNewPost(!isCreateNewPost)
}
  const savePost = event => {
    event.preventDefault();
    const id = Date.now();
    setAllPosts([...allPosts, { title, content, id }]);
    console.log(allPosts);
    getTitle.current.value = "";
    getContent.current.value = "";
    toggleCreateNewPost()
  };
if(isCreateNewPost){
      return (
    <>
      <CreateNewPost
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
        getTitle={getTitle}
        getContent={getContent}
        savePost={savePost}
      />
    </>
  );
}
return (
    <>
    <h2>All Posts</h2>
    <br/>
    <br/>
    <button onClick={toggleCreateNewPost}>Create New</button>
    </>
)
};
export default DisplayAllPosts;

We created a new state variable called isCreateNewPost and we initialized it with a boolean value, false.
Then we created another function called toggleCreateNewpost, this function will make isCreateNewPost state variable to toggle between true and false.If the previous state value of isCreateNewPost is true, toggleCreateNewpost will change it to false otherwise, true.
We added a new button called Create New. This button will call the toggleCreateNewpost function once a user clicks on it. After that, we created a conditional statement that only renders the CreateNewPost component if the isCreateNewPost boolean value is true.
This process of rendering a component only when a condition is met is called Conditional Rendering in React.
We can go ahead and preview our changes by refreshing our browser. We should have a similar output like this:

When we click on our Create New button, it should render our CreateNewPost component like so:

alt text

When we enter our post title and contents and click Save Post button, it should save them and render back our DisplayAllPosts component, but our post will not display yet.
To display all posts, we need to modify our Post component to receive the props we are going to pass down to it from its parent component, DisplayAllPosts.
Let open our Post.jsx and modify it to look like this:

import React from 'react';

const Post = (props) => {
    return (
    <>
    <section>
    <h3>{props.title}</h3>
    <p> {props.content}</p>
    <button>Edit</button>
    <button>Delete</button>
    </section>
    </>
    )
}
export default Post

Our Post component can be considered as a template that will be used to render the post data passed down to it via props.
Now that we are done with our Post component, let's modify our DisplayAllPosts to look like this:

import React, { useState, useRef } from "react";
import CreateNewPost from "./CreateNewPost";
import Post from "./Post";
const DisplayAllPosts = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [allPosts, setAllPosts] = useState([]);
  const [isCreateNewPost, setIsCreateNewPost] = useState(false);

  // Initialize useRef
  const getTitle = useRef();
  const getContent = useRef();

  const savePostTitleToState = event => {
    setTitle(event.target.value);
    console.log(title)
  };
  const savePostContentToState = event => {
    setContent(event.target.value);
    console.log(content)
  };
  const toggleCreateNewPost = () => {
    setIsCreateNewPost(!isCreateNewPost);
  };

  const savePost = event => {
    event.preventDefault();
    setAllPosts([...allPosts, { title, content }]);
    console.log(allPosts);
    getTitle.current.value = "";
    getContent.current.value = "";
    toggleCreateNewPost();
  };
  if (isCreateNewPost) {
    return (
      <>
        <CreateNewPost
          savePostTitleToState={savePostTitleToState}
          savePostContentToState={savePostContentToState}
          getTitle={getTitle}
          getContent={getContent}
          savePost={savePost}
        />
      </>
    );
  }

  return (
<>
      <h2>All Posts</h2>
      {!allPosts.length ? (
        <div>
          <h3>There is nothing to see here!</h3>
        </div>
      ) : (
        allPosts.map(eachPost => {
          return (
            <Post
              id={eachPost.id}
              key={eachPost.id}
              title={eachPost.title}
              content={eachPost.content}
            />
          );
        })
      )}

      <br />
      <br />
      <button onClick={toggleCreateNewPost}>Create New</button>
    </>
  );
};
export default DisplayAllPosts;

Here our DisplayAllPosts component has been modified to display our post data. If the allPosts array is empty it is going to show There is nothing to see here! to the user else it is going to use the array.map() method to loop through allPosts array and pass down each post id, key, title and content as props to our Post component.
Let us refresh our browser, click on the Add New button, enter some value into the title and contents field and click on save.
If everything goes well, we should have a similar output that looks like this:
alt text

We can click on the Create New button to add more posts and see all our posts being rendered to the screen.
So far, we are done with the C and R (Create and Read) feature of our CRUD app. The next feature we are going to implement now is the Update feature. This feature will enable the user of our app to modify a selected post once the user clicks on the Edit button.
Let's open our DisplayAllPosts.js and create a new state called isModifyPost below isCreateNewPost state:

const [isModifyPost, setIsModifyPost] = useState(false);

We are going to use this state to render the ModifyPost component once isModifyPost boolean value is true.
Next, we'll to create another function called toggleModifyPostComponent just below our toggleCreateNewPost function:

  const toggleModifyPostComponent = () => {
    setIsModifyPost(!isModifyPost)
  }

This function will be used to toggle isModifyPost boolean value between true and false. If the previous boolean value is false, it switches it to true and if the previous value is true it switches it to false.
Let's create another state called editPostId, below our isModifyPost state.

const [editPostId, setEditPostId] = useState("");

This state variable will be used to save the id of the post that a user wants to modify.
After that, well create another function called editPost below our toggleModifyPostComponent function:

  const editPost = id => {
    setEditPostId(id);
    toggleModifyPostComponent();
  };

This function will be passed down to the Post component and get called from inside our Post component with the id of the post that the user clicks on as its parameter. The setEditPostId function will save the post id into editPostId state, while the toggleModifyPost function will render or remove our ModifyPost component depending on isModifyPost state variable boolean value.
We are saving the id of the post that a user wants to modify into the editPostId state variable because we want our updatePost function to have access to it.
Now we are going to create a new function called updatePost. This function will be used to update our modified post:

  const updatePost = (event) => {
    event.preventDefault();
    const updatedPost = allPosts.map(eachPost => {
      if (eachPost.id === editPostId) {
        return {
          ...eachPost,
          title: title || eachPost.title,
          content: content || eachPost.content
        };
      }
      return eachPost;
    });
    setAllPosts(updatedPost);
    toggleModifyPostComponent();
  };

Here we used one of the inbuilt array methods called map() to iterate over each post in allPosts to find the post that a user wants to modify using the post id that was saved earlier into editPostId state variable. Then we used the rest syntax (...) to modify only the title and contents of the post leaving the id of the post untouched. We used OR operator (||) to save the previous post title and post content instead of an empty value in case the user decides to update the post without making any modifications.
The next thing we need to do now is to render our ModifyPost component if the isModifyPost state variable is true.
Still in DisplayAllPost.jsx, let's add the following code below our if (isCreateNewPost){} statement:

  else if (isModifyPost) {
    const post = allPosts.find(post => {
      return post.id === editPostId;
    });
    return (
      <ModifyPost
        title={post.title}
        content={post.content}
        updatePost={updatePost}
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
      />
    );
  }

What we are tryig to achieve here is to preload the input fields in the ModifyPost component with the data of the post that the user wants to modify. So we searched for the selected post first and passed down the post title and contents as props to the ModifyPost component.
We also passed down our updatePost, saveTitleToState, savePostContentToState function to our ModifyPost component respectively. We have used saveTitleToState and savePostContentToState before in our CreateNewPost component to save user input value to our state variable.
Now we are going to use the props that we have passed to our ModifyPost component. Let's open our ModifyPost.jsx and update its code to look like this:

import React from "react";
const ModifyPost = props => {
  return (
    <>
      <form>
        <h1>Modify Post</h1>
        <input
          defaultValue={props.title}
          onChange={props.savePostTitleToState}
          text
          placeholder="title"
          size="39"
        ></input>
        <br />
        <br />
        <textarea
          defaultValue={props.content}
          placeholder="contents"
          onChange={props.savePostContentToState}
          rows="8"
          cols="41"
        ></textarea>
        <br />
        <br />
        <button onClick ={props.updatePost}>Update Post</button>
      </form>
    </>
  );
};
export default ModifyPost;

We set the default value of the inputs field that will be rendered to the user with the post title and content that was passed down to this component. We also set the submit button with an onClick event which called our updatePost function that was passed down to the ModifyPost component.
One more thing before we can test our ModifyPost component, we want to trigger the ModifyPost component once a user clicks on the edit button, Therefore, we are going to pass down the editPost function to Post component from DisplayAllPosts.
Let's modify our DisplayAllPosts component to render our Post component:

return (
    <>
      <h2>All Posts</h2>
      {!allPosts.length ? (
        <div>
          <h3>There is nothing to see here!</h3>
        </div>
      ) : (
        allPosts.map(eachPost => {
          return (
            <Post
              id={eachPost.id}
              key={eachPost.id}
              title={eachPost.title}
              content={eachPost.content}
              editPost={editPost}
            />
          );
        })
      )}
      <br />
      <br />
      <button onClick={toggleCreateNewPost}>Create New</button>
    </>
  );

Now we are going to update our Post component to use the editPost function that was passed to it.
Our Post Component should look like this:

import React from 'react';

import React from "react";

const Post = ({ title, content, editPost, id }) => {
  return (
    <>
      <section>
        <h3>{title}</h3>
        <p> {content}</p>
        <button onClick={() => editPost(id)}>Edit</button>
        <button>Delete</button>
      </section>
    </>
  );
};
export default Post;

You might have noticed that this Post component is a bit different from the previous Post component, that's because we have destructured the props data that was passed down to it by unpacking the data and assigning to them their own variable name.
Before we run our app, let's compare our DisplayAllPost.jsx file and make sure it looks like this:

import React, { useState, useRef } from "react";
import CreateNewPost from "./CreateNewPost";
import Post from "./Post";
import ModifyPost from "./ModifyPost"
const DisplayAllPosts = () => {
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [allPosts, setAllPosts] = useState([]);
  const [isCreateNewPost, setIsCreateNewPost] = useState(false);
  const [isModifyPost, setIsModifyPost] = useState(false);
  const [editPostId, setEditPostId] = useState("");

  // Initialize useRef
  const getTitle = useRef();
  const getContent = useRef();

  const savePostTitleToState = event => {
    setTitle(event.target.value);
  };
  const savePostContentToState = event => {
    setContent(event.target.value);
  };
  const toggleCreateNewPost = () => {
    setIsCreateNewPost(!isCreateNewPost);
  };
  const toggleModifyPostComponent = () => {
    setIsModifyPost(!isModifyPost)
  }
  const editPost = id => {
    setEditPostId(id);
    console.log(id)
    toggleModifyPostComponent();
  };
  const updatePost = (event) => {
    event.preventDefault();
    const updatedPost = allPosts.map(eachPost => {
      if (eachPost.id === editPostId) {
        console.log([eachPost.id, editPostId] )
        return {
          ...eachPost,
          title: title || eachPost.title,
          content: content || eachPost.content
        };
      }
      console.log(eachPost)
      return eachPost;
    });
    setAllPosts(updatedPost);
    toggleModifyPostComponent();
  };
  const savePost = event => {
    event.preventDefault();
    const id = Date.now();
    setAllPosts([...allPosts, { title, content, id }]);
    console.log(allPosts);
    setTitle("");
    setContent("");
    getTitle.current.value = "";
    getContent.current.value = "";
    toggleCreateNewPost();

  };
  if (isCreateNewPost) {
    return (
      <>
        <CreateNewPost
          savePostTitleToState={savePostTitleToState}
          savePostContentToState={savePostContentToState}
          getTitle={getTitle}
          getContent={getContent}
          savePost={savePost}
        />
      </>
    );
  }
  else if (isModifyPost) {
    const post = allPosts.find(post => {
      return post.id === editPostId;
    });
    return (
      <ModifyPost
        title={post.title}
        content={post.content}
        updatePost={updatePost}
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
      />
    );
  }
  return (
    <>
      <h2>All Posts</h2>
      {!allPosts.length ? (
        <div>
          <h3>There is nothing to see here!</h3>
        </div>
      ) : (
        allPosts.map(eachPost => {
          return (
            <Post
              id={eachPost.id}
              key={eachPost.id}
              title={eachPost.title}
              content={eachPost.content}
              editPost={editPost}
            />
          );
        })
      )}
      <br />
      <br />
      <button onClick={toggleCreateNewPost}>Create New</button>
    </>
  );
};
export default DisplayAllPosts;

We can go ahead and refresh our browser now, to view our changes

Finally, we are going to implement the last and probably the easiest feature of our CRUD app, the Delete feature. This feature will enable a user to remove a specific post once he/she clicks on the delete button. Let's open our DisplayAllPosts.jsx and create deletePost function below editPostfunction.

  const deletePost = id => {
    const modifiedPost = allPosts.filter(eachPost => {
      return eachPost.id !== id;
    });
    setAllPosts(modifiedPost);
  };

The deletePost function takes in the id of the post that a user wants to remove as its parameter. We used one of the JavaScript array methods called filter() to remove the post that matches the id. The filter() method creates a new array with the remaining post data that doesn't match the post id then we saved the array into the modifiedPost variable. After that, we saved the modifiedPost data into the allPosts state.
Next we are going to pass down the deletePost function from DisplayAllPosts.jsx to the Post component.
To do that, we are going update the Post component we imported in DisplayAllPost.jsx by adding deletePost={deletePost} to the child component like so:

          return (
    <>
      <h2>All Posts</h2>
      {!allPosts.length ? (
        <div>
          <h3>There is nothing to see here!</h3>
        </div>
      ) : (
        allPosts.map(eachPost => {
          return (
            <Post
              id={eachPost.id}
              key={eachPost.id}
              title={eachPost.title}
              content={eachPost.content}
              editPost={editPost}
              deletePost={deletePost}
            />
          );
        })
      )}
      <br />
      <br />
      <button onClick={toggleCreateNewPost}>Create New</button>
    </>
  );

Finally, we are going to make use of the deletePost function we passed down to Post component by launching the Post.jsx file and updating it to look like this:

import React from "react";

const Post = ({ title, content, editPost, id, deletePost }) => {
  return (
    <>
      <section>
        <h3>{title}</h3>
        <p> {content}</p>
        <button onClick={() => editPost(id)}>Edit</button>
        <button onClick={() => deletePost(id)}>Delete</button>
      </section>
    </>
  );
};
export default Post;

Once a user clicks on the Delete button, it calls the deletePost function we passed down to Post component with the id of the current post.
If all goes well we should have a similar output that looks like this:

alt text

That's all!

The full code is here: https://github.com/tope-olajide/react-simple-blog.

Thanks for reading.

Discussion

pic
Editor guide