Project Structure

Frontend Folder Structure

The updated dependency in package.json file of frontend will look like:

"dependencies": {
"@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.7",
"nodemon": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},

Example: Create the required files and write the following code.

CSS




/* App.css */
 
@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
 
:root{
  --primary-color: #0DFF92;
  --dark-color:#222222;
  --light-color: #f0f0f0;
}
 
body, html{
  height: 100%;
  background: var(--dark-color)
}
 
* > *{
    font-family: 'Poppins', sans-serif;
}
 
.container{
  display: block;
  position: relative;
  margin: 40px auto;
  height: auto;
  width: 800px;
  padding: 20px;
}
 
.container .title{
    font-size: 3em;
    text-align: center;
    border: 5px solid var(--primary-color);
    padding: .3em .2em;
    border-radius: 4px;
}
 
.text-light {
    color: var(--light-color)
}
 
.container ul{
  list-style: none;
  margin: 0;
  padding: 0;
    overflow: auto;
}
 
.container .questions{
    padding: 3em;
}
 
/*
.container .questions .qid{
  padding: .2em .7em;
  color: #222222;
  background-color: #0DFF92;
  border-radius: 50px;
} */
 
.container .grid{
    margin-top: 3em;
    display: grid;
    grid-template-columns: 1fr 1fr;
}
 
 
.container .btn{
    padding: .2em 1.7em;
    border: none;
    border-radius: .1em;
    font-size: 1.2em;
}
 
.container .btn:hover{
    cursor: pointer;
    background-color: #f0f0f0;
    color: #202020;
}
 
.next{
    background-color: var(--primary-color);
    justify-self: flex-end;
}
 
.prev{
    background-color: #faff5a;
    justify-self: flex-start;
}
 
ul li{
  color: #AAAAAA;
  display: block;
  position: relative;
  float: left;
  width: 100%;
  height: 100px;
    border-bottom: 1px solid #333;
}
 
ul li input[type=radio]{
  position: absolute;
  visibility: hidden;
}
 
ul li label{
  display: block;
  position: relative;
  font-weight: 300;
  font-size: 1.35em;
  padding: 25px 25px 25px 80px;
  margin: 10px auto;
  height: 30px;
  z-index: 9;
  cursor: pointer;
  -webkit-transition: all 0.25s linear;
}
 
ul li:hover label{
    color: #FFFFFF;
}
 
ul li .check{
  display: block;
  position: absolute;
  border: 5px solid #AAAAAA;
  border-radius: 100%;
  height: 25px;
  width: 25px;
  top: 30px;
  left: 20px;
    z-index: 5;
    transition: border .25s linear;
    -webkit-transition: border .25s linear;
}
 
ul li:hover .checked {
  border: 5px solid #FFFFFF;
}
 
ul li .check::before {
  display: block;
  position: absolute;
    content: '';
  border-radius: 100%;
  height: 15px;
  width: 15px;
  top: 5px;
    left: 5px;
  margin: auto;
    transition: background 0.25s linear;
    -webkit-transition: background 0.25s linear;
}
 
 
input[type=radio]:checked ~ .check {
  border: 5px solid var(--primary-color)
}
 
input[type=radio]:checked ~ .check::before{
  background: var(--primary-color)
}
 
input[type=radio]:checked ~ .text-primary{
  color: var(--primary-color)
}
 
/* To get selected option we are using this checked class */
.checked {
  border: 5px solid var(--primary-color) !important;
}
 
.checked::before{
  background: var(--primary-color)
}


CSS




/* index.css */
 
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
 
code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}


CSS




/* Main.css */
 
.container ol li {
  font-size: 1.4em;
  color: #cecece;
}
 
.start {
  display: flex;
  justify-content: center;
  padding-top: 2em;
}
 
.start .btn {
  padding: .2em 1.7em;
  border: none;
  border-radius: .1em;
  font-size: 1.2em;
  color: #202020;
  text-decoration: none;
  background-color: #faff5a;
}
 
#form {
  display: flex;
  justify-content: center;
  margin-top: 4em;
}
 
#form .userid {
  padding: .7em 2em;
  width: 50%;
  border: none;
  border-radius: 3px;
  font-size: 1em;
}


CSS




/* Result.css */
 
.flex-center {
  display: flex;
  justify-content: center;
  flex-direction: column;
  border: 1px solid #cecece;
  padding: 3em 4em;
  gap: 1em;
}
 
.container .flex {
  display: flex;
  justify-content: space-between;
 
}
 
