DEV Community

Cover image for React Post Creation Form: Building the Frontend for Your Content Management System
WebCraft Notes
WebCraft Notes

Posted on • Originally published at webcraft-notes.com

React Post Creation Form: Building the Frontend for Your Content Management System

Following our successful backend implementation for post creation, it's time to build an intuitive frontend interface that will bring our CMS to life. While we've already added a posts page, today we'll focus on implementing a crucial feature - the post creation form.

In this comprehensive guide, we'll walk through building a dynamic post creation interface using React. We'll create a user-friendly form that includes all essential fields from our MongoDB schema - from basic title and subtitle inputs to meta information and a rich text editor for content management. We'll also implement proper navigation from the posts list to our new creation form, ensuring a seamless user experience. Okay, so let's start:

1. Preparing a new "create-post" route

As was mentioned we already have a "posts" page and now we need to add a top section with "create new post", and "filters" buttons, and also a "search" field.

  • create new "posts" folder inside the "views" folder of the "components" directory;

  • add new "PostsAction.component.jsx" and "postsAction.styles.scss" files;

  • we need to import some icons from the "MUI-icons" and "useNavigate" hook, and the "TextField" component for the search field and then return the JSX component;

import React from "react";
import { useNavigate } from "react-router-dom";
import "./postsAction.styles.scss";
import TextField from '@mui/material/TextField';
import AddIcon from '@mui/icons-material/Add';
import FilterAltIcon from '@mui/icons-material/FilterAlt';
import SearchIcon from '@mui/icons-material/Search';

const PostsAction = () => {
    const navigate = useNavigate();
    return (
        <div className="posts-action">
            <div className="posts-action__buttons">
                <button className="posts-action__buttons--item"
                    onClick={() => navigate("/post-form") }>
                    <AddIcon />
                </button>
                <button className="posts-action__buttons--item">
                    <FilterAltIcon />
                </button>
            </div>
            <div className="posts-action__search">
            <TextField
                label="Search"
                id="outlined-size-small"
                size="small"
                value={}
                onChange={(e) => ()}
                />
                <button className="posts-action__search--button">
                    <SearchIcon />
                </button>
            </div>
        </div>
    );
}

export default PostsAction;
Enter fullscreen mode Exit fullscreen mode
  • open the "Posts.jsx" file from the "posts" folder in the "views" directory, import our "PostsAction" component, and add it to the page;
import React from 'react';
import './posts.styles.scss';
import PostsAction from '../../components/views/posts/PostsAction.component';

const Posts = () => {
    return (
        <div className="Posts">
            <PostsAction />
        </div>
    );
}

export default Posts;
Enter fullscreen mode Exit fullscreen mode
  • we have to add a new "PostForm.jsx" file into the "post-form" folder in the "views" directory;

  • import "PostForm" into our "App.jsx" file, and then register a new route;

