DEV Community

Temitope
Temitope

Posted on • Updated on

Learn React Hook by building a Simple Blog App

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.

One of the biggest advantages of using React is the reusability of code. Components can be easily reused throughout an application or even across different applications, saving time and effort for developers.
This is especially helpful in large projects where there may be a lot of repetitive code. Additionally, React's modular architecture allows for easier testing and debugging, which can further speed up the development process.

Hooks in 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.

Hooks provide a way for developers to manage state and other React features without needing to convert their functional components to class components. They also make it easier to reuse stateful logic across different components, as the logic can be encapsulated in a custom hook.

What we’re building

We are building a simple frontend 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 the 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, you'll launch your Command-Line Interface, install React and create a new project by running the following command:

npx create-react-app react-simple-blog
Enter fullscreen mode Exit fullscreen mode

The above command will create a new directory called react-simple-blog and install React and its dependencies on it.

To make sure React is working, launch your CLI, navigate to the react-simple-blog folder (or whatever you chose to name 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 that looks like this:
alt text

Modify the App.js file

Let's update the App.js to display a welcoming message instead of the default flash screen.
Navigate to react-simple-blog/src on your computer, then
open the App.js file 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;

Enter fullscreen mode Exit fullscreen mode

Here, we modified the 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 the 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>
  );
Enter fullscreen mode Exit fullscreen mode

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

What is JSX?

JSX stands for JavaScript Syntax Extension. It has a familiar syntax with plain HTML and it can also be used directly in your 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 you don't have to be worried about configuring the app to transform your JSX code into javascript.
You can read more about JSX here

Navigate to React-simple-blog/src and open the index.js file in your editor. This file serves as the starting point of the application and it is responsible for rendering the main component, which is the "App" component, to the root element located in the public/index.html file.
In the index.js file, the App component is imported on line 4 and then rendered to the browser using the React.render method on line 7. This means that when the user loads the webpage, the App component will be displayed inside the root div. This is how React components are rendered to the browser - by being inserted into the DOM through the "React.render" method.

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, open your 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 the index.js, else we might end up with a "failed to compile" error.
By now, you should have just 3 files left in the src folder (i.e App.js, index.js and serviceWorker.js).

Let's create a new folder called Components inside the src folder. This folder will be used to store the remaining components we'll be building for this app:
react-simple-blog/src/Components.

Inside the Components folder, 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 the 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;
Enter fullscreen mode Exit fullscreen mode

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. All we did here was create a new form that will be used to collect the user's input.

Also, it is good to know that React component name starts with an uppercase letter because this is how React distinguishes components from regular HTML elements or other functions.
By convention, only components that start with a capital letter are considered to be React components, while lower-case elements are assumed to be standard HTML elements.

To display the CreateNewPost component, we need to import it first in into the App component and render it.
To do that, open up the 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 the App component looks like this:

import React from "react";

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

You may refresh your browser if React hasn't done so.

If everything went right, you should see an output that looks almost the same as this one:

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 all the necessary components.

Our next task is to create the Post component, which will be responsible for displaying each individual post. If you are finding the concept of multiple components confusing, don't worry. As you see these components working together, everything will become clearer.

Let's create a new file inside the 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
Enter fullscreen mode Exit fullscreen mode

In the code above, we import the React library, which is a prerequisite for writing React code. We then define the Post component as a function using arrow function syntax.

Inside the function, we use JSX to create a section element with a title and content of a blog post, as well as two buttons for editing and deleting 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 the Post component, navigate to the App.js file and update it with the following code:

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

After refreshing your browser, you should have a typical output that looks like this:

alt text

Next, you will create a new component named ModifyPost, which will be used for modifying a selected post. As the name suggests, this component will be used to modify the post, and we want it to be rendered by React only when a user clicks the Edit button. To create this component, navigate to the Components directory and create a new file called ModifyPost.jsx.

Then add the following code to the 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;
Enter fullscreen mode Exit fullscreen mode

Create the DisplayAllPosts component

The final component we will be creating for this tutorial is the DisplayAllPosts component.
This component will serve as a parent component to CreatePost, ModifyPost, and Post.

Navigate to the Components directory and create a new file called DisplayAllPosts.jsx, then add the following code to it:

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

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

Now that we have finished building the components, it's time to bring them to life. As mentioned earlier, we intentionally did not add CSS to all the components at this stage, as the styling will be implemented once all the functionalities of this app are completed.

The next step is to capture user input as they type into the text field and directly save it into the component state variable. To do this, we'll be using the first React hook in this tutorial called useState.

State in React

In React, state is means an object that stores information about how a component works. It is important in React and helps to handle the changing parts of a user interface. The component keeps track of the state object, and you can change it using the setState() method.

Here are a few key points to keep in mind about state in React:

  • States in React are changeable and dynamic, meaning they can be updated throughout the lifecycle of a component.

  • A state holds information about the component it was declared in, and the component that declares a state is the owner of that state. No other components can directly access or modify it.

  • When the state of a component changes, the component will re-render itself, updating the corresponding elements in the DOM to reflect the new state. This makes it an efficient way to manage dynamic data and user input in React applications.

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 are destructured into state and setState variables respectively.

Having gained a brief understanding of the useState concept, let's modify the DisplayAllPosts component in the following ways:

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
Enter fullscreen mode Exit fullscreen mode

Here, two state variables, title and content, are created using the useState hook along with their corresponding updater functions, setTitle and setContent. Two functions, savePostTitleToState and savePostContentToState, are also defined to save user input values into the corresponding state variables. console.log() statements are added to these functions to view the input value as the user types.
Finally, the two functions are passed as props to the CreateNewPost component using the props keyword. Props are used to pass data, including functions and state, from a parent component, DisplayAllPosts in this case, to its child components, such as CreateNewPost.

The next step involves passing down the props data from the parent component, DisplayAllPosts, to its child component, CreateNewPost.

Open the CreateNewPost component and update the 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;
Enter fullscreen mode Exit fullscreen mode

To see the changes you have made, refresh your browser and open your browser console (press ctrl+shift+i if you are using Chrome) to view the captured data. Enter some text in the input fields and if everything works correctly, you should see a similar output like this:

alt text

We will now move on to the next step, which involves saving the post title and content captured from the user into a new state variable called allPosts. This will be done by handling the onClick event of the Save Post button.

In the DisplayAllPosts.jsx file, create a new state called allPost:

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);
  };
Enter fullscreen mode Exit fullscreen mode

The savePost() function will be used to save the current post's title and content into the allPost state variable. The function first generates a unique ID using the Date.now() method and then uses the setAllPost() function, which is provided by the useState() hook, to add a new post to the allPost array. The new post is an object with title, content, and id properties that are set to the values of the title, content, and id variables, respectively. Finally, the function logs the allPost array to the console to show the new post has been added.

Let's update the savePost function to reset the title and content once the post has been added to the allPost array:

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

Clearing the input field with useRef

We need to make sure a user can easily create a new post without having to manually delete the old content from the input fields. Clearing the state value will not affect the input field value on the DOM. To locate the input fields on the DOM and clear their value, we are going to use another React hook called useRef.

In React, the useRef hook creates a reference to a specific element or value in a component. This creates an object that can be updated without causing a re-render. With this hook, you can access and manipulate the properties of HTML elements like buttons or input fields, or store and access previous values of a component's props or state.

We are going to import useRef by updating the React import statement like this:

import React, { useState, useRef } from "react";

Next, we will initialize the useRef:

  const getTitle = useRef();
  const getContent = useRef();
Enter fullscreen mode Exit fullscreen mode

Then pass down the refs to CreateNewPost component as props:

      <CreateNewPost
        savePostTitleToState={savePostTitleToState}
        savePostContentToState={savePostContentToState}
        getTitle={getTitle}
        getContent={getContent}
      />
Enter fullscreen mode Exit fullscreen mode

After that, we'll navigate to the CreateNewPost.jsx and make it use the new props data we passed down to it.

The CreateNewPost component should be looking 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;
Enter fullscreen mode Exit fullscreen mode

The ref attribute is used to get the value of the input and textarea fields, and the functions getTitle and getContent passed in as props are used to set the value of the ref.

Open the DisplayAllPosts.jsx file and update the savePost function to look like this:

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

We used the event.preventDefault() to prevent the default refreshing behaviour of HTML form when a user clicks on the submit button.

The fourth and fifth lines of the function are used to clear the input fields by setting the value of the getTitle and getContent refs to an empty string.

To use the savePost function, we'll pass it down as props to the CreateNewPost component. Let's update the return statement in DisplayAllPosts.jsx file:

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

Open the 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;
Enter fullscreen mode Exit fullscreen mode

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.

The DisplayAllPosts component should be looking 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([]);
  // 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;
Enter fullscreen mode Exit fullscreen mode

You can refresh the browser now and launch the browser console to see if the captured data is being saved correctly into the AllPosts state variable.

You should have a similar output that looks like this:
alt text

Now that the post data has been captured successfully, it's time to display them in the DisplayAllPost component. But before then, let's render the CreateNewPost component only when a user clicks on the Add New button and remove the component immediately the user clicks on the Save Post button. To do that, let's update the 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;
Enter fullscreen mode Exit fullscreen mode

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 the isCreateNewPost state variable to swicth 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 it is invoked. Finally, 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.

After refreshing the browser, you can preview your changes. The output you see should be nearly identical to this one:

When you click on the Create New button, it should render the CreateNewPost component like so:

alt text

When you enter the post title and contents and click on the Save Post button, it should save them and render back the DisplayAllPosts component, but the post will not be displayed yet.
To display all the posts, we need to modify the Post component to receive the props that will be passed down to it from its parent component, DisplayAllPosts.

Open thePost.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
Enter fullscreen mode Exit fullscreen mode

Now that we are done with the Post component, let's modify the 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;
Enter fullscreen mode Exit fullscreen mode

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

alt text

You can click on the Create New button to add more posts and see all the 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 to modify a selected post once the Edit button is clicked.

Open the DisplayAllPosts.js and create a new state called isModifyPost below the isCreateNewPost state:

const [isModifyPost, setIsModifyPost] = useState(false);
Enter fullscreen mode Exit fullscreen mode

We will 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 the toggleCreateNewPost function:

  const toggleModifyPostComponent = () => {
    setIsModifyPost(!isModifyPost)
  }
Enter fullscreen mode Exit fullscreen mode

This function switches the value of the isModifyPost variable between true and false. If the previous value of the variable is false, it changes it to true, and if the previous value is true, it changes it to false.

Let's create another state called editPostId, below the isModifyPost state:

const [editPostId, setEditPostId] = useState("");
Enter fullscreen mode Exit fullscreen mode

This state variable will be used to save the id of the post that a user wants to modify.

After that, you will create another function called editPost below the toggleModifyPostComponent function:

  const editPost = id => {
    setEditPostId(id);
    toggleModifyPostComponent();
  };
Enter fullscreen mode Exit fullscreen mode

This function will be passed down to the Post component and get called from inside the 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 the ModifyPost component depending on the isModifyPost state boolean value.

We are saving the id of the post that a user wants to modify into the editPostId state variable because we want the updatePost function to have access to it.

Now, we will create a new function called updatePost. This function will be used to update the 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();
  };
Enter fullscreen mode Exit fullscreen mode

In this code, we used the map() method to loop through each post in the allPosts component to locate the post that the user wants to edit, based on the post id stored in the editPostId state variable. Then, we used the rest syntax (...) to modify only the title and content of the post, while keeping the id of the post unchanged. To prevent empty values from being saved when the user updates a post without making changes, we used the OR operator (||) to save the previous post title and content.

The next thing we need to do now is to render the ModifyPost component if the isModifyPost state variable is true.

In the DisplayAllPost.jsx, let's add the following code below the
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}
      />
    );
  }
Enter fullscreen mode Exit fullscreen mode

What we are tryig to achieve here is to preload the input fields in the ModifyPost component with the data of the selected post.
We also passed down the updatePost, saveTitleToState, savePostContentToState function to the ModifyPost component respectively. We have used saveTitleToState and savePostContentToState before in the CreateNewPost component to save user input value to the state variable.

Now we are going to use the props that was passed to the ModifyPost component. Open the 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;
Enter fullscreen mode Exit fullscreen mode

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 will call the updatePost function that was passed down to the ModifyPost component.
One more thing before we test the ModifyPost component, we will trigger the ModifyPost component once a user clicks on the edit button. Let's pass down the editPost function to the Post component from DisplayAllPosts component.

Let's modify the DisplayAllPosts component to render the Post component like this:

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>
    </>
  );
Enter fullscreen mode Exit fullscreen mode

Now we are going to update the Post component to use the editPost function that was passed to it.

The Post Component should be looking 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;
Enter fullscreen mode Exit fullscreen mode

Before running the app, let's compare the 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;
Enter fullscreen mode Exit fullscreen mode

Now we can refresh the browser to view the changes

Finally, we will 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 they click on the delete button.

Open the DisplayAllPosts.jsx file and create a deletePost function below editPost function:

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

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.

Next we are going to pass down the deletePost function from the DisplayAllPosts component 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>
    </>
  );
Enter fullscreen mode Exit fullscreen mode

Finally, we will 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;
Enter fullscreen mode Exit fullscreen mode

When a user click on the Delete button, it will invoke 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

Summary

This tutorial showed us how to create a basic blog frontend using React without making any API requests to a backend server. We didn't need to rely on a server to provide the data to our application, instead, we used React to create and manage our data. We also learned how to manipulate the state by performing CRUD (Create, Read, Update, Delete) operations on the state data. By doing so, we were able to add, read, update, and delete posts from our blog application.

We also used conditional rendering in our application to switch between two components. Conditional rendering allowed us to only display a specific component when a particular condition is met. This way, we could show or hide certain parts of our application based on user interactions or other factors. Overall, this tutorial provided a foundation for creating more complex React applications and using its features to manage data and manipulate the user interface.

Thanks for reading. The full code used in this tutorial can be found here: https://github.com/tope-olajide/react-simple-blog.

If you enjoy my post, please share it with others and give it some emojis so people can see it.

Top comments (2)

Collapse
 
kmert10 profile image
Mert Karakas

Great article. Just some feedback, there are some typos with your code and your descriptions. One example is:

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);
  };
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kingdavid profile image
Temitope

Thanks, I've fixed it.