Reputation: 87
I'm trying to make a POST request to my Rails backend with information that I get from an API. I'm currently using React.js and Ruby on Rails.
So, here's a simple look of what I made. I got this list of games from an API and I would like to somehow save games by clicking "Add to Wishlist" button. So far, I attempted to save the data using the useState and send the info with a POST request. However, it only saves null data. I thought my Rails backend was the problem but, my backend is working fine based on my test using rails console and postman.
I want to grab "title", "gameID", "retailPrice", "cheapestPrice", and "thumb"(thumbnail) from the API and save it to my rails backend with a click of button.
Here's my current code:
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { debounce } from './utils';
import StoreFinder from './StoreFinder';
function Browse({currentUser}) {
const [gameDealsList, setGameDealsList] = useState([]);
const [gameTitle, setTitle] = useState('');
const [maxPrice, setMaxPrice] = useState('');
// const [savedtitle, setSaveTitle] = useState('');
// const [savedGameID, setSaveGameID] = useState('');
// const [savedRetailed, setSaveRetailed] = useState('');
// const [savedCheapest, setSaveCheapest] = useState('');
// const [savedThumb, setSaveThumb] = useState('');
// const [addedStatus, setStatus] = useState(false);
const fetchDeals = useCallback((queryObject) => {
const url = new URL(`https://www.cheapshark.com/api/1.0/deals?`);
for(const [key, value] of Object.entries(queryObject)){
if(value) url.searchParams.append(key, value);
}
console.log(url);
return fetch(url)
.then((r)=>r.json())
.then((gameList)=> setGameDealsList(gameList));
}, []);
// It is to prevent API from crashing due to excessive amount of requests.
const fetchDealsDebounced = useMemo(() => {
// So API call will not be triggered until 400ms passed since last
// action that may trigger api call
return debounce(fetchDeals, 400);
}, [fetchDeals])
useEffect(()=>{
fetchDeals({ title: gameTitle, upperPrice: maxPrice})
},[fetchDealsDebounced, gameTitle, maxPrice]);
function handleRedirect(e, dealID){
e.preventDefault();
window.open(`https://www.cheapshark.com/redirect?pageSize=10&dealID=${dealID}`, '_blank');
return null;
}
// function saveData(){
// const dataForWishlist = {
// savedtitle,
// savedGameID,
// savedRetailed,
// savedCheapest,
// savedThumb
// }
// console.log(dataForWishlist)
// fetch(`/games`, {
// method: "POST",
// headers: { 'Content-Type': 'application/json'},
// body: JSON.stringify(dataForWishlist)
// })
// .then((r)=>{
// if(r.ok){
// r.json().then((x)=>console.log(x))
// setStatus(true)
// }
// })
// .catch((error)=>console.log(error))
// }
return(
<div className="container-fluid">
<h1>Browse</h1>
<h4>Filter:</h4>
<input placeholder='Enter a Title' value={gameTitle} onChange={(e)=>setTitle(e.target.value)}></input>
<span>Max Price $:</span>
<input type="range" className="price-filter" min="0" max="70" value={maxPrice} onChange={(e)=>setMaxPrice(e.target.value)}/>
<span>${maxPrice}</span>
<br/><br/>
{gameDealsList ? gameDealsList.map((game) =>
<div className="container" key={game.dealID}>
<div className="row">
<div className="col">
<img src={game.thumb} className="img-thumbnail" alt='thumbnail'/>
</div>
<div className="col">
<strong><p>{game.title}</p></strong>
</div>
<div className="col">
<span><s>${game.normalPrice}</s></span><br/>
<span>${game.salePrice}</span><br/>
<span>{Math.round(game.savings)}% Off</span>
</div>
<div className="col">
<StoreFinder storeID={game.storeID}/>
<button onClick={(e)=>handleRedirect(e, game.dealID)}>Visit Store</button>
</div>
<div className="col">
{currentUser ? <button>Add to wishlist</button> : null}
</div>
</div><br/>
</div>
) : <h1>No Result Found</h1>}
</div>
)
}
export default Browse;
FYI, the currentUser data is coming from my authentication in App.js and the debounce method is to prevent API from overflowing with fetch requests. Please help.
games_controller.rb
class GamesController < ApplicationController
before_action :authorized
skip_before_action :authorized, only: [:index, :show, :create]
def show
games = User.find(params[:id]).games
render json: games, include: :user
end
def index
games = Game.all
render json: games, include: :user
end
def create
user = User.find_by(id: session[:user_id])
game = user.games.create(games_params)
if game.valid?
render json: game, status: :created
else
render jsons: {error: game.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
game = Game.find_by(id: params[:id])
if game
game.destroy
render json: {}
else
render json: { error: "Game not found" }, status: :not_found
end
end
private
def authorize
return render json: { error: "Not authorized" }, status: :unauthorized unless session.include? :user_id
end
def games_params
params.permit(:title, :gameID, :retailPrice, :cheapestPrice, :thumb)
end
end
routes.rb
resources :wishlists
resources :sessions
get '/login', to: "sessions#create"
get '/users', to: "users#index"
# Register new user
post '/users', to: "users#create"
# Login User
post "/login", to: "sessions#create"
# Logout user
delete '/logout', to: "sessions#destroy"
# Update User profile
patch '/users/:id', to: "users#update"
# Keep user logged in
get '/me', to: "users#show"
# Get request for find user based on User id
get '/users/:id', to: "users#find_user"
# -------------GAMES Routes----------------------
# Show all the games that belongs to logged in user
get '/games/:id', to: "games#show"
# Create a new wishlist
post '/games', to: "games#create"
# Delete a game from wishlist
delete '/games/:id', to: "games#destroy"
Upvotes: 1
Views: 514
Reputation: 1639
✅ already working
] fetch all availible games via API and store the results inside the gameDealsList
state varible✅ already working
] once gameDealsList
state variable is being set, it will automatically display a list in the frontend❌ not working yet
] when the user clicks on "add to wishlist", you want to add a new game to his wishlist in the backend, and somehow store it also in the frontend to displayhandleAddToWishlist
function. To ensure you can later reference the game they clicked, pass in the game ID as argument to that function.handleAddToWishlist
function. You need 2 main actions:
(a) store all game Ids in a state variable savedGameIds
to keep track of the users current wishlist in the frontend.
(b) save the new game to the users wishlist in the backend by sending a POST request to the serverwishlists_controller.rb
and add a add_to_wishlist
action to it. In that action you would to the following:
(a) if the user has already a wishlist, fetch it and add the game to it (probably you would want to prevent adding games that are already in your wishlist)
(b) if the user has no wishlist, create a new one and save the game as first element.routes.rb
file.JS frontend code
<div className="col">
{currentUser ? <button onClick=${() => handleAddToWishlist(game.gameID)}>Add to wishlist</button> : null}
</div>
handleAddToWishlist
JS frontend code
async function Browse({currentUser}) {
// ...
// create state variable to track wishlist in the frontend
// since you have all games already fetched, its enough to
// only store the gameIds
const [savedGameIds, setSavedGameIds] = useState([]);
function handleAddToWishlist(gameId) {
// get game details from the `gameDealsList`
const gameToAdd = gameDealsList.find(game => game.gameId === gameId);
// (2a) add this game to the users saved games in the frontend (do not add if already added)
if (!savedGameIds.includes(gameId)) {
const newSavedGameIds = [...savedGameIds, gameId];
setSavedGameIds(newSavedGameIds);
}
// (2b) send add_to_wishlist POST request to ruby backend
await addToWishlistPostRequest(gameToAdd);
}
}
async function addToWishlistPostRequest(gameDetails) {
// whatever you need to filter, you can filter here
const sendData = {
game_title: gameDetails.title,
game_id: gameDetails.gameID,
game_thumb: gameDetails.thumb,
// gameRetailed: gameDetails.retailed, // not sure where did you get this info
// gameCheapest: gameDetails.cheapest, // not sure where did you get this info
user_id: currentUser.id, // the backend needs the userId to assign the wishlist to
authenticity_token: document.querySelector('[name="csrf-token"]').content, // needed for the backend to accept the request
};
// create POST request with callback like this:
const xhr = new XMLHttpRequest();
// process server response
xhr.onload = function () {
const backendJsonResponse = JSON.parse(this.response);
console.log("response from backend:");
console.log(backendJsonResponse);
};
// actually send the POST request to the backend
xhr.open("POST", "/wishlists/add_to_wishlist");
xhr.setRequestHeader("Content-type", "application/json");
xhr.send(JSON.stringify(sendData));
console.log("POST request send with data: ");
console.log(sendData);
}
class WishlistsController < ApplicationController
before_action :authorized
# catch POST request that was sent from your frontend
def add_to_wishlist
# get user_id
user_id = params[:user_id]
# get game data
game_title = params[:game_title],
game_id = params[:game_id],
game_thumb = params[:game_thumb],
# @TODO: your backend logic goes here, you need to
# 1. fetch the user object
# 2. check if this user has already a wishlist
# if yes => add the game to the wishlist, make sure you don't add the same game twice
# if no => create a wishlist and add the game as the first item in his wishlist
# 3 send a json response to the frontend:
render json: {
status: "successfully added to wishlist" # send a status back to your frontend
}
end
end
# ADD THIS TO YOUR ROUTES.rb FILE
# ...
# update a users wishlist
post '/wishlists/add_to_wishlist', to: "wishlists#add_to_wishlist"
Upvotes: 1