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:
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
Venues are not fetched in
App.js
after you delete a review.useEffect
‘s dependency array is empty, which meansuseEffect
‘seffect
(first argument’s body) only gets executed when the component mounts.One possible solution is fetching venues right after successfully deleting a review.
To get realtime updates, you can use
onSnapshot
(docs) instead ofgetDocs