简体   繁体   中英

How to save selected data from an API request

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"

To recap what I think you try to do (just for clarity):

  • [ ✅ 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 display

To achieve this I'd suggest the following strategy:

  1. when the user clicks the "Add to wishlist" button, he should trigger a handleAddToWishlist function. To ensure you can later reference the game they clicked, pass in the game ID as argument to that function.
  2. create the 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 server
  3. create a wishlists_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.
  4. link the backend function in the routes.rb file.

1 create user onclick events for games

JS frontend code

<div className="col">
    {currentUser ? <button onClick=${() => handleAddToWishlist(game.gameID)}>Add to wishlist</button> : null}                   
</div>

2 create 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);
}

3 wishlists_controller.rb

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

4 Update routes.rb

# ADD THIS TO YOUR ROUTES.rb FILE

# ...

# update a users wishlist
post '/wishlists/add_to_wishlist', to: "wishlists#add_to_wishlist"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM