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.
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:
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:
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:
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:
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:
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:
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:
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:
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 editPost
function.
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:
That's all!
The full code is here: https://github.com/tope-olajide/react-simple-blog.
Thanks for reading.
Discussion (1)
Great article. Just some feedback, there are some typos with your code and your descriptions. One example is: