Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Modern Full-Stack React Projects

You're reading from   Modern Full-Stack React Projects Build, maintain, and deploy modern web apps using MongoDB, Express, React, and Node.js

Arrow left icon
Product type Paperback
Published in Jun 2024
Publisher Packt
ISBN-13 9781837637959
Length 506 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Daniel Bugl Daniel Bugl
Author Profile Icon Daniel Bugl
Daniel Bugl
Arrow right icon
View More author details
Toc

Table of Contents (28) Chapters Close

Preface 1. Part 1:Getting Started with Full-Stack Development
2. Chapter 1: Preparing for Full-Stack Development FREE CHAPTER 3. Chapter 2: Getting to Know Node.js and MongoDB 4. Part 2:Building and Deploying Our First Full-Stack Application with a REST API
5. Chapter 3: Implementing a Backend Using Express, Mongoose ODM, and Jest 6. Chapter 4: Integrating a Frontend Using React and TanStack Query 7. Chapter 5: Deploying the Application with Docker and CI/CD 8. Part 3:Practicing Development of Full-Stack Web Applications
9. Chapter 6: Adding Authentication with JWT 10. Chapter 7: Improving the Load Time Using Server-Side Rendering 11. Chapter 8: Making Sure Customers Find You with Search Engine Optimization 12. Chapter 9: Implementing End-to-End Tests Using Playwright 13. Chapter 10: Aggregating and Visualizing Statistics Using MongoDB and Victory 14. Chapter 11: Building a Backend with a GraphQL API 15. Chapter 12: Interfacing with GraphQL on the Frontend Using Apollo Client 16. Part 4:Exploring an Event-Based Full-Stack Architecture
17. Chapter 13: Building an Event-Based Backend Using Express and Socket.IO 18. Chapter 14: Creating a Frontend to Consume and Send Events 19. Chapter 15: Adding Persistence to Socket.IO Using MongoDB 20. Part 5:Advancing to Enterprise-Ready Full-Stack Applications
21. Chapter 16: Getting Started with Next.js 22. Chapter 17: Introducing React Server Components 23. Chapter 18: Advanced Next.js Concepts and Optimizations 24. Chapter 19: Deploying a Next.js App 25. Chapter 20: Diving Deeper into Full-Stack Development 26. Index 27. Other Books You May Enjoy

Creating the user interface for our application

When designing the structure of a frontend, we should also consider the folder structure, so that our app can grow easily in the future. Similar to how we did for the backend, we will also put all our source code into a src/ folder. We can then group the files in separate folders for the different features. Another popular way to structure frontend projects is to group code by routes. Of course, it is also possible to mix them, for example, in Next.js projects we can group our components by features and then create another folder and file structure for the routes, where the components are used. For full-stack projects, it additionally makes sense to first separate our code by creating separate folders for the API integration and UI components.

Now, let’s define the folder structure for our project:

  1. Create a new src/api/ folder.
  2. Create a new src/components/ folder.

Tip

It is a good idea to start with a simple structure at first, and only nest more deeply when you actually need it. Do not spend too much time thinking about the file structure when starting a project, because usually, you do not know upfront how files should be grouped, and it may change later anyway.

After defining the high-level folder structure for our projects, let’s now take some time to consider the component structure.

Component structure

Based on what we defined in the backend, our blog application is going to have the following features:

  • Viewing a single post
  • Creating a new post
  • Listing posts
  • Filtering posts
  • Sorting posts

The idea of components in React is to have each component deal with a single task or UI element. We should try to make components as fine-grained as possible, in order to be able to reuse code. If we find ourselves copying and pasting code from one component to another, it might be a good idea to create a new component and reuse it in multiple other components.

Usually, when developing a frontend, we start with a UI mock-up. For our blog application, a mock-up could look as follows:

Figure 4.1 – An initial mock-up of our blog application

Figure 4.1 – An initial mock-up of our blog application

Note

In this book, we will not cover UI or CSS frameworks. As such, the components are designed and developed without styling. Instead, the book focuses on the full-stack aspect of the integration of backends with frontends. Feel free to use a UI framework (such as MUI), or a CSS framework (such as Tailwind) to style the blog application on your own.

When splitting up the UI into components, we use the single-responsibility principle, which states that every module should have responsibility over a single encapsulated part of the functionality.

In our mock-up, we can draw boxes around each component and subcomponent, and give them names. Keep in mind that each component should have exactly one responsibility. We start with the fundamental components that make up the app:

Figure 4.2 – Defining the fundamental components in our mock-up

Figure 4.2 – Defining the fundamental components in our mock-up

We defined a CreatePost component, with a form to create a new post, a PostFilter component to filter the list of posts, a PostSorting component to sort posts, and a Post component to display a single post.

Now that we have defined our fundamental components, we are going to look at which components logically belong together, thereby forming a group: we can group the Post components together in PostList, then make an App component to group everything together and define the structure of our app.

