skip to Main Content

I’m building a simple venue review app using react/redux toolkit/firebase.

The feature VenueList.js renders a list of venues. When the user clicks on a venue, it routes them to Venue.js page which renders information about the specific venue clicked on.

Here’s the problem: Venue.js renders on the first page load, but crashes when I try to refresh the page.

After some investigating I found that in Venues.js, the useSelector hook returned the correct state on first load, and then an empty array upon refresh:

Intial page load:

enter image description here

On page refresh

enter image description here

Why is this happeing and how can I fix this so that the page renders in all circumstances?

Here’s Venue.js

import { useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import AddReview from "../../components/AddReview";
import Reviews from "../../components/Reviews";

const Venue = () => {
  const { id } = useParams();

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

  const venue = venues.venues.filter((item) => item.id === id);

  console.log(venues)

  const content = venue.map((item) => (
    <div className="venue-page-main" key = {item.name}>
      <h2>{item.name}</h2>
      <img src={item.photo} alt = "venue"/>
    </div>
  ));

  return (
    <>
        {content}
        <AddReview id = {id}/>
        {/* <Reviews venue = {venue}/> */}
    </>
  );
};
 
export default Venue;

The list of venues in VenueList.js

import { Link } from "react-router-dom";
import { useEffect } from "react";
import { fetchVenues } from "./venueSlice";
import { useSelector,useDispatch } from "react-redux";


const VenueList = () => {
  const dispatch = useDispatch();

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

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

  const content = venues.venues.map((venue) => (
    <Link to={`/venue/${venue.id}`} style = {{textDecoration: "none"}} key = {venue.name}>
      <div className="venue-item">
        <h2>{venue.name}</h2>
        <img src={venue.photo} />
      </div>
    </Link>
  ));

  return (
    <div className="venue-list">
      {content}
    </div>
  );
};

export default VenueList;

And here’s the slice venueSlice.js controlling all the API calls

import { createSlice,createAsyncThunk } from "@reduxjs/toolkit";
import { collection,query,getDocs,doc,updateDoc,arrayUnion, arrayRemove, FieldValue } from "firebase/firestore";
import { db } from "../../firebaseConfig";

const initialState = {
    venues: []
}

export const fetchVenues = createAsyncThunk("venues/fetchVenues", async () => {
    try {
      const venueArray = [];
      const q = query(collection(db, "venues"));
      const querySnapshot = await getDocs(q);
      querySnapshot.forEach((doc) =>
        venueArray.push({ id: doc.id, ...doc.data() })
      );
      return venueArray;
    } catch (err) {
      console.log("Error: ", err);
    }
  });

  export const postReview = createAsyncThunk("venues/postReview", async (review) => {
      try {
        const venueRef = doc(db,"venues",review.id)
        await updateDoc(venueRef, {
          reviews: arrayUnion({ 
            title:review.title,
            blurb:review.blurb, 
            reviewId:review.reviewId })
        })
      } catch (err) {
        console.log('Error :', err)
      }
  })

export const deleteReview = createAsyncThunk("venues/deleteReview", async (review) => {

  const newReview = {blurb:review.blurb, title: review.title, reviewId: review.reviewId}

  try {
    const venueRef = doc(db,"venues",review.id)
    await updateDoc(venueRef, {
      reviews: arrayRemove(newReview)
    })
  } catch (err) {
    console.log('Error: ', err)
  }
})




const venueSlice = createSlice({
  name: "venues",
  initialState,
  reducers: {},
  extraReducers(builder) {
    builder
      .addCase(fetchVenues.fulfilled, (state, action) => {
      state.venues = action.payload;
    })
  },
});

export default venueSlice.reducer

2

Answers


  1. I think this is what is going on:

    First time you load this page, you first visit the list of venues so the call to fetch them is made and the venues are stored to redux. Then when you visit a specific venue, the list exists so the selector always returns data.

    dispatch(fetchVenues());
    

    When you refetch the page you are in the /venue/${venue.id} route.
    The dispatch to fetch the list hasn’t been called and so you get the errors you mention.

    There are a couple of ways to fix your issue

    1. Fetch the venues if the data are not available. In Venue.js do something like:
    const Venue = () => {
      const { id } = useParams();
      
      const venues = useSelector((state) => state.venues) || [];
    
      const venue = venues.venues.filter((item) => item.id === id);
    
      useEffect(() => {
        if(venues?.length === 0) {
         dispatch(fetchVenues());
        } 
      }, [dispatch, venues, id]);
    
      console.log(venues)
    
      // You need to check if the venue exists, otherwise your code will throw errors
    
      if(!venue) {
       return <div>Some loader or error message<div/>
      }
    
      const content = venue.map((item) => (
        <div className="venue-page-main" key = {item.name}>
          <h2>{item.name}</h2>
          <img src={item.photo} alt = "venue"/>
        </div>
      ));
    
      return (
        <>
            {content}
            <AddReview id = {id}/>
            {/* <Reviews venue = {venue}/> */}
        </>
      );
    };
     
    export default Venue;
    
    1. Second option would be to use something like redux-persist so your data remains when the reload happens
    Login or Signup to reply.
  2. const venues = useSelector((state) => state.venues)
    
    render(){
     <React.Fragment>
    {
    (venues && venues.venues && venues.venues instanceof Array && venues.venues.length>0) && venues.venues.map((elem,index)=>{
    return(
    <div className="venue-page-main" key={index}>
          <h2>{elem.name}</h2>
          <img src={elem.photo} alt="venue" />
        </div>
    );
    })
    }
    </React.Fragment>
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search