.container .flex span {
  font-size: 1.4em;
  color: #cecece;
}
 
.container .flex span.achive {
  font-weight: bold;
  color: #ff2a66;
  color: #2aff95;
}
 
table {
  width: 100%;
}
 
.table-header {
  color: #cecece;
  font-size: 1.1em;
  text-align: center;
  background: #212121;
  padding: 18px 0;
}
 
.table-body {
  font-size: 1.1em;
  text-align: center;
  /* color: #cecece; */
  background: #d8d8d8;
  padding: 18px 0;
}
 
.table-header>tr>td {
  border: 1px solid #faff5a;
}


Javascript




// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './styles/index.css';
import App from './components/App';
 
 
/** Redux Store */
import store from './redux/store';
import { Provider } from 'react-redux';
 
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <Provider store={store}>
        <App />
    </Provider>
);


Javascript




// Result.js
 
import React, { useEffect } from 'react'
import '../styles/Result.css';
import { Link } from 'react-router-dom';
 
import ResultTable from './ResultTable';
import {
    useDispatch,
    useSelector
} from 'react-redux';
import {
    attempts_Number,
    earnPoints_Number,
    flagResult
} from '../helper/helper';
 
/** import actions  */
import {
    resetAllAction
} from '../redux/question_reducer';
import {
    resetResultAction
} from '../redux/result_reducer';
import {
    usePublishResult
} from '../hooks/setResult';
 
 
export default function Result() {
 
    const dispatch = useDispatch()
    const { questions: { queue, answers }, result:
        { result, userId } } = useSelector(state => state)
 
    const totalPoints = queue.length * 10;
    const attempts = attempts_Number(result);
    const earnPoints = earnPoints_Number(result, answers, 10)
    const flag = flagResult(totalPoints, earnPoints)
 
 
    /** store user result */
    usePublishResult({
        result,
        username: userId,
        attempts,
        points: earnPoints,
        achived: flag ? "Passed" : "Failed"
    });
 
    function onRestart() {
        dispatch(resetAllAction())
        dispatch(resetResultAction())
    }
 
    return (
        <div className='container'>
            <h1 className='title text-light'>Quiz Application</h1>
 
            <div className='result flex-center'>
                <div className='flex'>
                    <span>Username</span>
                    <span className='bold'>{userId || ""}</span>
                </div>
                <div className='flex'>
                    <span>Total Quiz Points : </span>
                    <span className='bold'>{totalPoints || 0}</span>
                </div>
                <div className='flex'>
                    <span>Total Questions : </span>
                    <span className='bold'>{queue.length || 0}</span>
                </div>
                <div className='flex'>
                    <span>Total Attempts : </span>
                    <span className='bold'>{attempts || 0}</span>
                </div>
                <div className='flex'>
                    <span>Total Earn Points : </span>
                    <span className='bold'>{earnPoints || 0}</span>
                </div>
                <div className='flex'>
                    <span>Quiz Result</span>
                    <span style={{ color: `${flag ? "#2aff95" : "#ff2a66"}` }}
                        className='bold'>{flag ? "Passed" : "Failed"}</span>
                </div>
            </div>
 
            <div className="start">
                <Link className='btn' to={'/'}
                    onClick={onRestart}>Restart</Link>
            </div>
 
            <div className="container">
                {/* result table */}
                <ResultTable></ResultTable>
            </div>
        </div>
    )
}


Javascript




// Questions.js
 
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
 
 
/** Custom Hook */
import { useFetchQestion } from '../hooks/FetchQuestion'
import { updateResult } from '../hooks/setResult'
 
 
export default function Questions({ onChecked }) {
 
    const [checked, setChecked] = useState(undefined)
    const { trace } = useSelector(state => state.questions);
    const result = useSelector(state => state.result.result);
    const [{ isLoading, apiData, serverError }] = useFetchQestion()
 
    const questions = useSelector(state =>
        state.questions.queue[state.questions.trace])
    const dispatch = useDispatch()
 
    useEffect(() => {
        dispatch(updateResult({ trace, checked }))
    }, [checked])
 
    function onSelect(i) {
        onChecked(i)
        setChecked(i)
        dispatch(updateResult({ trace, checked }))
    }
 
 
    if (isLoading) return <h3 className='text-light'>isLoading</h3>
    if (serverError) return
    <h3 className='text-light'>
        {serverError || "Unknown Error"}
    </h3>
 
    return (
        <div className='questions'>
            <h2 className='text-light'>{questions?.question}</h2>
 
            <ul key={questions?.id}>
                {
                    questions?.options.map((q, i) => (
                        <li key={i}>
                            <input
                                type="radio"
                                value={false}
                                name="options"
                                id={`q${i}-option`}
                                onChange={() => onSelect(i)}
                            />
 
                            <label className='text-primary'
                                htmlFor={`q${i}-option`}>{q}</label>
                            <div className={
                                `check ${result[trace] == i ? 'checked' : ''}`}>
                            </div>
                        </li>
                    ))
                }
            </ul>
        </div>
    )
}


Javascript




// ResultTable.js
 
import React, { useEffect, useState } from 'react'
import { getServerData } from '../helper/helper'
 
export default function ResultTable() {
 
    const [data, setData] = useState([])
 
    useEffect(() => {
        getServerData(`
        ${process.env.REACT_APP_SERVER_HOSTNAME}/api/result`, (res) => {
            setData(res)
        })
    })
 
    return (
        <div>
            <table>
                <thead className='table-header'>
                    <tr className='table-row'>
                        <td>Name</td>
                        <td>Attemps</td>
                        <td>Earn Points</td>
                        <td>Result</td>
                    </tr>
                </thead>
                <tbody>
                    {!data ?? <div>No Data Found </div>}
                    {
                        data.map((v, i) => (
                            <tr className='table-body' key={i}>
                                <td>{v?.username || ''}</td>
                                <td>{v?.attempts || 0}</td>
                                <td>{v?.points || 0}</td>
                                <td>{v?.achived || ""}</td>
                            </tr>
                        ))
                    }
 
                </tbody>
            </table>
        </div>
    )
}


Javascript




// helper.js
 
import { useSelector } from "react-redux";
import { Navigate } from "react-router-dom";
import axios from 'axios'
 
export function attempts_Number(result) {
    return result.filter(r => r !== undefined).length;
}
 
export function earnPoints_Number(result, answers, point) {
    return result.map((element, i) =>
        answers[i] === element).filter(i => i).map(i => point).reduce(
            (prev, curr) => prev + curr, 0);
}
 
export function flagResult(totalPoints, earnPoints) {
    return (totalPoints * 50 / 100) < earnPoints; /** earn 50% marks */
}
 
/** check user auth  */
export function CheckUserExist({ children }) {
    const auth = useSelector(state => state.result.userId)
    return auth ? children :
        <Navigate to={'/'} replace={true}></Navigate>
}
 
/** get server data */
export async function getServerData(url, callback) {
    const data = await (await axios.get(url))?.data;
    return callback ? callback(data) : data;
}
 
 
/** post server data */
export async function postServerData(url, result, callback) {
    const data = await (await axios.post(url, result))?.data;
    return callback ? callback(data) : data;
}


Javascript




// FetchQuestion.js
 
import { useEffect, useState } from "react"
import { useDispatch } from "react-redux";
import { getServerData } from "../helper/helper";
 
/** redux actions */
import * as Action from '../redux/question_reducer'
 
/*
 fetch question hook to fetch api data and set value to store
 */
export const useFetchQestion = () => {
    const dispatch = useDispatch();
    const [getData, setGetData] = useState({
        isLoading: false,
        apiData: [], serverError: null
    });
 
    useEffect(() => {
        setGetData(prev => ({ ...prev, isLoading: true }));
 
        /** async function fetch backend data */
        (async () => {
            try {
                const [{ questions, answers }] = await getServerData(`
                ${process.env.REACT_APP_SERVER_HOSTNAME}/api/questions`,
                    (data) => data)
 
                if (questions.length > 0) {
                    setGetData(prev => ({ ...prev, isLoading: false }));
                    setGetData(prev => ({ ...prev, apiData: questions }));
 
                    /** dispatch an action */
                    dispatch(Action.startExamAction({
                        question: questions, answers
                    }))
 
                } else {
                    throw new Error("No Question Avalibale");
                }
            } catch (error) {
                setGetData(prev => ({ ...prev, isLoading: false }));
                setGetData(prev => ({ ...prev, serverError: error }));
            }
        })();
    }, [dispatch]);
 
    return [getData, setGetData];
}
 
 
/** MoveAction Dispatch function */
export const MoveNextQuestion = () => async (dispatch) => {
    try {
        dispatch(Action.moveNextAction()); /** increase trace by 1 */
    } catch (error) {
        console.log(error)
    }
}
 
