React Redux Quiz Application Code
Posted on Mar 29, 2025 in Other university degrees and diplomas
App.js
import React from 'react';
import './App.css';
import { connect } from 'react-redux';
// import logo from './logo.svg'; // Logo import seems unused in the provided snippet
// import ReduxProvider from './redux/ReduxProvider'; // ReduxProvider is likely used higher up the component tree
import Game from "./Game";
import { questionAnswer, changeQuestion, submit, initQuestions } from './redux/actions';
function App(props) {
return (
<div>
<Game
onQuestionAnswer={(answer) => {
props.dispatch(questionAnswer(props.currentQuestion, answer));
}}
presentQuestion={props.currentQuestion}
questionsAvailable={props.questions}
totalScore={props.score}
gameStatus={props.finished}
onChangeQuestion={(index) => {
props.dispatch(changeQuestion(index));
}}
onSubmit={(questions) => {
props.dispatch(submit(questions));
}}
onInitQuestions={(questions) => {
props.dispatch(initQuestions(questions));
}}
// Assuming 'question' prop is needed by Game, derived from state
question={props.questions[props.currentQuestion]}
/>
</div>
);
}
function mapStateToProps(state) {
return {
...state
};
}
export default connect(mapStateToProps)(App);
App.css
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #09d3ac;
}
ReduxProvider.js
import { Provider } from 'react-redux';
import GlobalState from './reducers';
import { createStore } from 'redux';
import React from 'react';
import App from '../App';
// Assuming 'assets' was intended instead of 'asserts'
// import { questions } from "../assets/mock-data"; // Using fetched data instead of mock
export default class ReduxProvider extends React.Component {
constructor(props) {
super(props);
this.initialState = {
score: 0,
finished: false,
currentQuestion: 0,
questions: [] // Initial questions will be fetched
};
this.store = this.configureStore();
}
configureStore() {
return createStore(GlobalState, this.initialState);
}
render() {
return (
<Provider store={this.store}>
<App />
</Provider>
);
}
}
reducers.js
import { combineReducers } from 'redux';
import { QUESTION_ANSWER, CHANGE_QUESTION, INIT_QUESTIONS, SUBMIT } from './actions';
function score(state = 0, action = {}) {
switch (action.type) {
case SUBMIT:
let correctQuestions = 0;
// Ensure action.questions is defined and is an array
if (Array.isArray(action.questions)) {
for (let i = 0; i < action.questions.length; i++) {
if (action.questions[i].answer === action.questions[i].userAnswer) {
correctQuestions++;
}
}
}
return correctQuestions;
default:
return state;
}
}
function finished(state = false, action = {}) {
switch (action.type) {
case SUBMIT:
return true; // Corrected: return boolean true directly
case INIT_QUESTIONS: // Reset finished state when new questions are loaded
return false;
default:
return state;
}
}
function currentQuestion(state = 0, action = {}) {
switch (action.type) {
case CHANGE_QUESTION:
// Add boundary checks if necessary
return action.index;
case INIT_QUESTIONS: // Reset to first question on init
return 0;
case SUBMIT: // Optionally reset or stay on last question
return state;
default:
return state;
}
}
function questions(state = [], action = {}) {
switch (action.type) {
case QUESTION_ANSWER:
return state.map((question, i) => {
// Ensure payload exists and index is valid
if (action.payload && action.payload.index === i) {
return { ...question, userAnswer: action.payload.answer };
} else {
return question;
}
});
case INIT_QUESTIONS:
// Ensure action.questions is an array, add default userAnswer
return Array.isArray(action.questions) ? action.questions.map(q => ({...q, userAnswer: undefined })) : [];
default:
return state;
}
}
const GlobalState = combineReducers({
score,
finished,
currentQuestion,
questions
});
export default GlobalState;
actions.js
export const QUESTION_ANSWER = 'QUESTION_ANSWER';
export const CHANGE_QUESTION = 'CHANGE_QUESTION';
export const SUBMIT = 'SUBMIT';
export const INIT_QUESTIONS = 'INIT_QUESTIONS';
export function questionAnswer(index, answer) {
return { type: QUESTION_ANSWER, payload: { index, answer } };
}
export function changeQuestion(index) {
return { type: CHANGE_QUESTION, index };
}
export function submit(questions) {
return { type: SUBMIT, questions };
}
export function initQuestions(questions) {
return { type: INIT_QUESTIONS, questions };
}
Game.js
import React from 'react';
import './Game.css';
const API_URL = "https://quiz.dit.upm.es/api/quizzes/random10wa?token=291da9667a66a796f79a";
export default class Game extends React.Component {
componentDidMount() {
this.fetchQuestions();
}
fetchQuestions = () => {
fetch(API_URL)
.then(res => {
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
return res.json();
})
.then(
(result) => {
// console.log(result, "request");
// Initialize questions in Redux store
this.props.onInitQuestions(result);
},
(error) => {
console.error("Error fetching questions:", error);
// Handle error state, maybe dispatch an action
this.props.onInitQuestions([]); // Pass empty array on error
}
);
}
resetGame = () => {
// Re-fetch questions to reset the game
this.fetchQuestions();
// Note: Reducer for INIT_QUESTIONS should reset score, finished status, currentQuestion index etc.
}
renderTips() {
// Ensure question and tips exist
if (!this.props.question || !Array.isArray(this.props.question.tips) || this.props.question.tips.length === 0) {
return <p>No tips available for this question.</p>;
}
return (
<ul>
{this.props.question.tips.map((tip, index) => (
<li key={index}>{tip}</li>
))}
</ul>
);
}
getTipsSection() {
return (
<div className="tips-section">
<h4>Tips:</h4>
{this.renderTips()}
</div>
);
}
renderEndGame() {
return (
<div className="end-game-screen">
<h3>Quiz Game Finished!</h3>
<p>Your score is: {this.props.totalScore} out of {this.props.questionsAvailable.length}.</p>
<button onClick={this.resetGame}>Reset Game</button>
</div>
);
}
renderNoQuestions() {
return (
<div className="no-questions-screen">
<h3>Quiz Game Error</h3>
<p>There was a problem loading questions.</p>
<button onClick={this.resetGame}>Retry</button>
</div>
);
}
// Check if the previous button should be disabled
isPreviousButtonDisabled() {
return this.props.presentQuestion === 0;
}
// Check if the next button should be disabled
isNextButtonDisabled() {
return this.props.presentQuestion >= this.props.questionsAvailable.length - 1;
}
handleAnswerChange = (e) => {
this.props.onQuestionAnswer(e.target.value);
}
render() {
const { gameStatus, questionsAvailable, presentQuestion, question, onSubmit } = this.props;
if (gameStatus === true) {
return this.renderEndGame();
}
if (!Array.isArray(questionsAvailable) || questionsAvailable.length === 0) {
return this.renderNoQuestions();
}
// Ensure 'question' prop is valid before rendering
if (!question) {
// This might happen briefly while loading or if index is out of bounds
return <div>Loading question...</div>;
}
return (
<div className="game-container">
<div className="game-header">
<h3>Quiz Game</h3>
</div>
<div className="question-area">
{question.attachment && question.attachment.url &&
<img src={question.attachment.url} alt={`Question ${presentQuestion + 1} visual aid`} className="question-image"/>
}
<div className="question-text">
<h4>Question {presentQuestion + 1} of {questionsAvailable.length}</h4>
<p>{question.question}</p>
</div>
<div className="answer-input">
<label htmlFor="userAnswer">Your Answer:</label>
<input
type="text"
id="userAnswer"
value={question.userAnswer || ''} // Controlled component
onChange={this.handleAnswerChange}
/>
</div>
{this.getTipsSection()}
</div>
<div className="navigation-buttons">
<button
onClick={() => this.props.onChangeQuestion(presentQuestion - 1)}
disabled={this.isPreviousButtonDisabled()}
>
Previous Question
</button>
<button
onClick={() => this.props.onChangeQuestion(presentQuestion + 1)}
disabled={this.isNextButtonDisabled()}
>
Next Question
</button>
<button onClick={() => onSubmit(questionsAvailable)}>
Submit Quiz
</button>
<button onClick={this.resetGame}>
Reset Game
</button>
</div>
</div>
);
}
}