I am trying to render only those items that belong to another controller in React

55 views Asked by At

I am working on a travel planning web app project in Rails and Ract. In my Ruby on Rails backend I have a model Place that belongs to my model Trip. in React.js on the frontend I am trying to render just those places that belong to a certain trip in a Modal. At present, I am rendering all the places in my database (because on my backend the index method in calling Place.all) so when I open my Modal for a certain trip, it displays all of the places rather than just the places that belong to that trip.

I need a way on either the back or front end to render only those places that belong to the trip that I am opening in the Modal

Currently my code looks like this:

Rails backend: def index @places = Place.all render json: @places.as_json end

React frontend Home.jsx

//Places actions
  const [places, setPlaces] = useState([]);

  const handleIndexPlaces = () => {
    axios.get("http://localhost:3000/places.json").then((response) => {
      console.log(response.data);
      setPlaces(response.data);
    });
  };
  useEffect(handleIndexPlaces, []);

//returning

 <Modal show={isTripShowVisable} onClose={handleHideTrip}>
   <TripsShow places={places} trips={trips} />
 </Modal>

//tripsShow

export function TripsShow(props) {
  return (
    <>
      <h2>Places to visit on this trip:</h2>
      <div>
        {props.places.map((place) => {
          return (
            <div key={place.id}>
              <h3>{place.name}</h3>
            </div>
          );
        })}
      </div>
    </>
  );
}

I have tried changing the backend to Place.where(trip_id: current_trip.id) but I know that I don't currently have a current_trip so that doesn't work.

2

There are 2 answers

0
Aspiring Dev 23000 On

You can pass the trip ID as a parameter in the axios GET request to the backend, and then retrieve only the places that belong to the trip with the same ID in the controller.

Backend:

def index
trip_id = params[:trip_id]
@places = Place.where(trip_id: trip_id)
render json: @places.as_json
end

Frontend:

const handleIndexPlaces = (trip_id) => {
  axios.get(`http://localhost:3000/places.json? 
    trip_id=${trip_id}`).then((response) => {
    console.log(response.data);
    setPlaces(response.data);
  });
}; 

The TripsShow takes more props now, it needs the selected trip, and a function to resolve the places for it :

<Modal show={isTripShowVisable} onClose={handleHideTrip}>
  <TripsShow places={places} trips={trips} handleIndexPlaces={handleIndexPlaces} trip={selectedTrip} />
</Modal>



export function TripsShow(props) {
  
  useEffect(() => {
    props.handleIndexPlaces(props.trip.id);
  }, []);

return (
  <>
    <h2>Places to visit on this trip:</h2>
    <div>
      {props.places.map((place) => {
        return (
          <div key={place.id}>
            <h3>{place.name}</h3>
          </div>
        );
      })}
    </div>
  </>
)}
0
max On

I would first address the backend concerns.

You need to setup assocations between the models and create a nested route which returns the places for a specific trip.

Place.where(trip_id: current_trip.id) implies that the assocation is one-to-many:

class Place
  belongs_to :trip
end

class Trip
  has_many :places
end

Place.where(trip_id: current_trip.id) is smelly as it leaks the implementation details of your models. The controller should just know that Trip has a places method and not the nitty gritty details.

But I wonder if you don't actually want a many-to-many assocation as it seems strange that that different trips can't visit the same place.

class Place
  has_many :trip_places
  has_many :trips, through: :trip_places
end

class Trip
  has_many :trip_places
  has_many :places, through: :trip_places
end

# for lack of a better name
# rails g model TripPlace trip:belongs_to place:belongs_to
class TripPlace
  belongs_to :trip
  belongs_to :place
end

Then you want to setup the nested route:

resources :trips do
  resources :places, shallow: true
end

This will define the route /trips/:trip_id/places which RESTfully defines that there is a relationship between the two resources.

class PlacesController < ApplicationController
  # GET /trips/1/places
  # GET /trips/1/places.json
  def index
    @trip = Trip.find(params[:trip_id]) # this can be DRYed into a callback
    @places = @trip.places
    render json: @places # you don't need to explicitly call `.as_json`
  end
end

While you could acheive the same result with a query string parameter - places?trip_id=1 that's an implicit instead of explicit relation and not the Rails Way of handling nested resources. As an added bonus this will also reurn a 404 - Not Found if the trip id is not valid instead of just an empty collection.

You should then adjust your frontend code so that it sends a request to the correct url:

const handleIndexPlaces = (trip_id) => {
  // don't hardcode `http://localhost:3000` into your JS.
  axios.get(`http://localhost:3000/trips/${trip_id}/places`).then((response) => {
    console.log(response.data);
    setPlaces(response.data);
  });
};