/** PrevAction Dispatch function */
export const MovePrevQuestion = () => async (dispatch) => {
    try {
        dispatch(Action.movePrevAction()); /** decrease trace by 1 */
    } catch (error) {
        console.log(error)
    }
}


Javascript




import { postServerData } from '../helper/helper'
import * as Action from '../redux/result_reducer'
 
export const PushAnswer = (result) => async (dispatch) => {
    try {
        await dispatch(Action.pushResultAction(result))
    } catch (error) {
        console.log(error)
    }
}
export const updateResult = (index) => async (dispatch) => {
    try {
        dispatch(Action.updateResultAction(index));
    } catch (error) {
        console.log(error)
    }
}
 
/** insert user data */
export const usePublishResult = (resultData) => {
    const { result, username } = resultData;
    (async () => {
        try {
            if (result !== [] && !username)
                throw new Error("Couldn't get Result");
            await postServerData(`http://localhost:${process.env.PORT}/api/result`,
                resultData, data => data)
        } catch (error) {
            console.log(error)
        }
    })();
}


Javascript




// question_reducer.js
 
import { createSlice } from "@reduxjs/toolkit";
 
/** create reducer */
export const questionReducer = createSlice({
    name: 'questions',
    initialState: {
        queue: [],
        answers: [],
        trace: 0
    },
    reducers: {
        startExamAction: (state, action) => {
            let { question, answers } = action.payload
            return {
                ...state,
                queue: question,
                answers
            }
        },
        moveNextAction: (state) => {
            return {
                ...state,
                trace: state.trace + 1
            }
        },
        movePrevAction: (state) => {
            return {
                ...state,
                trace: state.trace - 1
            }
        },
        resetAllAction: () => {
            return {
                queue: [],
                answers: [],
                trace: 0
            }
        }
    }
})
 
export const { startExamAction,
    moveNextAction,
    movePrevAction,
    resetAllAction } = questionReducer.actions;
 
export default questionReducer.reducer;


Javascript




// result_reducer.js
 
import { createSlice } from "@reduxjs/toolkit"
 
export const resultReducer = createSlice({
    name: 'result',
    initialState: {
        userId: null,
        result: []
    },
    reducers: {
        setUserId: (state, action) => {
            state.userId = action.payload
        },
        pushResultAction: (state, action) => {
            state.result.push(action.payload)
        },
        updateResultAction: (state, action) => {
            const { trace, checked } = action.payload;
            state.result.fill(checked, trace, trace + 1)
        },
        resetResultAction: () => {
            return {
                userId: null,
                result: []
            }
        }
    }
})
 
export const { setUserId,
    pushResultAction,
    resetResultAction,
    updateResultAction } = resultReducer.actions;
 
export default resultReducer.reducer;


Javascript




// store.js
 
import {
    combineReducers,
    configureStore
} from '@reduxjs/toolkit';
 
/** call reducers */
import questionReducer from './question_reducer';
import resultReducer from './result_reducer';
 
const rootReducer = combineReducers({
    questions: questionReducer,
    result: resultReducer
})
 
/** create store with reducer */
export default configureStore({ reducer: rootReducer });


Javascript




import './App.css';
 
import {
    createBrowserRouter,
    RouterProvider
} from 'react-router-dom'
 
/** import components */
import Main from './Main.js';
import Quiz from './Quiz.js';
import Result from './Result.js';
import { CheckUserExist } from '../src/helper/';
 
/** react routes */
const router = createBrowserRouter([
    {
        path: '/',
        element: <Main></Main>
    },
    {
        path: '/quiz',
        element: <CheckUserExist><Quiz /></CheckUserExist>
    },
    {
        path: '/result',
        element: <CheckUserExist><Result /></CheckUserExist>
    },
])
 
function App() {
    return (
        <>
            <RouterProvider router={router} />
        </>
    );
}
 
export default App;


Quiz App using MERN Stack

In this article, we’ll walk through the step-by-step process of creating a complete quiz application with MongoDB, ReactJS, ExpressJS, and NodeJS. This application will provide users with a user-friendly interface for taking and submitting quizzes and a scoreboard to check their standing among others.

Similar Reads

Prerequisites:

React JS MongoDB ExpressJS NodeJS MERN Stack BootStrap...

Approach to Create a Quiz App with MERN Stack:

Backend:...

Steps to Create the Backend Server:

Step 1: Create a directory for the project....

Project Structure:

Backend Folder Structure...

Steps to Setup Frontend with React

...

Project Structure:

...

Steps to run the App:

...