Learn React JS by building this one Web app

thumbnail

Hello friends, Today in this post we will learn React JS by building a Quiz web app. So here we will try to implement React concepts in building this Quiz web app so by the end of this post you will be confident about React and able to build your own web apps. I have divided each concept into steps to better track where we are in. We will cover more theoretical knowledge in form of questions and answers in our next post. Also, this post will be updated based on your suggestion, please feel to contact/comment with us for suggestions that will help in improving this post.

Prerequisites —

Basic Understanding of HTML, CSS, and Javascript. If you are not familiar with this you can refer to w3schools to learn the basics of HTML, CSS, and Javascript.

W3Schools

Let’s start with a basic understanding of React js.

What is React JS?

  1. It is a library for building User Interfaces.
  2. Used for building Single Page Applications.
  3. It helps in building complex UI by splitting it into simpler components and helps in component reusability.

How does React work?

Browser re-renders the page only when DOM (Document Object Model) is updated. But, updating this DOM would be a heavy task so React actually uses a virtual DOM before updating the Real DOM. So whenever any changes are there it is first updated to virtual DOM and it will compare it with real DOM using the diffing algorithm which compares for the changes and update only those elements which are changed.

So now let's work on a practical approach.

The first step is to install Node JS

Download | Node.js

You can download the latest or node version greater than 14.0.0, after downloading, install it.

I am using VSCode as my code editor, feel free to choose your own favorite editor.

create-react-app

npx create-react-app quiz-app

npx comes along with the npm when you install node js, so this command will help us in creating a react app with basic boilerplate code which contains everything you need to get started with React JS, so basically it will create a Hello World Program in React.

Now it's a show time, open your command prompt Ctrl + J in vs code, and type npm run start

In your browser, you will be able to view the web application

Writing JSX (Javascript XML)

In React, writing HTML inside a javascript file called JSX this HTML is internally converted to Javascript objects, and updated to DOM. Without JSX syntax for creating a react component is

React.createElement(elementName, props, children)

Eg: If I need to create a simple button then

