Flashcards challenge project (redux): Can't render new quizzes on page

Hello everyone,

I am currently working on the flashcards challenge project in the redux section. Here is the link to the project: Flashcards project

I am currently working on the part where I am trying to make a new quiz for a specific topic. However, when I submit the new quiz that I made, the new quiz does not render on the page. I have a feeling that it has something to do with my thunk action creator. I am not sure if I passed the correct values into the action creators, “addTopic” and “addQuizId”, when I dispatched them in the thunk action creator. Here is the code where I wrote the thunk action creator function in quizSlice.js:

import {createSlice} from '@reduxjs/toolkit';
import {addTopic, addQuizId} from '../topics/topicsSlice';
//import {useDispatch} from 'react-redux';
//const dispatch = useDispatch();
export const quizSlice = createSlice(
    {
        name:'quizzes',
        initialState:{
            quizzes:{

            }
        },
        reducers:{
            addQuiz:(state,action) => {
                const {id,name,topicId,cardIds} = action.payload;
                state.quizzes[id] = {
                    id: id,
                    name: name,
                    topicId: topicId,
                    cardIds: cardIds
                }
            }
        }
    }
)
export const thunkActionCreator = (payload) => {
    const {name,topicId,cardIds,id} = payload;
    return (dispatch) => {
      // dispatch multiple actions here
      dispatch(addTopic({id,name}));
      dispatch(addQuizId({topicId}));

    };
};


export const selectQuiz = (state) => {return state.quizzes.quizzes};
export const {addQuiz} = quizSlice.actions;
export default quizSlice.reducer;

And here is the code where I implement the thunk action creator in the “handleSubmit” event handler in 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 { thunkActionCreator } from "../features/quizzes/quizSlice";

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 id = uuidv4();

  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
    // create the new quiz here
    history.push(ROUTES.quizzesRoute());
    dispatch(thunkActionCreator({name,id,topicId,cardIds}));

    
    
  };

  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>
  );
}

Any ideas as to why I can’t render the new quizzes? Any help is appreciated! Thanks everyone and happy coding :slight_smile:

Hi @vernonroque
what does your topicSlice look like?
You dispatch objects as payload. I’m not sure if a shorthand works here. But seeing what the topic state and reducer that receive the payload would be relevant here.

Hi @staledata

I hope all is well. Thanks for your help! This is my topicSlice.js code:

import {createSlice} from '@reduxjs/toolkit';

export const topicsSlice = createSlice({
    name: 'topics',
    initialState: {
        topics: {

        }
    },
    reducers: {
        addTopic: (state,action) => {
            const { id, name, icon } = action.payload;
            state.topics[id] = {
                id: id,
                name: name,
                icon: icon,
                quizIds: []
            }
        },
        addQuizId: (state,action) => {
            const {quizId, topicId} = action.payload;
            state.topics[topicId].quizIds.push(quizId);
        }
    }
    
    }
)
export const selectTopic = (state) => state.topics.topics;
export const {addTopic, addQuizId} = topicsSlice.actions;
export default topicsSlice.reducer;

Yes, I also have some doubts if I wrote the “addQuizId” reducer correctly. Did I write it right? Thanks again! Take care :slight_smile:

Ok, so you have an addQuiz reducer here:

That is supposed to add the quiz to the other quizzes in the state. But your reducer replaces it with the last item dispatched each time. You don’t copy the previous quizzes from the state. Yet you should at least see one quiz.

But I don’t see that you dispatch the addQuiz action anywhere. In the thunk you just dispatch addQuizId, but that just adds the id of the quiz to the related topic and addTopic.

Step 12 (that’s where you currently are, right?) advises you to dispatch the actions from the previous two steps in the thunkActionCreator. That would be addQuiz from step 10 and addQuizId from step 11.

Hi @staledata ,

Thank you for pointing out that I didn’t call the addQuiz action in the thunk action creator. I made the mistake by calling addTopic instead of addQuiz. So I changed that now. Here is my thunk action creator:

