React/Redux Reddit Client Project, trouble rendering the subreddits with map function

Hello everyone,

I hope you all are doing well. I am currently working on the Reddit Client Project. Here is a link to the project: Reddit Project

I’m in the process of trying to render the subreddits by fetching data from the /r/popular subreddit. First I fetch the data from this subreddit. Then I will fetch the data from the api to retrieve the subreddit name from each particular post. In this manner I can make a list of different subreddits on the reddit client app.

So far I was able to retrieve the information from /r/popular subreddit:

And here is the code I have written:

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadSubreddits } from './subredditSlice';

export const SubredditContainer = () => {

const dispatch = useDispatch();
const subreddits= useSelector(state => state.subreddits);
console.log('subreddit info',subreddits);


useEffect(() => {
    dispatch(loadSubreddits());
},[]);

    /*
    const [icon,setIcon] = useState();
    const [subRedditName,setSubRedditName] = useState();

    /*useEffect(() => {
        const fetchSubredditIcon = async() => {
            const subredditIcon = await fetch('https://www.reddit.com/r/pokemon/about.json');
            const json = await subredditIcon.json();
            setIcon(json.data.header_img);
        }
        fetchSubredditIcon();

        const fetchRedditInfo = async() => {
            const redditInfo = await fetch('https://www.reddit.com/r/pokemon.json');
            const json = await redditInfo.json();
            console.log(json.data.children[0].data.subreddit);
            setSubRedditName(json.data.children[0].data.subreddit);
          }
          fetchRedditInfo();
    },[]);*/

    
    return(
        <>
        <section className="subreddit_container">
            <h1 className="subreddits_title">Subreddits</h1>
            <div className = 'subreddit_button'>
                   {
                    subreddits.map((element) =>{
                       return (
                            <h2 className ='subreddit_name'>{element.subreddit}</h2>
                         )
                     })
                    
                } 
                {/* <h2>{subreddits}</h2> */}
            
                {/* <img className='subreddit_icon' src = {icon} alt ="subreddit logo"/>
                <h2 className='subreddit_name'>{}</h2> */}
            </div>

        </section>
        </>
    );
} 

Now my next plan is to retrieve the subreddit name data from each array:

In order to do that, I am trying to use the map function to render the subreddit name for each post like this:

return(
        <>
        <section className="subreddit_container">
            <h1 className="subreddits_title">Subreddits</h1>
            <div className = 'subreddit_button'>
                   {
                    subreddits.map((element) =>{
                       return (
                            <h2 className ='subreddit_name'>{element.subreddit}</h2>
                         )
                     })
                    
                } 
                {/* <h2>{subreddits}</h2> */}
            
                {/* <img className='subreddit_icon' src = {icon} alt ="subreddit logo"/>
                <h2 className='subreddit_name'>{}</h2> */}
            </div>

        </section>
        </>
    );

However I get the error:

I also tried mapping over the array like this:

subreddits.map((element,index) =>{
                       return (
                            <h2 className ='subreddit_name'>{element[index].data.subreddit}</h2>
                         )
                     })

However, I still get the same error. I thought this would work because when I console log with this particular data path:

console.log('subreddit info',subreddits[2].data.subreddit);

I get this result in the console:

It seems that I’m not mapping over the array of objects correctly. Any ideas as to what I am doing wrong? This problem has me stumped. I appreciate any feedback. Thanks for your help everyone! Happy Coding :slight_smile:

Hi there, me again :slight_smile:
When I compare what you successfully log to the console with your failing trial of mapping the subreddits, this strikes me:

If subreddits is an array of objects, then element is an object with the property data. That means you can directly chain it to element like:

subreddits.map((element,index) => (
<h2 className ='subreddit_name'>{element.data.subreddit}</h2>))
1 Like

Hey @mirja_t,

It’s great hearing from ya again! So I tried what you recommended, but I still get an error:

I also got this weird error when I tried to console.log a particular array:

Maybe it has something to do with the asynchronous function. I was thinking that the map function is running before the async function is able to fetch the data. Here is my code where I actually fetch the data:

import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
import React, {useState,useEffect} from 'react';

const LOAD_SUBREDDITS = 'subreddits/loadSubreddits';

export const loadSubreddits = ()=> async (dispatch) => {

  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  dispatch(getSubreddits(json.data.children));

};

    const getSubreddits = (subreddits) => {
      return {
        type: LOAD_SUBREDDITS,
        payload: subreddits
      }
    }

    export const subredditsReducer = (state=[],action) => {
      switch(action.type){
        case LOAD_SUBREDDITS:
          return {
            ...action.payload
          }
        default: return state;

      }
    }

