Home > database >  Components not updating when state changes
Components not updating when state changes

Time:12-07

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?

CodePudding user response:

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.

CodePudding user response:

To get realtime updates, you can use onSnapshot (docs) instead of getDocs

  • Related