<Route path="/" element={<MainLayout />}>
  <Route index element={<Dashboard />} />
  <Route path='analytics' element={<Analytics />} />
  <Route path='posts' element={<Posts />} />
  <Route path="post-form" element={<PostForm />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

Great. In this part, we created a new section in the "posts" page with the control buttons and registered a new "create post" page. Let's go further and start working on a new post creation form.

2. Creating a Post Form Component

  • we need to import a few icons, hooks, our loader component, and some "MUI" components;

  • we will start from the end, and configure the return JSX section. We need to build a structured form with the text fields for each backend defined values like "title", "subTitle" etc...;

  • we have a body key in the post object, it is an array of possible sections (in our case Text or Image), and we will render through an Array method "map", and need to add an "Autocomplete" dropdown of those sections. Our starting component will look like:

import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from 'react-router-dom';
import Loader from "../../components/ui/loader/Loader.component";
import TextField from '@mui/material/TextField';
import Autocomplete from '@mui/material/Autocomplete';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
const postForm = () => {
return (
    <div className="post-form">
        <div className="post-form__container">
            <div className="post-form__container--header">
                <p className="post-form__container--header-text">Header</p>
                <div className="post-form--item">
                    <TextField 
                        className="post-form--item-input"
                        label="Title"
                        id="outlined-size-normal"
                        value={postForm.title}
                        onChange={(e) => setpostForm((prev) => ({ ...prev, title: e.target.value }))}/>
                </div>
                ... Additional Fields ...
            </div>
        </div>
        <div className="post-form__container">
            <div className="post-form__container--header">
                <p className="post-form__container--header-text">Body</p>
                <div className="post-form__body">
                    {sections.map((section, index) => (
                        <div key={index} className="post-form__body--section">
                            {renderSectionComponent(section, index)}
                        </div>
                    ))}
                </div>
                <div className="post-form--item post-form--item--body">
                <Autocomplete
                    disablePortal
                    id="combo-box-demo"
                    options={bodyOptions}
                    sx={{ width: 300 }}
                    value={bodyOptions.find(option => option.value === selectedSection) || null}
                    onChange={(event, newValue) => setSelectedSection(newValue ? newValue.value : null)}
                    renderInput={(params) => <TextField {...params} label="Choose Section Type" />}
                    />
                    <button className="post-form--item--body-button"
                        onClick={handleAddSection}>
                        {loading ? <Loader /> : 'Add Section'}
                    </button>
                </div>
            </div>
        </div>
         ... Settings Section ...
        <div className="post-form__actions">
            <div className="post-form__actions--button">
                <button className="post-form__actions--button-create" 
                    onClick={handleSubmit}>
                    {loading ? <Loader /> : slug ? 'Update Post' : 'Create Post'}
                </button>
            </div>
        </div>
    </div>
);
Enter fullscreen mode Exit fullscreen mode
  • let's create a "renderSectionComponent" function that will get type and return a JSX (image or text) editor or image uploader (we will take care of it in the next article);
const renderSectionComponent = (type, index) => {
    switch (slug && typeof type !== "string" ? type.type : type) {
        case 'image':
        return (
            <div className="post-form__container--header">
                ImageUploader
            </div>
        );
        case 'text':
        return (
            <div className="flex-column">
                Editor
            </div>
        );
        default:
        return null;
    }
};
Enter fullscreen mode Exit fullscreen mode
  • also we need to define the "selectedSection", and "postForm" value itself, loading;
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [selectedSection, setSelectedSection] = useState(null);
const [postForm, setpostForm] = useState({
    title: '',
    subTitle: '',
    slug: '',
    status: 'offline',
    archived: false,
    meta: {
        description: '',
        keywords: '',
        schema: '',
    },
    body: []
});
const bodyOptions = [
    { label: 'Image', value: 'image' },
    { label: 'Text', value: 'text' },
];
const languages = [
    { label: 'Ukrainian', value: 'uk' },
    { label: 'English', value: 'en' },
    { label: 'Spanish', value: 'es' },
];
Enter fullscreen mode Exit fullscreen mode
  • and our final touch, for now, will be creating a "handleSubmit" function that will get the form data, push created value, handle loader, and notification and send it to the server;
const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    postForm.created = {
        date: {
            day: String(new Date().getDate()).padStart(2, '0'),
            month: String(new Date().getMonth() + 1).padStart(2, '0'),
            year: String(new Date().getFullYear()),
            time: new Date().toTimeString().split(' ')[0],
        }
    };
    try {
        const response = await createNewPost({ post: postForm });
        if (response.status === 200) {
            dispatch(aPushNewNotification({
                type: 'success',
                text: response.message,
            }));
            setpostForm({
                title: '',
                subTitle: '',
                slug: '',
                status: 'offline',
                archived: false,
                meta: {
                    description: '',
                    keywords: '',
                    schema: '',
                },
                body: [],
            });
            setLoading(false);
            navigate('/posts');
        }
    } catch (error) {
        dispatch(aPushNewNotification({
            type: 'error',
            text: response.message,
        }));
        setLoading(false);
        console.log("Error:", error);
    }
};
Enter fullscreen mode Exit fullscreen mode

Let's restart our React app and check the UI result.

post creation form

Looks great, but we still do not have the possibility to upload images and work with the text, I suggest we fix this temporary problem in the next article.

You can find the complete code for this implementation in the archive.

Stay tuned for the next part where we'll transform this basic form into a full-featured post editor with media handling capabilities!

Found this post useful? ☕ A coffee-sized contribution goes a long way in keeping me inspired! Thank you)
Buy Me A Coffee

Next step: "Integrating Quill Editor and Image Upload Functionality in a React CMS"

Dev Diairies image

User Feedback & The Pivot That Saved The Project

🔥 Check out Episode 3 of Dev Diairies, following a successful Hackathon project turned startup.

Watch full video 🎥

Top comments (0)