Now that we are done with structuring our React components, we can move on to implementing the static React components.

Implementing static React components

Before integrating with the backend, we are going to model the basic features of our application as static React components. Dealing with the static view structure of our application first makes sense, as we can play around and re-structure the application UI if needed, before adding integration to the components, which would make it harder and more tedious to move them around. It is also easier to deal only with the UI first, which helps us to get started quickly with projects and features. Then, we can move on to implementing integrations and handling state.

Let’s get started implementing the static components now.

The Post component

We have already thought about which elements a post has during the creation of the mock-up and the design of the backend. A post should have a title, contents, and an author.

Let’s implement the Post component now:

  1. First, create a new src/components/Post.jsx file.
  2. In that file, import PropTypes:
    import PropTypes from 'prop-types'
  3. Define a function component, accepting title, contents, and author props:
    export function Post({ title, contents, author }) {
  4. Next, render all props in a way that resembles the mock-up:
      return (
        <article>
          <h3>{title}</h3>
          <div>{contents}</div>
          {author && (
            <em>
              <br />
              Written by <strong>{author}</strong>
            </em>
          )}
        </article>
      )
    }

Tip

Please note that you should always prefer spacing via CSS, rather than using the <br /> HTML tag. However, we are focusing on the UI structure and integration with the backend in this book, so we simply use HTML whenever possible.

  1. Now, define propTypes, making sure only title is required:
    Post.propTypes = {
      title: PropTypes.string.isRequired,
      contents: PropTypes.string,
      author: PropTypes.string,
    }

Info

PropTypes are used to validate the props passed to React components and to ensure that we are passing the correct props when using JavaScript. When using a type-safe language, such as TypeScript, we can instead do this by directly typing the props passed to the component.

  1. Let’s test out our component by replacing the src/App.jsx file with the following contents:
    import { Post } from './components/Post.jsx'
    export function App() {
      return (
        <Post
          title='Full-Stack React Projects'
          contents="Let's become full-stack developers!"
          author='Daniel Bugl'
        />
      )
    }
  2. Edit src/main.jsx and update the import of the App component, because we are now not using export default anymore:
    import { App } from './App.jsx'

Info

I personally tend to prefer not using default exports, as they make it harder to re-group and re-export components and functions from other files. Also, they allow us to change the names of the components, which could be confusing. For example, if we change the name of a component, the name when importing it is not changed automatically.

  1. Also, remove the following line from src/main.jsx:
    import './index.css'
  2. Finally, we can delete the index.css and App.css files, as they are not needed anymore.

Now that our static Post component has been implemented, we can move on to the CreatePost component.

The CreatePost component

We’ll now implement a form to allow for the creation of new posts. Here, we provide fields for author and title and a <textarea> element for the contents of the blog post.

Let’s implement the CreatePost component now:

  1. Create a new src/components/CreatePost.jsx file.
  2. Define the following component, which contains a form to enter the title, author, and contents of a blog post:
    export function CreatePost() {
      return (
        <form onSubmit={(e) => e.preventDefault()}>
          <div>
            <label htmlFor='create-title'>Title: </label>
            <input type='text' name='create-title' id='create-title' />
          </div>
          <br />
          <div>
            <label htmlFor='create-author'>Author: </label>
            <input type='text' name='create-author' id='create-author' />
          </div>
          <br />
          <textarea />
          <br />
          <br />
          <input type='submit' value='Create' />
        </form>
      )
    }

    In the preceding code block, we defined an onSubmit handler and called e.preventDefault() on the event object to avoid a page refresh when the form is submitted.

  3. Let’s test the component out by replacing the src/App.jsx file with the following contents:
    import { CreatePost } from './components/CreatePost.jsx'
    export function App() {
      return <CreatePost />
    }

As you can see, the CreatePost component renders fine. We can now move on to the PostFilter and PostSorting components.

Tip