export const thunkActionCreator = (payload) => {
    const {name,topicId,cardIds,id} = payload;
    return (dispatch) => {
      // dispatch multiple actions here
      dispatch(addQuiz({id,name,topicId,cardIds}));
      dispatch(addQuizId({topicId}));

    };

However, when I run the code, nothing is still rendered on the quizzes page:

But when I check the Topics page, it shows that my particular topic has quizzes associated with it:

Thanks again for your help! Cheers :slight_smile:

Out of curiosity I wanted to see the values of quizId in the topics object. This is the value that I see when I make a quiz:

The value is null. It seems I’m not passing the quizId correctly. This is my addQuizId reducer:

addQuizId: (state,action) => {
            const {quizId, topicId} = action.payload;
            state.topics[topicId].quizIds.push(quizId);
        }

Is it correct, the way I am storing the quizId in the topics object?

If you havent’t changed that yet, you dispatch addQuizId only with the topicId as payload.

Then quizId is the ID of the topic and topicId is undefined.

@staledata ok thanks! It seems I fixed the quizId problem. Here is my thunk action creator:

export const thunkActionCreator = (payload) => {
    const {name,topicId,cardIds,id} = payload;
    return (dispatch) => {
      // dispatch multiple actions here
      dispatch(addQuiz({id,name,topicId,cardIds}));
      dispatch(addQuizId({quizId:id,topicId:topicId}));

    };

And here are the results:

However, the quizzes still do not render on the quizzes page.

I was thinking I had to pass the quizSlice reducer into store.js as such:

import { configureStore } from "@reduxjs/toolkit";
import {topicsSlice} from "../features/topics/topicsSlice";
import {quizSlice} from "../features/quizzes/quizSlice";

export default configureStore({
  reducer: {
    topics: topicsSlice.reducer,
    quizzes: quizSlice.reducer
  },
});

But nothing still renders on the quizzes page

What does your Quizzes.js look like?

Oh that was the problem! Haha I didn’t pass my selector into Quizzes.js :sweat_smile:

Now it is rendering the quizzes:

Thank you again so much @staledata . I couldn’t have solved this problem without you! I wish you all the best :slight_smile:

1 Like

You’re welcome! Happy coding :slight_smile:

This solution worked, but I’m not sure technically HOW it works.

In the react-redux lessons before, we were taught about thunks, asynchronous thunks, creating ‘extraReducer’ properties on the ‘createSlice’ object. It seems none of that was necessary. It’s not even necessary to initialise useDispatch() as somehow it gets passed down as a parameter to the thunk function.

I’d love to understand why all of this is possible.

Does anyone still have an answer for this? I had the same issue as the original post:
Doing the “Create quiz” would add it in the topics page, route me to the Quizzes page, but not actually add the quiz. Looking through Redux Devtool, it would add the quizIds from topicId, but not create the quiz.

Then I restarted the only exercise from scratch and I have the opposite issue: I can add the quiz, but not the quizIds from the topicId and I cannot figure out why! Please help me, I am stuck on this for a whole day.

Here is my current code (working on-platform):

topicsSlice.js file

import { createSlice } from '@reduxjs/toolkit';

export const topicsSlice = createSlice({
  name: 'topics',
  initialState: {
    topics: {}
  },
  reducers: {
    addTopic: (state, action) => {
      const { id, name, icon } = action.payload;
      state.topics[id] = { id, name, icon, quizIds: [] }
    },
    addQuizId: (state, action) => {
      const { quizId, topicId } = action.payload;
      state.topics[topicId].quizIds.push(quizId);
    }
  }
});

// Selector
export const selectTopics = (state) => state.topics.topics;

// Action Creators
export const { addTopic, addQuizId } = topicsSlice.actions;

// Reducer
export default topicsSlice.reducer;

quizzesSlice.js

import { createSlice } from '@reduxjs/toolkit';
import { addQuizId } from '../topics/topicsSlice.js';

export const quizzesSlice = createSlice({
  name: 'quizzes',
  initialState: {
    quizzes: {}
  },
  reducers: {
    addQuiz: (state, action) => {
      const { id, name, topicId, cardIds } = action.payload;
      state.quizzes[id] = { id, name, topicId, cardIds };
    }
  }
});

export const addQuizAndQuizId = (quiz) => {
  const { name, topicId, cardIds, id } = quiz;
  return (dispatch) => {
    dispatch(addQuiz({ id, name, topicId, cardIds }));
    dispatch(addQuizId({ topicId, quizId: id }));
  }
}

// Selector
export const selectQuizzes = (state) => state.quizzes.quizzes;

// Action Creators
export const { addQuiz } = quizzesSlice.actions;

// Reducer
export default quizzesSlice.reducer;

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 { selectTopics } from '../features/topics/topicsSlice';
import { addQuizAndQuizId } from '../features/quizzes/quizzesSlice';

export default function NewQuizForm() {
  const [name, setName] = useState("");
  const [cards, setCards] = useState([]);
  const [topicId, setTopicId] = useState("");
  const history = useHistory();
  
  const dispatch = useDispatch();
  const topics = useSelector(selectTopics);

  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
    // create the new quiz here
    dispatch(
      addQuizAndQuizId({
        id: uuidv4(),
        topicId: topicId,
        name: name,
        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>
  );
}

I also get the following error when I try to create a new quiz:
image

If you need anymore information let me know! This is my first time posting code so I hope I did it correctly. I am very stuck and really want to know how to move forward, where I went wrong and why, etc
Thank you in advance for your time

Just in case someone has the same error as me and and instead of deleting the comment:

  • Check your imports on the Quiz.js and Quizzes.js; Since I am doing this on-platform, it did not recognize the error.
  • I personally don’t recommend doing this on-platform due to these type of mistakes that can occur not being caught here, but would be found through VS Code for example. I spent over a day, not understanding how this could possibly be wrong, checking it with other solutions where the code was basically the same.
    The console error “Uncaught ReferenceError: quizId is not defined” bringing me back to the quizzesSlice file. All because I imported the selectQuizzes wrong in Quiz.js and Quizzes.js.

Anyways, lesson learned. I hope I can help someone who might be having something similar! Remember to take a longer break if you are stuck for long; your brain will be tired and you won’t be able to see sometimes obvious mistakes. Even if not so obvious, if you are at it for long, it will not go well because you are tired/done with the current problem. So step away and come back later or the next day.

Happy coding, and you got this! :woman_technologist: