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
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:
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;
Here, we modified the App component to display Hello World. Your browser should automatically refresh and display a similar output like this:
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>
);
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:
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;
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;
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:
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
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;
After refreshing your browser, you should have a typical output that looks like this:
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;
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
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
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;
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:
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);
};
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);
};
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();
Then pass down the refs to CreateNewPost
component as props:
<CreateNewPost
savePostTitleToState={savePostTitleToState}
savePostContentToState={savePostContentToState}
getTitle={getTitle}
getContent={getContent}
/>
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;
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 = "";
};
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}
/>
</>
);
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;
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;
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:
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;
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:
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
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;
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:
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);
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)
}
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("");
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();
};
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();
};
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}
/>
);
}
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;
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>
</>
);
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;
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;
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);
};
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>
</>
);
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;
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:
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)
Great article. Just some feedback, there are some typos with your code and your descriptions. One example is:
Thanks, I've fixed it.