My code updates the state with the new topic, new quiz and displays the newly created quiz on page. But when clicking on to the quiz it redirects to ‘…/quizzes/undefined’ and produces this error:
" TypeError: Cannot read properties of undefined (reading ‘name’) "
12 | 13 | return (
14 | <section>>
> 15 | <h1>{quiz.name}</h1>
16 | <ul className="cards-list">
17 | {quiz.cardIds.map((id) => (
18 | <Card key={id} id={id} />
The error seems to come from the Quiz.js page as when I log the ‘quiz’ variable it returns ‘undefined’. But I’m unsure why it’s undefined as the data is there in the state and even appears when I log the ‘quizzes’ variable 2 lines above. Any ideas?
Quiz.js:
import { Link, useParams } from "react-router-dom";
import Card from "../cards/Card";
import ROUTES from "../../app/routes";
import { useSelector } from "react-redux";
import { selectQuiz } from "./quizzesSlice";
export default function Topic() {
const quizzes = useSelector(selectQuiz); // replace this with a call to your selector to get all the quizzes in state
console.log(quizzes) //Returns state of quizzes
let { quizId } = useParams();
const quiz = quizzes[quizId];
console.log(quiz) //Returns undefined
return (
<section>
<h1>{quiz.name}</h1>
<ul className="cards-list">
{quiz.cardIds.map((id) => (
<Card key={id} id={id} />
))}
</ul>
<Link to={ROUTES.newQuizRoute()} className="button center">
Create a New Quiz
</Link>
</section>
);
}
quizzesSlice.js:
import { createSlice } from '@reduxjs/toolkit';
import { addQuizId } from '../topics/topicsSlice';
const quizzesSlice = createSlice({
name: 'quizzes',
initialState: {
quizzes: {}
},
reducers: {
addQuiz: (state, action) => {
const { quizId } = action.payload;
state.quizzes[quizId] = action.payload
}
}
})
export const addQuizThunk = (payload) => {
return (dispatch) => {
dispatch(addQuiz(payload));
dispatch(addQuizId({ quizId: payload.quizId, topicId: payload.topicId }))
}
};
export const selectQuiz = state => state.quizzes.quizzes;
export const { addQuiz } = quizzesSlice.actions;
export default quizzesSlice.reducer
Quizzes.js:
import { Link } from "react-router-dom";
import ROUTES from "../../app/routes";
import { useSelector } from "react-redux";
import { selectQuiz } from "./quizzesSlice";
export default function Quizzes() {
const quizzes = useSelector(selectQuiz); // replace this with a call to your selector to get all the quizzes in state
return (
<section className="center">
<h1>Quizzes</h1>
<ul className="quizzes-list">
{Object.values(quizzes).map((quiz) => (
<Link key={quiz.id} to={ROUTES.quizRoute(quiz.id)}>
<li className="quiz">{quiz.name}</li>
</Link>
))}
</ul>
<Link to={ROUTES.newQuizRoute()} className="button">
Create New Quiz
</Link>
</section>
);
}
NewQuizForm.js:
import React, { useState } from "react";
import { useHistory } from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import ROUTES from "../app/routes";
import { useSelector, useDispatch } from "react-redux";
import { selectTopic } from "../features/topics/topicsSlice";
import { addQuizThunk } from "../features/quizzes/quizzesSlice";
import { addCard } from "../features/cards/cardsSlice";
export default function NewQuizForm() {
const [name, setName] = useState("");
const [cards, setCards] = useState([]);
const [topicId, setTopicId] = useState("");
const history = useHistory();
const topics = useSelector(selectTopic);
const dispatch = useDispatch();
const handleSubmit = (e) => {
e.preventDefault();
if (name.length === 0) {
return;
}
const cardIds = [];
// create the new cards here and add each card's id to cardIds
cards.forEach((card) => {
let cardId = uuidv4();
cardIds.push(cardId);
dispatch(addCard({...card, id: cardId}))
})
// create the new quiz here
dispatch(addQuizThunk({
quizId: uuidv4(),
name: name,
topicId: topicId,
cardIds: cardIds
}));
history.push(ROUTES.quizzesRoute());
};
const addCardInputs = (e) => {
e.preventDefault();
setCards(cards.concat({ front: "", back: "" }));
};
const removeCard = (e, index) => {
e.preventDefault();
setCards(cards.filter((card, i) => index !== i));
};
const updateCardState = (index, side, value) => {
const newCards = cards.slice();
newCards[index][side] = value;
setCards(newCards);
};
return (
<section>
<h1>Create a new quiz</h1>
<form onSubmit={handleSubmit}>
<input
id="quiz-name"
value={name}
onChange={(e) => setName(e.currentTarget.value)}
placeholder="Quiz Title"
/>
<select
id="quiz-topic"
onChange={(e) => setTopicId(e.currentTarget.value)}
placeholder="Topic"
>
<option value="">Topic</option>
{Object.values(topics).map((topic) => (
<option key={topic.id} value={topic.id}>
{topic.name}
</option>
))}
</select>
{cards.map((card, index) => (
<div key={index} className="card-front-back">
<input
id={`card-front-${index}`}
value={cards[index].front}
onChange={(e) =>
updateCardState(index, "front", e.currentTarget.value)
}
placeholder="Front"
/>
<input
id={`card-back-${index}`}
value={cards[index].back}
onChange={(e) =>
updateCardState(index, "back", e.currentTarget.value)
}
placeholder="Back"
/>
<button
onClick={(e) => removeCard(e, index)}
className="remove-card-button"
>
Remove Card
</button>
</div>
))}
<div className="actions-container">
<button onClick={addCardInputs}>Add a Card</button>
<button>Create Quiz</button>
</div>
</form>
</section>
);
}