You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用useEffect页面加载时调用Spotify Web API获取歌单报400错误

问题:React组件初始化时调用Spotify API获取歌单报400错误,提交操作后调用却正常

控制台错误信息:

GET https://api.spotify.com/v1/me/playlists 400 (Bad Request)
Uncaught (in promise) Error: Failed to fetch playlists custom error
at DashboardPage.tsx:44:17

上述错误重复出现两次。

相关代码:

import { IconPlaylistAdd } from '@tabler/icons-react'
import { useEffect, useState } from 'react'
import { useDisclosure, useInputState } from '@mantine/hooks';
import { Modal, Button, TextInput, Image } from '@mantine/core';

export default function Dashboard() {
  const [playlistName, setPlaylistName] = useInputState('');
  const [playlistDescription, setPlaylistDescription] = useInputState('');
  const [playlists, setPlaylists] = useState([]);
  const userId = 'hidden for privacy'
  const [token, setToken] = useState('');

  useEffect(() => {
    const token = localStorage.getItem('token')
    setToken(`${token}`)
    fetchPlaylists()
    console.log(token)
  }, [])

  const [opened, { open, close }] = useDisclosure(false);

  const renderItem = (playlist: any, index: number) => {
    const imageUrl = playlist.images.length > 0 ? playlist.images[0].url : 'https://placekitten.com/300/300';
    return (
      <div key={index}>
        <Image radius='sm' src={imageUrl} w={150} h={150} />
        <div>{`${playlist.name}`}</div>
      </div>
    );
  };

  const fetchPlaylists = () => {
    fetch('https://api.spotify.com/v1/me/playlists', {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    })
      .then((response) => {
        if (response.ok) {
          return response.json();
        } else {
          throw new Error('Failed to fetch playlists custom error');
        }
      })
      .then((data) => {
        setPlaylists(data.items)
        console.log(data.items)
      })
  }

  function handleSubmit() {
    fetch(`https://api.spotify.com/v1/users/${userId}/playlists`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        'name': `${playlistName}`,
        'description': `${playlistDescription}`,
        'public': false
      })
    });
    setTimeout(fetchPlaylists, 500)
    close()
  }

  return (
    <div className="Container">
      <Modal opened={opened} onClose={close} title="Create Playlist">
        <TextInput value={playlistName} placeholder='Playlist name...' onChange={setPlaylistName} />
        <TextInput value={playlistDescription} placeholder='Playlist description...' onChange={setPlaylistDescription} />
        <Button type='submit' onClick={handleSubmit}>Submit</Button>
      </Modal>

      <IconPlaylistAdd onClick={open} size='150' />

      <div>{playlists.map((playlist, index) => renderItem(playlist, index))}</div>
    </div>
  );
}

问题原因

核心问题是React的setState是异步操作:

  • 初始化的useEffect中,调用setToken(${token})后立刻执行fetchPlaylists(),此时组件状态里的token还是初始空字符串,导致请求头的Authorization为Bearer ,触发Spotify API的400错误。
  • 点击提交按钮时,组件已完成渲染,token状态已更新为localStorage中的有效值,因此调用fetchPlaylists()能拿到正确token,请求成功。

解决方案

方案1:直接使用localStorage获取的token调用接口

不依赖组件状态,直接将localStorage拿到的token传入fetchPlaylists:

useEffect(() => {
  const storedToken = localStorage.getItem('token')
  setToken(storedToken || '')
  // 直接用storedToken调用,无需等待状态更新
  fetchPlaylists(storedToken)
}, [])

// 修改fetchPlaylists,接收token参数
const fetchPlaylists = (authToken?: string) => {
  const currentToken = authToken || token
  if (!currentToken) return // 无token时直接返回,避免无效请求

  fetch('https://api.spotify.com/v1/me/playlists', {
    headers: {
      'Authorization': `Bearer ${currentToken}`
    }
  })
    .then((response) => {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error('Failed to fetch playlists custom error');
      }
    })
    .then((data) => {
      setPlaylists(data.items)
      console.log(data.items)
    })
}

// handleSubmit中调用时无需传参,使用状态中的token即可
function handleSubmit() {
  // ...原有代码
  setTimeout(() => fetchPlaylists(), 500)
}

方案2:监听token状态变化,更新时调用接口

给useEffect添加token依赖,当token从空字符串更新为有效值时自动调用接口:

// 初始化仅获取token并存入状态
useEffect(() => {
  const storedToken = localStorage.getItem('token')
  setToken(storedToken || '')
}, [])

// 新增useEffect,监听token变化
useEffect(() => {
  // 仅当token存在时调用
  if (token) {
    fetchPlaylists()
  }
}, [token]) // 依赖token,状态更新时触发

方案3:优化token初始化逻辑,避免异步问题

组件初始化时直接同步获取token并赋值,跳过useState的异步更新:

// 直接将token初始化为localStorage的值,避免异步问题
const [token, setToken] = useState(localStorage.getItem('token') || '')

useEffect(() => {
  fetchPlaylists()
}, [])

额外建议

  • 在fetchPlaylists中添加token校验,避免空token发起无效请求
  • 处理fetch的网络异常,避免控制台出现未捕获报错

内容的提问来源于stack exchange,提问作者NeemaToto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:17:09