Redux News Reader - can't get comment text to show up on page from returned object

Hi there,
Working my way through this project and managed to got to a good place with most of it. However, after sending a POST request, I’m supposed to add the comments to the page - I’ve got it working that it will actually add an entry for the comment, but I can’t get it to show the actual text of the comment. I’ve looked in Inspector and it seems like the returned JSON doesn’t have any text in it, but it’s being sent in the body of the request, so I’m a bit lost. Could anyone help?

Below is what I believe to be relevant code:

// Import createAsyncThunk and createSlice here.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

// Create loadCommentsForArticleId here.
export const loadCommentsForArticleId = createAsyncThunk(
  'comments/loadCommentsForArticleId',
  async (id) => {
    const response = await fetch(`api/articles/${id}/comments`);
    const json = await response.json();
    return json;
  }
);

// PARTICULARLY THIS BIT

export const postCommentForArticleId = createAsyncThunk('comments/postCommentForArticleId',
async ({articleId, comment}) => {
  const requestBody = JSON.stringify(comment);
  const response = await fetch(`api/articles/${articleId}/comments`, {
    method: 'POST',
    body: requestBody
  });
  const json = await response.json();
  return json;
});
// END OF THIS BIT

export const commentsSlice = createSlice({
  name: 'comments',
  initialState: {
    byArticleId: {},
    isLoadingComments: false,
    failedToLoadComments: false,
    createCommentIsPending: false,
    failedToCreateComment: false
  },
  extraReducers: {
    [loadCommentsForArticleId.pending]: (state, action) => {
      state.isLoadingComments = true;
      state.failedToLoadComments = false
    },
    [loadCommentsForArticleId.fulfilled]: (state, action) => {
      state.isLoadingComments = false;
      state.failedToLoadComments = false;
      state.byArticleId = {[action.payload.articleId]: action.payload.comments};
    },
    [loadCommentsForArticleId.rejected]: (state, action) => {
      state.isLoadingComments = false;
      state.failedToLoadComment = true
    },
    [postCommentForArticleId.pending]: (state, action) => {
    state.createCommentIsPending = true;
    state.failedToCreateComment = false
    },
//OR IT ALSO MIGHT BE THIS BIT
    [postCommentForArticleId.fulfilled]: (state, action) => {
    state.createCommentIsPending = false;
    state.failedToCreateComment = false;
    state.byArticleId[action.payload.articleId].push(action.payload);
    },
// END OF THIS BIT
    [postCommentForArticleId.rejected]: (state, action) => {
    state.createCommentIsPending = false;
    state.failedToCreateComment = true
    },
  }
});

export const selectComments = (state) => state.comments.byArticleId;
export const isLoadingComments = (state) => state.comments.isLoadingComments;
export const createCommentIsPending = (state) => state.comments.createCommentIsPending;

export default commentsSlice.reducer;

And this is the ‘CommentForm’ file, which I think is probably more likely to be OK but just in case:

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
  createCommentIsPending,
  postCommentForArticleId
} from '../features/comments/commentsSlice';

export default function CommentForm({ articleId }) {
  const dispatch = useDispatch();
  const [comment, setComment] = useState('');
  
  // Declare isCreatePending here.

  const handleSubmit = (e) => {
    e.preventDefault();
    dispatch(postCommentForArticleId({articleId, comment}));
    setComment('');
  };

  return (
    <form onSubmit={handleSubmit}>
      <label for='comment' className='label'>
        Add Comment:
      </label>
      <div id='input-container'>
        <input
          id='comment'
          value={comment}
          onChange={(e) => setComment(e.currentTarget.value)}
          type='text'
        />
        <button
          
          className='comment-button'
        >
          Submit
        </button>
      </div>
    </form>
  );
}

Thanks for your time!

1 Like

Hey @jonrosk089 i`m stuck in this exercise too, but in a previous step! How did you write the “useEffect()” in the 'Comment.js" file (step 9)? Can you help me?

Hey there - I should probably delete this one cause I did eventually get things working, but anyway. Could you post your useEffect and we’ll see what the problem is? One big thing though is to read the task really carefully - I missed a really important part of it when I wrote useEffect (that is, it should only be called if the post isn’t undefined)

Thanks for the tip!
I realized that I forgot to export “loadCommentsForArticleId” in commenstSlice.js.
Now I’m trying to get the POST part working, I’m having the exact same problem that you described, the returned JSON doesn’t have the comment text!

Finally, after a couple of hours I managed to make it work!! I made some mistakes using the action.payload in the extraReducers!

1 Like

Hey! I am also stuck in and around the same area you both were. I think I’m also incorrectly using the action.payload in the postCommentForArticleId.fulfilled reducer.

When I try to post a new comment, the existing comment disappears. I know with using createSlice we can update the state “mutably” and tried using .push to add the new comment, but I feel like I’m struggling to access the comments array like I need to.

Any tips would be greatly appreciated!

Hi digital, I’m a bit confused about what you mean - do you mean that you can post a comment, but then when you post a new comment, it deletes the old one while adding a new one? I think that probably would mean that it’s changing rather than adding to the array. Feel free to post the code to check.

A really really enormous massive help has been to realise that you can right click on the embedded browser to see the Redux Devtools just for that browser (rather than the Codecademy page), so you can kind of troubleshoot yourself. ALSO, the normal browser Devtools (Chrome Devtools or suchlike) will show you what the Post / Get requests you’ve been making actually look like.

I did a stupid thing and didn’t read the task properly.

THE SOLUTION TO THIS is that the requestBody constant in postCommentForArticleId should be an object that is {comment: comment} rather than just the single argument comment. It’s pretty plainly in the text, but I think this project is such a brain-melter that I needed a bit of time away before I could come back and solve this one.

2 Likes

Yep! If I’m not mistaken, it’s because of ES6 destructuring, the args {articleId: articleId, comment: comment} can be written as {articleId, comment}.

To try to further explain, how I had my [postCommentForArticleId.fulfilled] set up, when I tried to submit a new comment, any comment already on the page didn’t render/disappeared.

I did end up getting it to work! Using the: “state.byArticleId[action.payload.articleId].push(action.payload)” line.

I think a huge part I struggled with was not fully understanding the brackets “” in state.byArticleId lines in the extraReducers. Is it using object map notation?

Yeah, I think it’s the same thing as a calculated name? I need to have a go at using the builder as they do in the other slices because you have to use that memos if you use Typescript, which I want to learn after finishing the Full Stack path…

This video explains my question super well for those who came by later!

I’d be really keen to see the code for useEffect as well @jonrosk089 @gianpaolopapaiz - I’m not really getting my head around what is trying to be achieved here.

I’m also not sure about task 8 ! is this correct?
const comments = useSelector(selectComments);
const commentsAreLoading = useSelector(isLoadingComments);

Hi Martyg, the useSelectors look absolutely fine. What in particular are you not sure about with useEffect? Would you like to post what you’ve got already?

Thanks @jonrosk089 - appreciate you taking the time to respond.

This is my code for task 9 - in Comments.js:

useEffect(() => {
if (article !=== undefined) {
dispatch(loadCommentsForArticleId)};
}, [article.id]);

const commentsForArticleId = ;
if (article !=== undefined) {commentsForArticleId = comments[article.id]};

if (commentsAreLoading) return

Loading Comments
;
if (!article) return null;

return (


Comments





);

And this is what I have for Task 10 in CommentList.js:
return (


    {comments.map(comments => {
    return
    })
    }

);

but I’m still not seeing any comments come up when I select an article

cheers!

Hey Martyg,
Working backwards, think about what you’re actually mapping and returning in CommentList. Say we had an array of comments —

['Oh my God', 'Amazing!', 'Wonderful news']

— and wanted to use map() for that array, we would actually be iterating over each individual comment in the array.

At the moment, for each comment, you’re returning—i.e., ending the process—without actually doing anything. You need to do what it asks in the task:

Map over the comments prop and render a Comment (we’ve imported this component for you) for each value.

Actual code for CommentList (you don't have to look if you want to work it out yourself):
import React from 'react';
import Comment from './Comment';

export default function CommentList({ comments }) {
  if (!comments) {
    return null;
  }
  
  return (
    <ul className='comments-list'>
      {comments.map(comment => {
        return <Comment comment={comment} />
      })}
    </ul>
  );
}

OK, so then the useEffect: it’s actually more or less fine as you’ve done it. There’s one typo (not equals should be !== rather than what you’ve got there), but otherwise the problem is the dependency array. I’ll copy what the hint to the task says here:

you’ll want to include dispatch and article in the dependency array to ensure you fetch comments any time article changes.

Note that you should include article there rather than article.id.

Then…

Define a constant, commentsForArticleId , which should be an empty array when article is undefined and otherwise should be equal to comments[article.id] .

At the moment, what you’ve got for commentsForArticleId won’t work, as you’ve assigned a value to a constant variable and then want to change it with the if statement (notice the same typo with not equals). Have another think about how to achieve what the task is asking.

Again, a possible solution here, but don't look if you want to try it yourself

const commentsForArticleId = (article === undefined) ? [] : comments[article.id]

Hope that helps, let me know if it’s still not working!

@jonrosk089 Thanks again for taking the time!

Both my useEffects section and CommentList.js match what you have suggested now, (Codecademy’s Hint suggested the arg should be article.id - confusing!), but alas, still no comments appear.

I must have failed somewhere earllier…

reading earlier posts from @gianpaolopapaiz , maybe my extraReducer for fulfilled is wrong - I have:

.addCase(loadCommentsForArticleId.fulfilled, (state, action) => {
state.isLoadingComments = false;
state.byArticleId = action.payload;
state.failedToLoadComments = false;

what do you reckon?

Hopefully, I’ll be able to get my head around this all one day and be able to help someone out myself = pass on the karma :+1:

Hey Martyg,
No worries at all. This one’s a bit of a brain melter, and having had to do it all without help, I’m happy to spare others that struggle…!

The argument you pass to dispatch in useEffect should be article.id, but the dependency array should have article instead; is that reasonably clear?

I can’t comment on addCase because you’re using builder, which I have no experience with (I need to learn it eventually because I want to try learning typescript, where you have to use builder there), but unfortunately the reducer is slightly wrong. I think how they word it might be unclear because I spent ages banging my head against the wall with that bit.

state.byArticleId[article.id] = action.payload

If you look at the task and compare it to that, it’ll be immediately clear why that’s what you should do, but going from the text to that solution in the first place is really tricky somehow.

Hey @martyg_london did you manage to make it work? This exercise was really hard for me, but everything started to make sense when I understood exactly what data was inside the returned json of the asyncThunk ‘loadCommentsForArticleId’. At the end of this createAsyncThunk, before the 'return json, just console.log(json):

// Create loadCommentsForArticleId here.
export const loadCommentsForArticleId = createAsyncThunk(
‘comments/loadCommentsForArticleId’,
async (id) => {
const response = await fetch(api/articles/${id}/comments);
const json = await response.json();
console.log(json); <====== HERE
return json;
}
)

Use the second article (“LG says it might quit the smartphone market”) as example, because it has one comment to load (“Sad, I love my LG phone”) and you are going to be able to see the estructure of the response json in the DOM.

This helped me to write the .fulfilled extraReducer of ‘loadCommentsForArticleId’. After that I started to console.log() a lot of steps!!

Hey @gianpaolopapaiz I’d love to say its all working fine… but I still can’t see any comments … I’ve lost a bit of motivation now to be honest - it sucks as the lessons were all good, I did ok on the quizzes etc, but as soon as I get to these projects it all turns to custard! I could really do with a walkthrough video on this one (again)…

I’ve made the changes that @jonrosk089 (thanks again), has given me, but my problems must run deeper… that or it just doesn’t work on Firefox!

I’ve added the console.log as you’ve suggested, and this doesn’t show me any comments data - I get a MSW GET api/articles/2 return (maybe because I’m doing the project on the Codecademy’s site?

I might just hang my head in shame, move on, and come back to this later - other people do that right??

Hey Martyg,
Moving on and coming back to something that’s just stuck is definitely not a terrble ID, but maybe we can crack it together. Do you want to post your code for the two relevant files? Make sure you surround the code with three backticks (```) on each side so that it’s formatted correctly, makes it much easier to read.