Is it correct the way I fetched the data from the API? Thanks again for your help! I wish you well :slight_smile:

That’s possible. Obviously ‘subreddits’ itself is undefined. I’m always struggling with asynchronous data.
What do you get if you comment out the mapping in order to avoid the error (just return a <div/> for debugging purposes) and have a useEffect hook with one dependency ‘subreddits’. that just logs ‘subreddits’. Do you get two entries, the first being ‘undefined’ and the second your data?

So either you could use the extraReducers from the Actions Generated by createAsyncThunk lesson or you add a state called ‘loaded’ initialized as ‘false’ in the subredditContainer component and the use Effect hook with the dependencies [subreddits, loaded] like this:

useEffect(()=>{
  if(loaded) return;
  subreddits && setLoaded(true)
),[subreddits, loaded]);

if(!loaded) return <h1>Loading...</h1>
return subreddits.map(...

Hey @mirja_t ,

I hope all is well. So I tried doing what you mentioned in the post.

First, I just tried commenting out the mapping and just returned <div/> while creating a useEffect hook with one dependency ‘subreddits’ and just logged ‘subreddits’. I only get one entry that logs ‘subreddit’. There is no ‘undefined’ entry.

I also tried the second thing you mentioned. Here is my code I had written:

useEffect(() => {
    dispatch(loadSubreddits());
},[dispatch]);

useEffect(()=>{
  if(loaded) return;
   subreddits && setLoaded(true)
  },[subreddits, loaded]);

  useEffect(()=>{
      console.log('subreddits');
  },[subreddits])

and here is the code for rendering onto the application:

 return(
        <>
        <section className="subreddit_container">
            <h1 className="subreddits_title">Subreddits</h1>
            <div className = 'subreddit_button'>
                    {/* {
                    subreddits.map((element) =>{
                       return (
                            <h2 className ='subreddit_name'>{element.data.subreddit}</h2>
                         )
                     })
                    
                }  */}
                {(!loaded) ? <h1>Loading...</h1> : subreddits.map((element)=> {
                    return (
                        <h2 className = 'subreddit_name'>{element.data.subreddit}</h2>
                    )
                })}
                {/* <img className='subreddit_icon' src = {icon} alt ="subreddit logo"/>
                <h2 className='subreddit_name'>{}</h2> */}
            </div>

        </section>
        </>
    );

The result I get is an error:

However this means that the variable loaded is being set to true. It seems that there are no asynchronous issues.

I also noticed something interesting when I console logged the subreddits:

I’m not sure if it is significant or not, but subreddit info console logs []. But I’m not sure if that means anything because i set the initial state of subreddits to [].

Well, I’ll keep investigating. Take care :slight_smile:

Hi, I notice another thing I overlooked in your first post: what you get back does not seem to be an array but an object. And you could not iterate over an object with the map method. First, you would have to transform the returned data into an array with Object.values for example.

Hi @mirja_t,

Thank you for pointing that out! I assumed it was an array because I had fetched the ‘children’ array from the api:

export const loadSubreddits = ()=> async (dispatch) => {

  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  dispatch(getSubreddits(json.data.children));

};

This is based on how the json info is organized:

I extract the information using ‘useSelector’ like this:

const subreddits= useSelector(state => state.subreddits);

but when I console log the information, it is returned as an object as you mentioned, and not the array I extracted. useSelector() does it return the info in an object?

Hi @mirja_t ,

It seems I have it kind of working haha. So this is what I’ve done. First, I fetched only the json data like this:

export const loadSubreddits = ()=> async (dispatch) => {

  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  dispatch(getSubreddits(json));

};

Then, with the useSelector I extract the ‘children’ array:

export const SubredditContainer = () => {

const dispatch = useDispatch();
const subreddits= useSelector(state => state.subreddits.data.children);
console.log('subreddit info',subreddits);

const [loaded,setLoaded] = useState(false);

I run the application and I get this error:

The next thing I do is change how I extract the data with ‘useSelector( )’ to avoid the error like this:

const subreddits= useSelector(state => state.subreddits);

When I do this, I get no error and the application loads:

Once the application has loaded, I go back to extracting the data the way I did before:

const subreddits= useSelector(state => state.subreddits.data.children);

And when I do that, the subreddits render:

So it works, but it is inconsistent and I don’t know why I get the initial error. Any ideas why I get the initial error when I extract the data with const subreddits= useSelector(state => state.subreddits.data.children); ?

Hi,
I never used useSelector as you did here. I Import the selector like this:

import { useDispatch, useSelector } from 'react-redux';
import { selectSubreddits } from './subredditSlice'; // the subreddits state
export const SubredditContainer = () => {
 const subreddits= useSelector(selectSubreddits);
}

Maybe you get the initial error because the objects in the returned array don’t have consistent data. I probably wouldn’t save all the data in state because you don’t need all of it. If I log the data you get, I get this:

const loadSubreddits = async() => {

  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  const data = json.data.children;
  const arr = data.map(element => element.data.subreddit);
  console.log(arr). // logs ["AskReddit", "pics", "PublicFreakout", "MadeMeSmile", "news", "therewasanattempt", "todayilearned", "BlackPeopleTwitter", "Cricket", "facepalm", "interestingasfuck", "Genshin_Impact_Leaks", "HumansBeingBros", "politics", "marvelstudios", "gifs", "TrueCrime", "movies", "Damnthatsinteresting", "mildlyinteresting", "TheLastAirbender", "teenagers", "byebyejob", "NatureIsFuckingLit", "TrueOffMyChest"]
};
loadSubreddits();

I would rather recommend to save only the data you need…

Hey @mirja_t,

I tried your recommendations and I’m still getting that undefined error. In subredditSlice.js, I extract the information from state like this:

export const subredditsList = (state) => state.subreddits.data.children;

And in subredditContainer.js, I use useSelector() in this way:

const subreddits= useSelector(subredditsList);

But I still get this error:

I have a feeling it has something to do with how I am creating the slice. Maybe something is not being stored properly in state. Here is my subredditSlice.js:

import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
import React, {useState,useEffect} from 'react';

const LOAD_SUBREDDITS = 'subreddits/loadSubreddits';

export const loadSubreddits = ()=> async (dispatch) => {

  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  dispatch(getSubreddits(json));

};

    const getSubreddits = (subreddits) => {
      return {
        type: LOAD_SUBREDDITS,
        payload: subreddits
      }
    }

    export const subredditsReducer = (state=[],action) => {
      switch(action.type){
        case LOAD_SUBREDDITS:
          return {
            ...action.payload
          }
        default: return state;

      }
    }

    export const subredditsSlice = createSlice({
        name: 'subreddits',
        initialState: {
          // Add initial state properties here.
          isLoadingSubreddits: false,
          failedToLoadSubreddits: false
        },
        // Add extraReducers here.
        extraReducers: {
          [loadSubreddits.pending]: (state,action) => {
            state.isLoadingSubreddits = true;
            state.failedToLoadSubreddits = false;
      
          },
          [loadSubreddits.fulfilled]: (state,action) => {
          //state.byArticleId[action.payload.articleId] = action.payload.comments;
          state.isLoadingSubreddits = false;
          state.failedToLoadSubreddits = false;
         
          },
          [loadSubreddits.rejected]: (state,action) => {
            state.isLoadingSubreddits = false;
          state.failedToLoadSubreddits = true;
          }
        }
      });

//export const selectSubreddits = (state) => state.comments.byArticleId;
//export const isLoadingSubreddits = (state) => state.comments.isLoadingComments;
export const subredditsList = (state) => state.subreddits.data.children;

export default subredditsSlice.reducer;

And here is how I store the info in store.js:

import { configureStore } from '@reduxjs/toolkit';
import { subredditsReducer } from '../features/subredditSection/subredditSlice';
import {createStore,combineReducers} from 'redux';
import thunk from "redux-thunk";
import { applyMiddleware } from "redux";

/*
export const store = configureStore({
  reducer: {

  
  },
});*/
export const store = createStore(combineReducers(
    {
      subreddits:subredditsReducer,
    }
  ),
    undefined,
    applyMiddleware(thunk)
  );

Currently I’m not really using CreateSlice() to manage the state. I am just using a reducer to update state. Could this be the problem?

Hi,
I think you cannot extract anything from the state in store that you haven’t initialized:

What if you try this:
Slice:

 // dispatch only the data you need
export const loadSubreddits = ()=> async (dispatch) => {
  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  dispatch(getSubreddits(json.data.children));
};
// store data in substate 
const sliceOptions = {
  name: "subreddits",
  initialState: {
    subreddits: [],
    isLoading: false,
    hasError: false
  },
  reducers: {},
  extraReducers: {
    [loadSubreddits.pending]: (state, action) => {
      state.isLoading = true;
      state.hasError = false;
    },
    [loadSubreddits.fulfilled]: (state, action) => {
      state.recipes = action.payload;
      state.isLoading = false;
      state.hasError = false;
    },
    [loadSubreddits.rejected]: (state, action) => {
      state.isLoading = false;
      state.hasError = true;
    }
  }
}

Export state from Slice:

// state.nameOfSlice.nameOfSubstate
export const subredditsList = (state) => state.subreddits.subreddits;

Component:

export const SubredditContainer = () => {
  const subreddits= useSelector(selectSubreddits);
  return subreddit.map(...
}

Hey @mirja_t,

How’s it going? I hope all is well. So I tried the recommendations you gave me. It still doesn’t seem to work. I get this error:

This is because I had put the condition to see if subreddits had values stored like this:

 { subreddits.length>0 && subreddits.map((element,index)=> {
                    return (
                        <>
                        <h2 key={index} className ='subreddit_name'>{element.data.subreddit}</h2>
                        <br></br>
                        </>
                    )
                })}

Here is what my thunk function looks like:

export const loadSubreddits = ()=> async (dispatch) => {

  const response = await fetch(`https://www.reddit.com/r/popular.json`);
  const json = await response.json();
  dispatch(getSubreddits(json.data.children));

};

And here is my subredditSlice:

export const subredditsSlice = {
        name: 'subreddits',
        initialState: {
          // Add initial state properties here.
          subreddits: [],
          isLoading: false,
          hasError: false
        },
        reducers: {},

        extraReducers: {
          [loadSubreddits.pending]: (state,action) => {
            state.isLoading = true;
            state.hasError = false;
          },
          [loadSubreddits.fulfilled]: (state,action) => {
            state.isLoading = false;
            state.hasError = false;
      
          },
          [loadSubreddits.rejected]: (state,action) => {
            state.isLoading = false;
            state.hasError = true;
          }
        }
        };

And here is how I export the state from Slice:

export const selectSubreddits = (state) => state.subreddits.subreddits;

I’ll keep trying haha. I am really confused as to why this won’t work. I really appreciate your help! Take care and happy coding :slight_smile:

Hi @nyapk,

Do you mean there is something wrong with the libraries/packages that were downloaded?

Hi @mirja_t,

How’s it going? So after several weeks of trying to figure this problem, I finally figured it out! Of course I couldn’t have done it without your help!

So here is my subredditSlice.js

import {createAsyncThunk, createSlice} from '@reduxjs/toolkit';
import React, {useState,useEffect} from 'react';


    export const subredditsSlice = createSlice({
        name: 'subreddits',
        initialState: {
          // Add initial state properties here.
          subreddits: [],
          isLoadingSubreddits: false,
          failedToLoadSubreddits: false
        },
        reducers:{
          startGetSubreddits(state){
            state.isLoadingSubreddits = true;
            state.failedToLoadSubreddits = false;
          },
          getSubredditsSuccess(state,action){
            state.isLoadingSubreddits = false;
            state.subreddits = action.payload;
          },
          getSubredditsFailed(state){
            state.isLoadingSubreddits = false;
            state.failedToLoadSubreddits = true;
          }
        }
      });

      export const {
        getSubredditsFailed,
        getSubredditsSuccess,
        startGetSubreddits
      } = subredditsSlice.actions;

      export default subredditsSlice.reducer;

      export const loadSubreddits = ()=> async (dispatch) => {

        const response = await fetch(`https://www.reddit.com/r/popular.json`);
        const json = await response.json();
        const subreddits = json.data.children.map((element) => element.data);
        dispatch(getSubredditsSuccess(subreddits));
      };

export const selectSubreddits = (state) => state.subreddits.subreddits;

Once i fetched the info from the reddit api, I had to map the data objects within the children array:

Then I mapped the subreddits like this:

import React, { useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadSubreddits,selectSubreddits } from './subredditSlice';

export const SubredditContainer = () => {

const dispatch = useDispatch();
const subreddits= useSelector(selectSubreddits);
console.log('subreddit info',subreddits);

const [loaded,setLoaded] = useState(false);


useEffect(() => {
    dispatch(loadSubreddits());
},[dispatch]);

useEffect(()=>{
  if(loaded) return;
   subreddits && setLoaded(true)
  },[subreddits, loaded]);


      return(
        <>
        <section className="subreddit_container">
            <h1 className="subreddits_title">Subreddits</h1>
            <div className = 'subreddit_button'>
       
                { subreddits.length>0 && subreddits.map((element,index)=> {
                    return (
                        <>
                        <h2 key={index} className ='subreddit_name'>{element.subreddit}</h2>
                        <br></br>
                        </>
        
                    )
                })}
                {/* <img className='subreddit_icon' src = {icon} alt ="subreddit logo"/>
                <h2 className='subreddit_name'>{}</h2> */}
            </div>

        </section>
        </>
    );
} 

And here are the subreddits shown:

Man I feel so relieved haha. Thanks again @mirja_t ! I wish I could celebrate this victory in person with you! I think I’m gonna take a break and savor this win. Take care man. Happy coding :slight_smile:

1 Like