Quiz App

ref – https://www.youtube.com/watch?v=BNN4o4gnSF4

How to Operate

If you need to make updates to the questions and answer, make sure you delete existing object in questions collections.

Then create a new object with Postman like so:

POST localhost:8080/api/questions

This way, it updates mongo db.

When there are already data in results and you want to clear it, in your Postman:

DELETE localhost:8080/api/result

In our results reducer, we keep state for wechat id, douyin id and the user’s results. All answers from this user will will be saved in this slice.

Users and checking for existing users

First we check to see if the user’s wechat id or telephone exists.

When we start the app, we first save the user’s id from input to slice:

src/components/Main.js

We do a fetch using checkIfUserExistInMongoDB. It will return the user’s information if EITHER the wechat id or mobile matches. If it does, we throw out an error message.

If BOTH wechat id and mobile does not exist, we can proceed to save the data locally to the store:

src/components/Main.js

In startQuiz function, we dispatch actions generated from result_reducer.

src/redux/question_reducer.js

we throw in the user entered string into the action.
src/components/Main.js

The action object gets into the reducer.

src/redux/result_reducer.js

Question Reducer

Question reducer keeps track of what question index we’re on, the questions themselves, and the answers.

state:
queue is to represent the array of questions.
answers is to represent the array of answers the user inputs
questionIndex is to represent the cur of the data structure.

src/redux/question_reducer.js

When the next button is clicked, we dispatch an action to move the question index forward.
When the prev button is clicked, we dispatch an action to move backwards.

This is very similar to how a custom array would work. We have the array data, but keep a cur to keep track of what index we’re on.

src/hooks/FetchQuestion.js

Okay, so this is all happening in the store.

But for the UI, before we even touch the store, we need to create local state in order to get the user selected radio index. So we define value for the radio button index that the user has checked:

src/components/Quiz.js

We then get the current question by getting the questionIndex and using it to get the element in queue.

Now we render the options associated with this question object.

As you know aQuestion is an object from the store.
It contains:

  • question
  • id – the id of the whole question
  • options – an array of answer values. Thus, we use map to iterate through all the answer available.

src/components/Questions.js

Fetching that Question object

So we see how we retrieve the question object from the store. But how did we fetch from the web?

We use a custom hook called useFetchQestion.

src/components/Questions.js

It runs whenever our Question component is refreshed.

We start off basics. We use local state to show:

  • isLoading – whether something is loading/done
  • apiData – data container from the response object
  • serverError – will have data when/if error occurs

We initialize local data to default. As long as the component does not update, we use the same dispatch reference.

Note:

Its safe to add dispatch to the dependency array because its identity is stable across renders.

The doc says like in React’s useReducer, the returned dispatch function identity is stable and won’t change on re-renders
unless you change the store being passed to the , which would be extremely unusual

In similar situation, the dispatch function reference will be stable as long as the same store instance is being passed to the . Normally, that store instance never changes in an application.

However, the React hooks lint rules do not know that dispatch should be stable, and will warn that the dispatch variable should be added to dependency arrays for useEffect and useCallback. The simplest solution is to do just that:

Using and declaring async IIFE function in the effect to fetch data

In useEffect, we must give the declaration and immediately invoke it in order for the async operation to be part of the effect. Thus that’s why we declare the function, and then execute it.

If there is an error, we set local data isLoading to false so any kind of spinner can stop.
We also pass the error object to serverError.

Successfully fetching the data

Where getServerData awaits on an axios’s get on url http://localhost:8080/api/questions

src/helper/helper.js

ref – http://chineseruleof8.com/code/index.php/2022/12/16/axios-and-fetch-both-returns-a-promise-object/

So we see two awaits. First, we return a promise when the headers arrive. This is to let us know that the get response has returned. But in order to the get json data response, we’ll have to call json() to get another Promise object, which resolves to our data. Hence this is why we need a 2nd await.

After data returns, we create an array holder for it.

If questions array is valid, we first set local isLoading to false to say we’re done fetching data.

Then, we apply questions to local state apiData. Once we have have apiData, getData will have the valid data to be used and we return it from the functional component. However, in our app we don’t use this apiData data. We would fetch from the store as will be explained later.

src/components/Questions.js

After we dispatch action startExamAction, it goes to our question reducer startExamAction and the data is loaded from the action object’s payload.
We then assign them to the slice’s state:

src/redux/question_reducer.js

Pushing Next button

When the next button is pushed, it will evaluate what question index we’re on from the state.questions via question index.

If it’s still within the index of the last question, we will update the state to the next question.

src/components/Quiz.js

dispatch(MoveNextQuestion()) will increase the questionIndex:

src/redux/question_reducer.js

Once questionIndex is updated, then when we retrieve a question object from the store, it will update:

src/components/Questions.js

We know that questionIndex gets updated from pressing Next button, how does it get rendered?

How does Question data get rendered?

Keeping track of the question index

questionIndex is increased/decreased via MoveNextQuestion.