skip to Main Content

I’m making a venue review app with react/redux toolkit/firebase.

The user can submit, edit, and delete reviews for a particular venue. The problem I have is that CRUD operations for reviews are not instantly shown in the component – I have to refresh the page to see the changes.

I suspect this it’s because how I’ve structured my firestore database, and/or with how I’m handling state in the front end.

My firebase db is structured as follows:
enter image description here

There’s one collection "venues", with documents that have the following shape:

name:'',
photo:'',
reviews: [
  {
    title:'',
    blurb:'',
    reviewId:''
  },
  {...}
]

In my front end, I’m fetching all the "venues" documents in venueSlice.js :

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: [],
    isLoading: true
}

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

...

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

As as example of CRUD operation, I’m deleting reviews in Reviews.js

import { useDispatch } from "react-redux";
import { deleteReview,fetchVenues } from "../features/venues/venueSlice";
import { Link } from "react-router-dom";

const Reviews = ({ venue }) => {

    const dispatch = useDispatch()

    const venueId = venue[0]?.id

    const removeReview = (review) => {
        dispatch(deleteReview({...review, id:venueId}))
    }

    const content = venue[0]?.reviews.map(review => (
        <div className="review" key = {review.reviewId}>
            <h2>{review.title}</h2>
            <h3>{review.blurb}</h3>
            <div>
                <Link to = {`/venue/${venue[0].id}/${review.reviewId}/edit`}><button>Edit</button></Link>
                <button onClick = {() => removeReview(review)}>Delete</button>
            </div>
        </div>
    ))

    return (
        <div className="all-reviews">
            {content}
        </div>
    )
}
 
export default Reviews;

….which is handled by a thunk in venueSlice.js

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

Lastly, venues are fetched in App.js

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { useEffect } from "react";
import { useSelector,useDispatch } from "react-redux";
import { fetchVenues,fetchReviews } from "./features/venues/venueSlice";
import Venue from "./features/venues/Venue";
import VenueList from "./features/venues/VenueList";
import AddReview from "./components/AddReview";
import EditReview from "./components/EditReview";
import './styles.css'


const App = () => {

  const dispatch = useDispatch()

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

  return (
   <Router>
    <Routes>
      <Route path = '/' element = {<VenueList/>}/>
      <Route path = '/add-review' element = {<AddReview/>}/>
      <Route path = '/venue/:id' element = {<Venue/>}/>
      <Route path = '/venue/:id/:reviewId/edit' element = {<EditReview/>}/>
    </Routes>
   </Router>
  );
}

export default App;

I was thinking the problem is this: the venues collection is fetched and set to global state, and my application will respond to changes in venue state – but not changes of state within the reviews nested array. A possible solution would be to make a new "reviews" collection, or make a reviews subcollection in every venue document.
Suggestions?

2

Answers


  1. Venues are not fetched in App.js after you delete a review. useEffect‘s dependency array is empty, which means useEffect‘s effect (first argument’s body) only gets executed when the component mounts.

    One possible solution is fetching venues right after successfully deleting a review.

    Login or Signup to reply.
  2. To get realtime updates, you can use onSnapshot (docs) instead of getDocs

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search