购物车组件未在下一个 JS 应用程序中将数据保存到 localstorage

Cart Component not saving data to localstorage in Next JS Application

提问人:DrewS 提问时间:11/9/2023 更新时间:11/9/2023 访问量:27

问:

我正在尝试将购物车功能添加到我的 nextJS 应用程序中,这是一个投注单。用户可以选择赔率,数据存储在购物车/投注单中。我遇到的问题是,一旦选择数据,数据就不会保存到LocalStorage。因此,当我刷新页面或访问另一个页面时,购物车/投注单每次都会被清除。下面是组件每个部分的代码。谁能看到数据未存储到 LocalStorage 的明显原因?

betslip.tsx

import React, { useContext } from 'react';
import BetslipContext from './betslip-context';
import { XMarkIcon } from '@heroicons/react/24/outline';
import classNames from 'classnames';

const Betslip = () => {
  const { betslip, removeOddsFromBetslip, updateStakeInBetslip } = useContext(BetslipContext);

  const updateStake = (e, index) => {
    // Parse the stake as a number before sending to updateStakeInBetslip
    const stakeValue = parseFloat(e.target.value);
    updateStakeInBetslip(index, stakeValue);
  };

  return (


    <div className="col-span-full xl:col-span-4 sm:col-span-8 mb-5 shadow-lg border border-slate-200 bg-white">

  <header className="px-5 py-4 border-b border-slate-100 bg-[#1a2c49] dark:border-slate-700">
    <h2 className="font-bold text-slate-50 dark:text-slate-100 uppercase">Bet Slip</h2>
  </header>


  <ul className="list-none">
  {betslip.map((odds, index) => (
    <li key={`${odds.event_id}-${odds.bookmaker}`} className="p-4 border-b border-gray-300 relative">
      <button
        className="absolute top-5 right-4 p-1 bg-red-500 text-white rounded text-xs"
        onClick={() => removeOddsFromBetslip(odds.event_id, odds.bookmaker, odds.selection, odds.type)}
      >
        <XMarkIcon className="w-3 h-3"/>
      </button>

      <div className="flex items-center justify-between">
      <div className="w-2/3 flex items-center space-x-2">
      <img src={odds.bookie_icon} alt={odds.bookmaker} style={{ width: '25px', height: '25px' }}/>
      <span className="font-bold text-base text-sm w-40">{odds.selection}</span>
      <span className="font-bold text-base align-top">{odds.line > 0 ? `+${odds.line}` : odds.line}</span>
      </div>

        <div className="w-1/3 text-center">
          <span className="font-bold text-base">${odds.odds.toFixed(2)}</span>
        </div>
      </div>

      <div className="ml-8 text-sm text-black flex flex-col">
          <div>
              <p className='text-xs'>{odds.type}</p>
              <p className='text-xs'>Including Overtime</p>
              </div>

              <div className="text-sm text-black flex flex-col">
              <div className="flex justify-between mt-2">
              <div className="w-1/2 text-left">

                <p className="font-semibold text-xs">{odds.event_name}</p>
                <p className="text-xs mt-3">{odds.sport} - {odds.league}</p>
                <p className="inline-block text-xs">Start: {odds.event_start}</p> <span className="mx-1">-</span> <p className="inline-block text-xs">{odds.event_date}</p>
              </div>

              <div className="w-1/2 text-right">
                <input
                    type="number"
                    placeholder="Stake"
                    className="w-20 py-1 px-1 mt-2 border rounded-lg text-right"
                    id={`stake-input-${index}`}
                    name={`stake-${index}`}
                    onChange={e => updateStake(e, index)}
                  />
                <p className="text-right mt-2  font-semibold">Return: ${(odds.odds * (betslip[index].stake || 0)).toFixed(2)}</p>
              </div>
            </div>
          </div>

      </div>

    </li>
  ))}
</ul>
 <div className='bg-orange-100 p-3 font-semibold'>Total Stake: ${betslip.reduce((total, odds) => total + (+odds.stake || 0), 0).toFixed(2)}</div>
<div className='bg-emerald-50 p-3 font-semibold'>Potential Return: ${betslip.reduce((total, odds) => total + ((+odds.odds) * (+odds.stake || 0)), 0).toFixed(2)}</div>



    </div>
  );
};

export default Betslip;


betslip-context.tsx

import React, { useState, createContext, useCallback } from 'react';

// Define the shape of the odds data and the betslip item
export interface Odds {
  event_date: any;
  event_start: any;
  league: any;
  sport: any;
  event_name: any;
  line: Boolean;
  bookie_icon: any;
  event_id: string;
  bookmaker: string;
  selection: string;
  type: string;
  odds: number;
  stake?: number;
  // ... include all other properties related to odds
}

// Define the context type for better TypeScript integration
interface BetslipContextType {
  betslip: Odds[];
  addOddsToBetslip: (odds: Odds) => void;
  removeOddsFromBetslip: (eventId: string, bookmaker: string, selection: string, type: string) => void;
  updateStakeInBetslip: (index: number, stake: number) => void;
}

// Create the context with the initial default values
const BetslipContext = createContext<BetslipContextType>({
  betslip: [],
  addOddsToBetslip: () => {},
  removeOddsFromBetslip: () => {},
  updateStakeInBetslip: () => {},
});

// The provider component that will wrap your application's component tree
export const BetslipProvider: React.FC = ({ children }) => {
  const [betslip, setBetslip] = useState<Odds[]>([]);

  const addOddsToBetslip = useCallback((odds: Odds) => {
    setBetslip((prevBetslip) => [...prevBetslip, { ...odds, stake: 0 }]);
  }, []);

  const removeOddsFromBetslip = useCallback((eventId: string, bookmaker: string, selection: string, type: string) => {
    setBetslip((prevBetslip) =>
        prevBetslip.filter(
            (odds) =>
                !(odds.event_id === eventId && odds.bookmaker === bookmaker && odds.selection === selection && odds.type === type)
        )
    );
}, []);

  const updateStakeInBetslip = useCallback((index: number, stake: string) => {
  const stakeNumber = parseFloat(stake) || 0; // Convert to number and handle non-numeric inputs
  setBetslip((prevBetslip) => {
    const newBetslip = [...prevBetslip];
    newBetslip[index].stake = stakeNumber;
    return newBetslip;
  });
}, []);


  // The context provider passes down the betslip array and functions to manipulate it
  return (
    <BetslipContext.Provider value={{ betslip, addOddsToBetslip, removeOddsFromBetslip, updateStakeInBetslip }}>
      {children}
    </BetslipContext.Provider>
  );
};

export default BetslipContext;


betslip-provider.tsx

import React, { useState, useCallback, useMemo, useEffect } from 'react';
import BetslipContext, { Odds } from './betslip-context';

interface BetslipContextType {
  betslip: Odds[];
  addOddsToBetslip: (odds: Odds) => void;
  removeOddsFromBetslip: (eventId: string, bookmaker: string, selection: string, type: string) => void;
  updateStakeInBetslip: (index: number, stake: number) => void;
  totalStake: number;
  totalReturn: number;
}

const BetslipProvider: React.FC = ({ children }) => {
  const [betslip, setBetslip] = useState<Odds[]>(() => {
    try {
      const localData = localStorage.getItem('betslip');
      return localData ? JSON.parse(localData) : [];
    } catch (error) {
      console.error("Failed to load betslip from localStorage", error);
      return [];
    }
  });

  useEffect(() => {
    try {
      localStorage.setItem('betslip', JSON.stringify(betslip));
    } catch (error) {
      console.error("Failed to save betslip to localStorage", error);
    }
  }, [betslip]);

  const addOddsToBetslip = useCallback((odds: Odds) => {
    setBetslip(prevBetslip => [...prevBetslip, { ...odds, stake: 0 }]);
  }, []);

  const removeOddsFromBetslip = useCallback((eventId: string, bookmaker: string, selection: string, type: string) => {
    setBetslip(prevBetslip =>
      prevBetslip.filter(odds =>
        !(odds.event_id === eventId && odds.bookmaker === bookmaker && odds.selection === selection && odds.type === type))
    );
  }, []);

  const updateStakeInBetslip = useCallback((index: number, stake: number) => {
    setBetslip(prevBetslip => {
      const newBetslip = [...prevBetslip];
      newBetslip[index].stake = stake;
      return newBetslip;
    });
  }, []);

  const totalStake = useMemo(() => betslip.reduce((total, odds) => total + (odds.stake || 0), 0), [betslip]);
  const totalReturn = useMemo(() => betslip.reduce((total, odds) => total + (odds.stake || 0) * odds.odds, 0), [betslip]);

  const contextValue: BetslipContextType = {
    betslip,
    addOddsToBetslip,
    removeOddsFromBetslip,
    updateStakeInBetslip,
    totalStake,
    totalReturn,
  };

  return (
    <BetslipContext.Provider value={contextValue}>
      {children}
    </BetslipContext.Provider>
  );
};

export default BetslipProvider;


布局.tsx

"use client"
import React from 'react';
import Header from '@/components/ui/header';
import { BetslipProvider } from 'components/ui/betslip/betslip-context.tsx'; // Adjust the import path as necessary

export default function DefaultLayout({ children }: { children: React.ReactNode }) {
  return (
    // Wrap the existing layout with the BetslipProvider
    <BetslipProvider>
      <div className="flex h-[100vh] overflow-hidden">
        {/* Content area */}
        <div className="flex flex-col flex-1 overflow-y-auto ">
          {/* Site header */}
          <Header />

          <main className="grow [&>*:first-child]:scroll-mt-16">
            {children}
          </main>
        </div>
      </div>
    </BetslipProvider>
  );
}


reactjs typescript next.js 本地存储

评论


答: 暂无答案