Users have many reviews, and many trails through reviews, and vice versa.
Goal:
On a
userprofile page, render a list of names oftrailsthatuserhas reviewed, without duplicating any of thetrailnames. For example, if auserhas reviewed atrailmore than once, it should only display thetrailname once. If auserhas reviewed atrailmultiple times, and deletes one of thosereviews, the list should still display thetrailname. If all of theuser'sreviewsfor thattrailhave been deleted, thetrailname should be removed from the rendered list.When a
reviewis deleted, update frontenduserstate so thatuser.reviewsanduser.trailsare accurate, without making aGETrequest to do so.
What I've tried:
Create a set of unique trail names from user.trails and then render them in JSX by mapping over them
const uniqTrailNames = [...new Set(user.trails.map((trail) => {
return trail.name
}))]
{(uniqTrailNames.map((name) => {
return <p key={name}>{name}</p>
}))}
This is successful in rendering a unique list of trail names from the trails a user has reviewed. However, I'm struggling to setUser state correctly when a review is deleted.
Currently, my handleDelete() function successfully destroys the review in the database, and then calls setUser in order to update user.reviews and user.trails, but this is where I'm updating the trails property incorrectly. Filtering this way removes all user.trails objects with id's that match the review's associated trail_id, so even if the user had another review of the trail, the trail name doesn't render because the trails no longer exist in user.trails. How do I go about removing only a single trail object when deleting a review, so that user.trails remains accurate, if trail objects are essentially identical? Am I going about this the wrong way? It's part of my project requirement to showcase the full has_many through relationship, so that's why I've taken this approach so far.
import { useContext } from 'react'
import { UserContext } from '../contexts/UserContext'
import { TrailsContext } from '../contexts/TrailsContext'
import { NavLink } from 'react-router-dom'
function UserTrailReview({ review }) {
const {user, setUser} = useContext(UserContext)
const {trails, setTrails} = useContext(TrailsContext)
const {
id,
trail_id,
trail_rating,
condition,
content,
formatted_date,
trailname
} = review
const trail = trails.find((trail) => {
return trail.id === trail_id
})
function handleDelete() {
fetch(`/reviews/${id}`, {
method: "DELETE"
})
.then(setUser({
...user,
reviews: user.reviews.filter((review) => {
return review.id !== id
}),
trails: user.trails.filter((trail) => {
return trail.id !== trail_id
})
}))
.then(() => {
trail.reviews = trail.reviews.filter((review) => {
return review.id !== id
})
const updatedTrails = trails.map((t) => {
if (t.id === trail_id) {
return trail
} else {
return t
}
})
setTrails(updatedTrails)
})
}
A better way to avoid the duplication issue in the first place is to use the Active Record Query Method "distinct". In your models, wherever you have a has_many, through relationship, use distinct like so:
has_many :trails, -> { distinct }, through: :reviews