React.createElement(‘button’, {onClick: ()=>{console.log(“hello world”}}, “HEllo”)

We will write our first JSX for the home quiz page, so start with basic HTML tags like h2, and p tags to display a welcome screen

import React from "react";
function App(){
return (
    <div>
      <h2>Quizish</h2>
      <p>Welcome to React Quiz!!!</p>
    </div>
  );
};
export default App;

Functional / Class Component

In React we have class and functional components, initially when react was introduced class components are called stateful components and are used when the component requires state changes whereas functional components are read-only or stateless components, sometimes called the dumb component it was used only for a presentational purpose but after the react v16.8.2 with the introduction to hooks functional components are no longer a dumb component now it can be also used for managing state as well. So Functional components are used more widely than class components and we will focus more on functional components rather than class components. Let's create a new folder component under src and create a new component named Welcome.js.

import React from "react";
const Welcome = () => {
return (
    <div>
      <h2>Quizish</h2>
      <p>Welcome to React Quiz!!!</p>
    </div>
  );
};
export default Welcome;

Same using class component will be

import React, { Component } from 'react'
export class Welcome extends Component {
  render() {
    return (
      <div>
      <h2>Quizish</h2>
      <p>Welcome to React Quiz!!!</p>
    </div>
    )
  }
}
export default Welcome

Now you can import this component into App.js and use it.

import React from "react";
function App(){
return (
    <Welcome/>
  );
};
export default App;

Props

Now let’s create our next component to understand these props. Create a new component Button.js. We have chosen a button because we need to reuse this button in different components/pages where the only label, onclick function, and disabled would be different rest would be common so with the use of props we can reuse the components whenever needed. So props are an object which will have all the variables/functions which we have passed and we have extracted our properties by using brace brackets.

import React from "react";
const Button = (props) => {
  const { label, onClick} = props;
  return (
    <button disabled={!onClick} onClick={onClick}>
      {label}
    </button>
  );
};
export default Button;

Now in our welcome.js component, we will use this component to create a Let’s start button

import React from "react";
const Welcome = () => {
return (
    <div>
      <h2>Quizish</h2>
      <p>Welcome to React Quiz!!!</p>
</div>
);
}
export default Welcome;
      

State

In React JS state is a variable that helps in re-rendering the components whenever this state variable changes. We have useState() hook/function to define the state variable in react. This useState() returns an array of two values one is the state value and another one is the function that we use to update this state variable. This State variable is immutable we cannot update this value directly so we should use the function returned by useState() to update this value. useState() function has an optional parameter as an initial value, it can be anything from number, string, object, array, function, etc.

Usage: const [val, setVal] = useState(0)

We will create a state variable based on this we will show either show welcome screen or quiz screen. So in App.js create this state variable as below. Also, we need to create a new component QuizContainer.js

./src/components/QuizContainer.js

import React from "react";
const QuizContainer = () => {
return (
    <div>
      <h2>QuizContainer</h2>
    </div>
  );
};
export default QuizContainer;

./src/App.js

import React, {useState} from "react";
import Welcome from "./components/Welcome";
import QuizContainer from "./components/QuizContainer";
function App(){
const [start, setStart] = useState(false)
return (
    <div>
      <Welcome/>
      <QuizContainer />
    </div>
  );
};
export default App;

Conditional Rendering

Here, I have rendered welcome based on a state variable start if it is false then the welcome screen will be shown, or else the quiz page will be shown. We can do it in two ways, either use an if statement or a ternary operator.

import React, {useState} from "react";
import Welcome from "./components/Welcome";
import QuizContainer from "./components/QuizContainer";
function App(){
const [start, setStart] = useState(false)
// Type - 1
if (start) return <QuizContainer/>
else return <Welcome/>
//Type - 2
/*return (
    start ? 
      <Welcome/>
      : <QuizContainer />
    </div>
  );
};*/
export default App;

Rendering List

We will use the javascript map function to render the list of quizzes. We have sample quiz data which we will use to render quiz data. Create a new folder static and create quizData.js which is a javascript list. In our QuizContainer.js, we will show one quiz at a time but we have a list of choices for the single quiz. we will iterate over it and render using the javascript map function.

./src/static/quizData.js

export const quizes = [
  {
    question: "React is a ___ ?",
    choices: ["Library", "Framework", "Language", "Technology"],
    answer: 0,
  },
  {
    question: "Angular JS is a ___ ?",
    choices: ["Library", "Framework", "Language", "Technology"],
    answer: 1,
  },
];

We will create two-state variables, one for storing the index of the current quiz initialized to -1 and the other for storing the answers which are initialized to an empty list. Pass this state and set function variables to QuizContainer. Now we have multiple conditions for which components differ if currentQuiz is -1 we need to show Welcome, if greater than or equal to 0 and less total quizzes length then QuizContainer else Results component.

./src/App.js

import React, {useState} from "react";
import { quizes } from "./static/quizData";
import Welcome from "./components/Welcome";
import QuizContainer from "./components/QuizContainer";
import Results from "./components/Results";
function App(){
const [currentQuiz, setCurrentQuiz] = useState(-1)
const [selectedAnswers, setSelectedAnswers] = useState([])
return (<div>
    {currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
</div>
  );
};
export default App;

We will first get the current quiz by accessing the quiz data with the current index. For onclick function when the user selects the choice, we are setting the answer with respect to the current quiz index in selectedAnswers list. At last, we have buttons to navigate to the next quiz by incrementing the currentQuiz.

./src/components/QuizContainer.js

import React from "react";
import Button from "./Button";
const QuizContainer = ({quizes, selectedAnswers,setSelectedAnswers,currentQuiz,setCurrentQuiz}) => {
  const quiz = quizes[currentQuiz]
return (
    <div>
      <h3>{quiz.question}</h3>
      <div>
        {quiz.choices.map((c, i) => {
          return (
            <p
              key={c}
              onClick={() => {
                let answers = [...selectedAnswers];
                answers[currentQuiz] = i;
                setSelectedAnswers((prev) => [...answers]);
              }}
              className={`${selectedAnswers[currentQuiz] === i ? "selected" : ""}`}>
              {c}
            </p>
          );
        })}
      </div>
      <div>
       <Button onClick={ currentQuiz > 0 ? () =>    setCurrentQuiz(currentQuiz - 1);} : null } label={"< Prev"} /><Button onClick={() => { setCurrentQuiz(currentQuiz + 1); }} label={currentQuiz < quizes.length - 1 ? "Next >" : "Submit"} />
</div>
</div>
  );
};
export default QuizContainer;

./src/components/Results.js after the quiz gets submitted we will display the score based on filtering for answer matches with the selected answer and calculating its length.

import React from "react";
const Results = (props) => {
  const { quizes, selectedAnswers } = props;
  const answers = [...quizes.filter((q, i) => q.answer === selectedAnswers[i])];
  return (
    <div className="score">{`${answers.length} / ${selectedAnswers.length}`}</div>
  );
};
export default Results;

Component Lifecycle

Each components goes through the following stages — Mounting, Updating and Unmounting. In case of class component we had predefined functions for handling it componentDidMount(), componentDidUpdate() and componentWillUnmount() since we are focused more on functional components useEffect hook will help in writing component lifecycle like what to do when component is loaded/mounted, what to do when component updated etcc.

So syntax for useEffect is

useEffect(()=>{
  //your function to be executed
  return () => {
    //cleanup code / unmount code
  }
}, [dependency1, dependency2, ....]);

So when we specify any dependency then function inside will be called whenever dependency gets updated, if dependency list is empty then it will called after every state update.We can mention state variables as a dependency.

Suppose if we want to call function only once when component is loaded then just remove the dependency array.

Note there is difference between this two syntax

// will be executed only onceuseEffect(()=>{
  //your function to be executed
  return () => {
    //cleanup code / unmount code
  }
});// will be executed on every update
useEffect(()=>{
  //your function to be executed
  return () => {
    //cleanup code / unmount code
  }
}, []);

Portals

React Portals helps in rendering elements outside the DOM heirarchy of the parent component. In our index.js we have below

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

which basically get the element by id root so that all elements under App component will be rendered inside root DOM. One of the common case is we can use it when we need to layer the components. Without portals we can place all elements in single parent element so we cannot layer those based on z-index. Also we need to update ./public/index.html by adding a new div with id modal-root below root div so now our modal will place elements inside that div.

import ReactDOM from "react-dom";
import Button from "./Button";
const modalRoot = document.getElementById("modal-root");
const Modal = ({ title, children, show, setShow }) => {
  const ModalComp = () => {
    return (
      <div className={`${show ? "active" : "hide"}`}>
        <div>
          <p>{title}</p>
          <div>{children}</div>
          <Button onClick={() => setShow(false)} label={"Dismiss"} />
        </div>
      </div>
    );
  };
  return ReactDOM.createPortal(<ModalComp />, modalRoot);
};
export default Modal;

Error Boundaries

Error Boundaries are used to catch javascript errors and show a fallback UI, so basically it is a component that uses a method getDerivedStateFromError to find whether an error has occurred or not. As of now, we can’t create ErrorBoundary as a functional component, we need to use the class component to create it.

import React, { Component } from "react";
export class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
    };
  }
static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  render() {
    return this.state.hasError ? (
      <div>Something went wrong</div>
    ) : (
      this.props.children
    );
  }
}
export default ErrorBoundary;

./src/App.js

import React, {useState} from "react";
import { quizes } from "./static/quizData";
import Welcome from "./components/Welcome";
import QuizContainer from "./components/QuizContainer";
import Results from "./components/Results";
function App(){
const [currentQuiz, setCurrentQuiz] = useState(-1)
const [selectedAnswers, setSelectedAnswers] = useState([])
return (<ErrorBoundary><div>
    {currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
    </div></ErrorBoundary>
  );
};
export default App;

So if we have any javascript runtime errors you will be able to see the Something went wrong message. You can try adding the below fetch statement to hit any invalid urls to test it.

import React, {useState} from "react";
import { quizes } from "./static/quizData";
import Welcome from "./components/Welcome";
import QuizContainer from "./components/QuizContainer";
import Results from "./components/Results";
function App(){
const [currentQuiz, setCurrentQuiz] = useState(-1)
const [selectedAnswers, setSelectedAnswers] = useState([])
return (<ErrorBoundary><div>
{fetch("http://xyz.xyz")}
{currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
    </div></ErrorBoundary>
  );
};
export default App;

Code-Splitting

So every web app needs to be bundled properly before going into production, although the current web application which we are building does not have many libraries installed or any big coding base we will understand this strategy so that you can implement it in your upcoming projects. Code Splitting basically helps in loading only necessary libraries or components that the user requested at that time by dynamically importing the libraries/components, which will ultimately reduce the initial load time. For implementing this React provides lazy loading such that the component only loads when it is requested. We will create a loader component that will just have the text ‘Loading’ such that this Loader component will be shown until the component is loaded.

import React, {useState, Suspense } from "react";
import { quizes } from "./static/quizData";
const ErrorBoundary = React.lazy(() => import("./components/ErrorBoundary"));
const Welcome = React.lazy(() => import("./components/Welcome"));
const QuizContainer = React.lazy(() => import("./components/QuizContainer"));
const Results = React.lazy(() => import("./components/Results"));
export const AppContext = React.createContext();
function App(){
const [currentQuiz, setCurrentQuiz] = useState(-1)
const [answers, setAnswers] = useState([])
return (<AppContext.Provider value={{ quizes, selectedAnswers, setSelectedAnswers, currentQuiz, setCurrentQuiz}}>
<ErrorBoundary>
 <Suspense fallback={<Loader/>}>
   <div>
     {currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
    </div>
 </Suspense>
</ErrorBoundary>  );
};
export default App;

Use of Context API

Now we have one parent component where we are passing the state variable and its set function to child components through props. Considering we have a hierarchy of child components then it would be not ideal to pass these props through each and every child component. This problem will lead to props drilling, to solve this problem, we need a central state management system that is provided by context API. We can create and export a context using React.createContext. We need a context provider which is used to pass data to components wrapped under the provider without using props.

import React, {useState, Suspense} from "react";
import { quizes } from "./static/quizData";
const ErrorBoundary = React.lazy(() => import("./components/ErrorBoundary"));
const Welcome = React.lazy(() => import("./components/Welcome"));
const QuizContainer = React.lazy(() => import("./components/QuizContainer"));
const Results = React.lazy(() => import("./components/Results"));
export const AppContext = React.createContext();
function App(){
const [currentQuiz, setCurrentQuiz] = useState(-1)
const [answers, setAnswers] = useState([])
return (<AppContext.Provider value={{ quizes, selectedAnswers, setSelectedAnswers, currentQuiz, setCurrentQuiz}}><ErrorBoundary>
 <Suspense fallback={<Loader/>}>
  <div>
     {currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
    </div>
 </Suspense>
</ErrorBoundary>
</AppContext.Provider>  );
};
export default App;

Here in ./src/components/QuizContainer.js we will use useContext hook to access all the values passed in the provider it takes AppContext as a argument which we have defined in App.js.

import React from "react";
import { AppContext } from "../App";
import Button from "./Button";
const QuizContainer = () => {
const {quizes,selectedAnswers,setSelectedAnswers,currentQuiz,setCurrentQuiz} = useContext(AppContext);
const quiz = quizes[currentQuiz]
return (
    <div>
      <h3>{quiz.question}</h3>
      <div>
        {quiz.choices.map((c, i) => {
          return (
            <p key={c}
              onClick={() => {
                let answers = [...selectedAnswers];
                answers[currentQuiz] = i;
                setSelectedAnswers((prev) => [...answers]);
              }}
              className={`${selectedAnswers[currentQuiz] === i ? "selected" : ""}`}>
              {c}
            </p>
          );
        })}
      </div>
      <div>
       <Button onClick={ currentQuiz > 0 ? () =>    setCurrentQuiz(currentQuiz - 1);} : null } label={"< Prev"} /><Button onClick={() => { setCurrentQuiz(currentQuiz + 1); }} label={currentQuiz < quizes.length - 1 ? "Next >" : "Submit"} />
</div>
</div>
  );
};
export default QuizContainer;

For ./src/components/Results.js we will useContext to get the data.

import React from "react";
import { useContext } from "react";
import { AppContext } from "../App";
const Results = () => {
  const { quizes, selectedAnswers } = useContext(AppContext);
  console.log(quizes, selectedAnswers);
  const answers = [...quizes.filter((q, i) => q.answer === selectedAnswers[i])];
  return (
    <div>{`${answers.length} / ${selectedAnswers.length}`}</div>
  );
};
export default Results;

HTTP Request

For this, we will create a simple Node js API that will return a list of quizzes whenever an HTTP get request is called and add a new quiz when a POST request is called. We need an HTTP server we require an http package also we will initialize some variables (host, port, and quizzes). Now using this package set up an http server with the port and host. Now set up a request listener for this http server.

So for node js project, in your command prompt type

npm init -y

This will initialize an empty project, now create an index.js, and paste this code.

var http = require("http");
const host = "localhost";
const port = 8000;
var quizes = [
  {
    question: "React is a ___ ?",
    choices: ["Library", "Framework", "Language", "Technology"],
    answer: 0,
  },
  {
    question: "Angular JS is a ___ ?",
    choices: ["Library", "Framework", "Language", "Technology"],
    answer: 1,
  },
];
const requestListener = function (req, res) {
  res.setHeader("Access-Control-Allow-Origin", "*");
  if (req.method === "POST") {
    let newData;
    req.on("data", (d) => {
      console.log(d.toString());
      newData = JSON.parse(eval(d));
      console.log(newData, typeof newData);
      quizes = [...quizes, newData];
      res.setHeader("Content-Type", "application/json");
      res.writeHead(200);
      res.end(JSON.stringify(quizes));
    });
  } else {
    res.setHeader("Content-Type", "application/json");
    res.writeHead(200);
    res.end(JSON.stringify(quizes));
  }
};
//create a server object:
const server = http.createServer(requestListener);
server.listen(port, host, () => {
  console.log(`Server is running on http://${host}:${port}`);
});

now run using the below command

node index.js

Now in our App.js, we will fetch the quizzes from this API using fetch API in an async function after fetching we will store this data in state variable quizzes.

import React, {useState} from "react";
import { quizes } from "./static/quizData";
const ErrorBoundary = React.lazy(() => import("./components/ErrorBoundary"));
const Welcome = React.lazy(() => import("./components/Welcome"));
const QuizContainer = React.lazy(() => import("./components/QuizContainer"));
const Results = React.lazy(() => import("./components/Results"));
export const AppContext = React.createContext();
function App(){
const [currentQuiz, setCurrentQuiz] = useState(-1)
const [answers, setAnswers] = useState([])
const getData = async () => {     
 const res = await fetch("http://localhost:8000");     
 const data = await res.json();     
 console.log(data);     
 setQuizes([...data]);   
};   
useEffect(() => {     
 getData();   
}, []);
return (<AppContext.Provider value={{ quizes, selectedAnswers, setSelectedAnswers, currentQuiz, setCurrentQuiz}}><ErrorBoundary>
 <Suspense fallback={<Loader/>}>
  <div>
     {currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
    </div>
 </Suspense>
</ErrorBoundary>
</AppContext.Provider>  );
};
export default App;

Form Handling

We will try to implement the post method by creating a form to submit a new quiz. So we have 6 input elements wrapped under the form element for each input we will have a state value updated on input change so instead of creating a 6 state variable let's wrap all under a single state object data. Create a handleChange function that will be called whenever onChange is triggered on input. This onChange function gives an event object which we can use to access data on input using e.target.value, feel free to console.log(e) to know more about it. Finally, we will set this value to the appropriate key in the object by using the input name. Now for onSubmit we will use the fetch api and post method to call the API and add a new quiz.

./src/components/AddQuiz.js

import React, { useState } from "react";
const AddQuiz = () => {
  const [data, setData] = useState({
    question: "",
    choice1: "",
    choice2: "",
    choice3: "",
    choice4: "",
    answer: 0,
  });
  const handleSubmit = async (e) => {
    e.preventDefault();
    console.log(e);
    const { question, choice1, choice2, choice3, choice4, answer } = data;
    await fetch("http://localhost:8000/", {
      method: "POST",
      body: JSON.stringify({
        question,
        choices: [choice1, choice2, choice3, choice4],
        answer,
      }),
    });
  };
const handleChange = (e) => {
    setData((prev) => ({
      ...prev,
      [e.target.name]: e.target.value,
    }));
  };
  return (
    <div className="container">
      <h2>Add Quiz</h2>
      <form onSubmit={handleSubmit}>
        <input
          name="question"
          onChange={handleChange}
          required={true}
          placeholder="Question"
          type={"text"}
        />
        <div>
          <input
            onChange={handleChange}
            name="choice1"
            required={true}
            placeholder="Choice 1"
            type={"text"}
          />
          <input
            onChange={handleChange}
            name="choice2"
            required={true}
            placeholder="Choice 2"
            type={"text"}
          />
          <input
            name="choice3"
            onChange={handleChange}
            placeholder="Choice 3"
            type={"text"}
          />
          <input
            onChange={handleChange}
            name="choice4"
            placeholder="Choice 4"
            type={"text"}
          />
        </div>
        <input
          name="answer"
          required={true}
          onChange={handleChange}
          placeholder="Answer"
          type={"number"}
          min={0}
          max={3}
        />
        <button type={"submit"}>Submit</button>
      </form>
    </div>
  );
};
export default AddQuiz;

Refs

Refs are commonly used to access DOM nodes. Here we have a form to add a new quiz so whenever we load this component, we need to focus on the first input so we have to use the focus function. In this case, we will use the useRef hook to create refs and assign them to our first input. Try console.log (firstInputRef ) to get the contents of this ref, if you expand this you will get the current object inside it we will be able to get many properties/functions associated with it. Now on the component mount, we will call inpRef.current.focus() to focus on the input when this component is loaded.

./src/components/AddQuiz.js

import React, { useEffect, useRef, useState } from "react";

const AddQuiz = () => {
  const [data, setData] = useState({
    question: "",
    choice1: "",
    choice2: "",
    choice3: "",
    choice4: "",
    answer: 0,
  });
  const firstInpRef = useRef();
  const handleSubmit = async (e) => {
    e.preventDefault();
    const { question, choice1, choice2, choice3, choice4, answer } = data;
    await fetch("http://localhost:8000/", {
      method: "POST",
      body: JSON.stringify({
        question,
        choices: [choice1, choice2, choice3, choice4],
        answer,
      }),
    });
  };
  useEffect(() => {
    firstInpRef.current.focus();
  }, []);
  const handleChange = (e) => {
    setData((prev) => ({
      ...prev,
      [e.target.name]: e.target.value,
    }));
  };
  return (
    <div className="container">
      <h2>Add Quiz</h2>
      <form onSubmit={handleSubmit}>
        <input
          ref={firstInpRef}
          name="question"
          onChange={handleChange}
          required={true}
          placeholder="Question"
          type={"text"}
        />
        <div>
          <input
            onChange={handleChange}
            name="choice1"
            required={true}
            placeholder="Choice 1"
            type={"text"}
          />
          <input
            onChange={handleChange}
            name="choice2"
            required={true}
            placeholder="Choice 2"
            type={"text"}
          />
          <input
            name="choice3"
            onChange={handleChange}
            placeholder="Choice 3"
            type={"text"}
          />
          <input
            onChange={handleChange}
            name="choice4"
            placeholder="Choice 4"
            type={"text"}
          />
        </div>
        <input
          name="answer"
          required={true}
          onChange={handleChange}
          placeholder="Answer"
          type={"number"}
          min={0}
          max={3}
        />
        <button type={"submit"}>Submit</button>
      </form>
    </div>
  );
};
export default AddQuiz;

Hooks

We have covered important hooks useState, useEffect, useRef, useContext hook. We will cover other hooks with example in next post.

React Router DOM

Now we need to show different components based on the URL such if we are on the /add URL then AddQuiz component should be shown. So React Router DOM helps in acheiving this, we have BrowserRouter a top-level element to manage routes, followed by Routes and Route where we mention the URL path and element which needs to be shown. So now we will move all components returned inside an App.js into a single component(Main.js) and move it to a separate folder pages under src and also move AddQuiz.js into that pages folder.

./src/App.js

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./App.css";
import AddQuiz from "./pages/AddQuiz";
import Main from "./pages/Main";
export const AppContext = React.createContext();

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route index path="/" element={<Main />} />
        <Route index path="/add" element={<AddQuiz />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

New Component ./src/pages/Main.js

import React, { Suspense, useEffect, useState } from "react";
import { AppContext } from "../App";

const ErrorBoundary = React.lazy(() => import("../components/ErrorBoundary"));
const Welcome = React.lazy(() => import("../components/Welcome"));
const QuizContainer = React.lazy(() => import("../components/QuizContainer"));
const Results = React.lazy(() => import("../components/Results"));
const Main = () => {
  const [selectedAnswers, setSelectedAnswers] = useState([]);
  const [currentQuiz, setCurrentQuiz] = useState(-1);
  const [quizes, setQuizes] = useState([]);
  const getData = async () => {
    const res = await fetch("http://localhost:8000");
    const data = await res.json();
    console.log(data);
    setQuizes([...data]);
  };
  useEffect(() => {
    getData();
  }, []);
  return (
    <AppContext.Provider
      value={{
        quizes,
        selectedAnswers,
        setSelectedAnswers,
        currentQuiz,
        setCurrentQuiz
      }}
    >
      <ErrorBoundary>
        <Suspense fallback={<div>loading...</div>}>
          {currentQuiz === -1 && <Welcome />}
          {currentQuiz > -1 && currentQuiz < quizes.length && <QuizContainer />}
          {currentQuiz >= quizes.length && <Results />}
        </Suspense>
      </ErrorBoundary>
    </AppContext.Provider>
  );
};
export default Main;

In ./src/components/Welcome.js we will add “a tag” to navigate to AddQuiz Page

import React, { useState } from "react";
import { useContext } from "react";
import { AppContext } from "../App";
import Button from "./Button";
import Modal from "./Modal";
const Welcome = () => {
  const { setCurrentQuiz } = useContext(AppContext);
  const [showRules, setshowRules] = useState(false);
  
return (
    <div className={`home center`}>
      <h2 className="text-title">Quizish</h2>
      <p className="text-body mt-10 mb-20">Welcome to React Quiz!!!</p>
      <div className="welcome-buttons">
        <Button onClick={() => setCurrentQuiz(0)} label="Let's Start >" />
        <a href="/add">
          <Button onClick={() => {}} label="Add Quiz >" />
        </a>
        <Button onClick={() => setshowRules(true)} label="Rules" />
      </div>
      <Modal title={"Rules"} show={showRules} setShow={setshowRules}>
        <ul>
          <li>Each questions will be given 10 seconds</li>
          <li>Results will be displayed after submitting</li>
        </ul>
      </Modal>
    </div>
  );
};
export default Welcome;

Now the final step is to apply styles to the HTML elements add classNames to this elements and mention your styles in index.css which I am leaving it you as a challenge. You can also take this one step forward by adding timer for each quiz.

I tried to implement most of the concepts in this web app but some of the concepts would have been missed which I will cover in the next post with some other examples. If you have anything I need to cover please feel to contact/comment.

Thanks for reading this post, hope you learned and built the application and try to design and implement your own features to it. if you found this post helpful please share maximum, Thanks for reading 😊 Stay tuned.

You can find this code on my Github repository.

Quiz App | Github

If you are facing any issues please contact us from our contact section.

Contact Us | CodeWithMarish

Also please don’t forget to subscribe to our youtube channel codewithmarish for all web development-related challenges.

Code With Marish | Youtube

Related Posts