If you want to test out multiple components at once and keep the tests around for later, or build a style guide for your own component library, you should look into Storybook (https://storybook.js.org), which is a useful tool to build, test, and document UI components in isolation.

The PostFilter and PostSorting components

Similar to the CreatePost component, we will be creating two components that provide input fields to filter and sort posts. Let’s start with PostFilter:

  1. Create a new src/components/PostFilter.jsx file.
  2. In this file, we import PropTypes:
    import PropTypes from 'prop-types'
  3. Now, we define the PostFilter component and make use of the field prop:
    export function PostFilter({ field }) {
      return (
        <div>
          <label htmlFor={`filter-${field}`}>{field}: </label>
          <input
            type='text'
            name={`filter-${field}`}
            id={`filter-${field}`}
          />
        </div>
      )
    }
    PostFilter.propTypes = {
      field: PropTypes.string.isRequired,
    }

    Next, we are going to define the PostSorting component.

  4. Create a new src/components/PostSorting.jsx file.
  5. In this file, we create a select input to select which field to sort by. We also create another select input to select the sort order:
    import PropTypes from 'prop-types'
    export function PostSorting({ fields = [] }) {
      return (
        <div>
          <label htmlFor='sortBy'>Sort By: </label>
          <select name='sortBy' id='sortBy'>
            {fields.map((field) => (
              <option key={field} value={field}>
                {field}
              </option>
            ))}
          </select>
          {' / '}
          <label htmlFor='sortOrder'>Sort Order: </label>
          <select name='sortOrder' id='sortOrder'>
            <option value={'ascending'}>ascending</option>
            <option value={'descending'}>descending</option>
          </select>
        </div>
      )
    }
    PostSorting.propTypes = {
      fields: PropTypes.arrayOf(PropTypes.string).isRequired,
    }

Now we have successfully defined UI components to filter and sort posts. In the next step, we are going to create a PostList component to combine the filter and sorting with a list of posts.

The PostList component

After implementing the other post-related components, we can now implement the most important part of our blog app, that is, the feed of blog posts. For now, the feed is simply going to show a list of blog posts.

Let’s start implementing the PostList component now:

  1. Create a new src/components/PostList.jsx file.
  2. First, we import Fragment, PropTypes, and the Post component:
    import { Fragment } from 'react'
    import PropTypes from 'prop-types'
    import { Post } from './Post.jsx'
  3. Then, we define the PostList function component, accepting a posts array as a prop. If posts is not defined, we set it to an empty array, by default:
    export function PostList({ posts = [] }) {
  4. Next, we render all posts by using the .map function and the spread syntax:
      return (
        <div>
          {posts.map((post) => (
            <Post {...post} key={post._id} />
          ))}
        </div>
      )
    }

    We return the <Post> component for each post, and pass all the keys from the post object to the component as props. We do this by using the spread syntax, which has the same effect as listing all the keys from the object manually as props, like so:

    <Post
      title={post.title}
      author={post.author}
      contents={post.contents}
    />

Note

If we are rendering a list of elements, we have to give each element a unique key prop. React uses this key prop to efficiently compute the difference between two lists when the data has changed.

We used the map function, which applies a function to all the elements of an array. This is similar to using a for loop and storing all the results, but it is more concise, declarative, and easier to read! Alternatively, we could do the following instead of using the map function:

let renderedPosts = []
let index = 0
for (let post of posts) {
  renderedPosts.push(<Post {...post} key={post._id} />)
  index++
}
return (
  <div>
    {renderedPosts}
  </div>
)

However, using this style is not recommended with React.

  1. We also still need to define the prop types. Here, we can make use of the prop types from the Post component, by wrapping it inside the PropTypes.shape() function, which defines an object prop type:
    PostList.propTypes = {
      posts: PropTypes.arrayOf(PropTypes.shape(Post.propTypes)).isRequired,
    }
  2. In the mock-up, we have a horizontal line after each blog post. We can implement this without an additional <div> container element, by using Fragment, as follows:
          {posts.map((post) => (
            <Fragment key={post._id}>
              <Post {...post} />
              <hr />
            </Fragment>
          ))}

Note

The key prop always has to be added to the uppermost parent element that is rendered within the map function. In this case, we had to move the key prop from the Post component to Fragment.

  1. Again, we test our component by editing the src/App.jsx file:
    import { PostList } from './components/PostList.jsx'
    const posts = [
      {
        title: 'Full-Stack React Projects',
        contents: "Let's become full-stack developers!",
        author: 'Daniel Bugl',
      },
      { title: 'Hello React!' },
    ]
    export function App() {
      return <PostList posts={posts} />
    }

    Now we can see that our app lists all the posts that we defined in the posts array.

As you can see, listing multiple posts via the PostList component works fine. We can now move on to putting the app together.

Putting the app together

After implementing all the components, we now have to put everything together in the App component. Then, we will have successfully reproduced the mock-up!

Let’s start modifying the App component and putting our blog app together:

  1. Open src/App.jsx and add imports for the CreatePost, PostFilter, and PostSorting components:
    import { PostList } from './components/PostList.jsx'
    import { CreatePost } from './components/CreatePost.jsx'
    import { PostFilter } from './components/PostFilter.jsx'
    import { PostSorting } from './components/PostSorting.jsx'
  2. Adjust the App component to contain all the components:
    export function App() {
      return (
        <div style={{ padding: 8 }}>
          <CreatePost />
          <br />
          <hr />
          Filter by:
          <PostFilter field='author' />
          <br />
          <PostSorting fields={['createdAt', 'updatedAt']} />
          <hr />
          <PostList posts={posts} />
        </div>
      )
    }
  3. After saving the file, the browser should automatically refresh, and we can now see the full UI:
Figure 4.3 – Full implementation of our static blog app, according to the mock-up

Figure 4.3 – Full implementation of our static blog app, according to the mock-up

As we can see, all of the static components that we defined earlier are rendered together in one App component. Our app now looks just like a mock-up. Next, we can move on to integrating our components with the backend service.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image