我正在从我的后端获取3个不同的端点.我的目标是显示所有数据.我需要将此数据传递给正确的组件,但我使用的是动态路由.

我的错误是我的数据以空数组的形式到达Artists.js.

我试过了:

使用useNaviate传递数据link to previous SOF question, user suggested redux toolkit

使用链接传递数据.

使用REDUX工具包传递数据.我是Redux的新手,所以我可能在代码中做了一些错误的事情.

我认为这是一个时机,渲染周期错误.然而,我不确定如何修复它.当我在Artists组件中添加一些条件渲染时,它在第二次渲染时向我显示了控制台日志(log)中的正确数据.但现在我不确定从那里该做什么.它被页面上写着"正在加载"所困.我try 在条件呈现(其中return <div>Loading...</div>;是)之后映射数据,但在页面刷新时,所有呈现的数据都从页面中消失.

以下是从上到下的代码:

Index.js

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./redux/store";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </React.StrictMode>
);

reportWebVitals();

App.js

import "./App.css";
import Genres from "./Components/Genres"
import TopTracks from "./Components/TopTracks"
import Results from "./Components/Results"
import { Routes, Route} from "react-router-dom"

function App() {
  return (
    <Routes>
      <Route path='/' element={<Genres />} />
      <Route path='/:genre' element={<Results />} />
      <Route path='/:artist' element={<TopTracks />} />
    </Routes>
  );
}

export default App;

Genres.js

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useDispatch } from 'react-redux';
import { setData } from '../redux/spotifyDataSlice';

function Genres() {
  const [genres, setGenres] = useState([]);
  const dispatch = useDispatch();

  useEffect(() => {
    fetch("/api/genres/")
      .then((response) => response.json())
      .then((data) => setGenres(data.genres))
      .catch((error) => console.log(error));
  }, []);

  function handleClick(genre) {
    const query_params = {
      genre: genre
    };
  
    Promise.all([
      fetch("/api/artists/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-CSRFToken": "",
        },
        body: JSON.stringify(query_params),
      }),
      fetch("/api/playlists/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-CSRFToken": "",
        },
        body: JSON.stringify(query_params),
      }),
      fetch("/api/tracks/", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-CSRFToken": "",
        },
        body: JSON.stringify(query_params),
      })
    ])
      .then((responses) => Promise.all(responses.map((response) => response.json())))
      .then(([artists, playlists, tracks]) => {
        dispatch(setData({ artists, playlists, tracks }));
      })
      .catch((error) => console.log(error));
  }

  return (
    <div>
      <div className="genre-list-container">
        <ul className="genre-list">
          {genres.map((genre) => (
            <li className="genre" key={genre}>
              <Link to={`/${genre}`} onClick={() => handleClick(genre)}>{genre}</Link>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default Genres;

Results.js

import React from "react";
import Artists from "./Artists";
import Playlists from "./Playlists";
import Tracks from "./Tracks";

function Results() {
  return (
    <div>
      <Artists />
      <Playlists />
      <Tracks />
    </div>
  );
}
export default Results;

Artists.js

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector } from 'react-redux';

function Artists() {
  const navigate = useNavigate();
  const artists = useSelector((state) => state.spotifyData.artists);
  console.log(artists)
  const [isLoading, setIsLoading] = useState(true);

  if (isLoading) {
    if (artists.length === 0) {
      return <div>No artists found</div>;
    } else {
      return <div>Loading...</div>;
    }
  }

  function handleClick(artist) {
    fetch("/api/top_tracks/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-CSRFToken": "",
      },
      body: JSON.stringify({ artist_id: artist.id }),
    })
      .then((response) => response.json())
      .then((data) => {
        console.log(data);
        navigate(`/${artist.name}`, { state: { data } });
      });
  }

  return (
    <div>
      <div>Artists:</div>
      {artists.map((artist) => (
        <div key={artist.id}>
          <img src={artist.image_url} alt={artist.name} />
          <h1 onClick={() => handleClick(artist)}>{artist.name}</h1>
          <p>Popularity: {artist.popularity}</p>
          <p>Genres: {artist.genres}</p>
        </div>
      ))}
    </div>
  );
}
export default Artists;

SpotifyDataSlice.js:

import { createSlice } from '@reduxjs/toolkit';

const spotifyDataSlice = createSlice({
  name: 'spotifyData',
  initialState: {
    艺术家: [],
    playlists: [],
    tracks: [],
  },
  reducers: {
    setData(state, action) {
      const { artists, playlists, tracks } = action.payload;
      console.log("Artists:", artists);
      console.log("Playlists:", playlists);
      console.log("Tracks:", tracks);
      state.artists = artists;
      state.playlists = playlists;
      state.tracks = tracks;
    },
  },
});

export const { setData } = spotifyDataSlice.actions;
export default spotifyDataSlice.reducer;

_

import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import spotifyDataReducer from './spotifyDataSlice';

const loggerMiddleware = store => next => action => {
  console.log('Dispatching:', action);
  const result = next(action);
  console.log('Next State:', store.getState());
  return result;
};

const store = configureStore({
  reducer: {
    spotifyData: spotifyDataReducer,
  },
  middleware: getDefaultMiddleware().concat(loggerMiddleware)
});

export default store;

编辑: 以下是我的帖子回复的一些例子.

{艺术家: {…}, playlists: {…}, tracks: {…}}

艺术家:

艺术家: {total: 13, 艺术家: Array(13)}

Artists对象的示例:

{name: 'Aborted', id: '1XRhUgCyzIdeT8d9KMfeDR', image_url: 'https://i.scdn.co/image/ab6761610000e5eb457a41afcb20ec257fce22d4', popularity: 39, genres: Array(8)}

推荐答案

与其试图管理Results个或Results个子页面中的任何数据加载状态,我建议实际上更多地利用redux-TOOLKIT,方法是将所有获取逻辑移动到一个可以将其加载状态合并到状态中的异步操作中.

示例:

SpotifyDataSlice.js

创建一个Thunk函数来处理获取,并更新状态片以包括一个加载状态,该加载状态在异步操作挂起并稳定时更新.

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const fetchData = createAsyncThunk(
  'spotifyData/fetchData',
  async (genre, thunkAPI) => {
    const options = {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-CSRFToken": "",
      },
      body: JSON.stringify({ genre }),
    };
  
    try {
      const requests = ["artists", "playlists", "tracks"]
        .map(key => fetch(`/api/${key}/`, options)
          .then(response => response.json())
          // access response property, e.g. data.artists, data.playlists, etc
          .then(data => data[key])
        );

      const [artists, playlists, tracks] = await Promise.all(requests);
      return { artists, playlists, tracks };
    } catch(error) {
      return thunkAPI.rejectWithValue(error);
    }
    
  }
);

const spotifyDataSlice = createSlice({
  name: 'spotifyData',
  initialState: {
    loading: false,
    artists: [],
    playlists: [],
    tracks: [],
  },
  extraReducers: builder => {
    builder
      .addCase(fetchData.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchData.fulfilled, (state, action) => {
        state.loading = false;
        state.artists = action.payload.artists;
        state.playlists = action.payload.playlists;
        state.tracks = action.payload.tracks;
      })
      .addCase(fetchData.rejected, (state) => {
        state.loading = false;
      });
    },
  },
});

export default spotifyDataSlice.reducer;

Genres.jsx

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { useDispatch } from 'react-redux';
import { fetchData } from '../redux/spotifyDataSlice';

function Genres() {
  const dispatch = useDispatch();

  const [genres, setGenres] = useState([]);

  useEffect(() => {
    fetch("/api/genres/")
      .then((response) => response.json())
      .then((data) => setGenres(data.genres))
      .catch((error) => console.log(error));
  }, []);

  const handleClick = () => genre => {
    dispatch(fetchData(genre));
  }

  return (
    <div className="genre-list-container">
      <ul className="genre-list">
        {genres.map((genre) => (
          <li className="genre" key={genre}>
            <Link to={`/${genre}`} onClick={handleClick(genre)}>
              {genre}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Genres;

Results.jsx

import React from "react";
import { useSelector } from 'react-redux';
import Artists from "./Artists";
import Playlists from "./Playlists";
import Tracks from "./Tracks";

function Results() {
  const isLoading = useSelector(state => state.spotifyData.loading);

  return (
    <div>
      {isLoading
        ? <div>Loading...</div>
        : (
          <>
            <Artists />
            <Playlists />
            <Tracks />
          </>
        )
      }
    </div>
  );
}

export default Results;

Artists.jsx(100 and 101 components get same treatment)

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector } from 'react-redux';

function Artists() {
  const navigate = useNavigate();
  const artists = useSelector(state => state.spotifyData.artists);

  function handleClick(artist) {
    ....
  }

  if (!artists.length) {
    return <div>No artists found</div>;
  }

  return (
    <div>
      <div>Artists:</div>
      {artists.map((artist) => (
        <div key={artist.id}>
          <img src={artist.image_url} alt={artist.name} />
          <h1 onClick={() => handleClick(artist)}>{artist.name}</h1>
          <p>Popularity: {artist.popularity}</p>
          <p>Genres: {artist.genres}</p>
        </div>
      ))}
    </div>
  );
}

export default Artists;

但是在页面刷新时,所有呈现的数据都会从页面中消失

Redux是内存中状态管理,当页面重新加载时,来自前一个实例的内存将被转储.典型的解决方案是将Redux存储持久化到本地存储或其他更长期的存储中.Redux Persist是一个很好的通用解决方案,而且很容易集成.

作为权宜之计,您可以简单地将获取逻辑移动到"/:genres"路由,这样当页面重新加载并挂载Results时,它将简单地触发重新获取.

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";

function Genres() {
  const [genres, setGenres] = useState([]);

  useEffect(() => {
    fetch("/api/genres/")
      .then((response) => response.json())
      .then((data) => setGenres(data.genres))
      .catch((error) => console.log(error));
  }, []);

  return (
    <div className="genre-list-container">
      <ul className="genre-list">
        {genres.map((genre) => (
          <li className="genre" key={genre}>
            <Link to={`/${genre}`}>
              {genre}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Genres;
import React from "react";
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from "react-router-dom";
import Artists from "./Artists";
import Playlists from "./Playlists";
import Tracks from "./Tracks";
import { fetchData } from '../redux/spotifyDataSlice';

function Results() {
  const dispatch = useDispatch();
  const { genre } = useParams();

  const isLoading = useSelector(state => state.spotifyData.loading);

  React.useEffect(() => {
    // fetch data on component mount or when genre value changes
    dispatch(fetchData(genre));
  }, [dispatch, genre]);

  return (
    <div>
      {isLoading
        ? <div>Loading...</div>
        : (
          <>
            <Artists />
            <Playlists />
            <Tracks />
          </>
        )
      }
    </div>
  );
}

export default Results;

Reactjs相关问答推荐

有正确的方法来设置我的金牛座应用程序的图标吗?

如何在react组件中使用formik进行验证

如何在取数后添加新数据,并在页面上保存旧数据?

无法从正在使用Reaction的Electron 商务store 项目中的购物车中删除项目

更改购物车中的产品数量后,PayPal Reaction/Next JS按钮未更新

面对动画警告:未指定`useNativeDriver`.这是必需的选项,并且必须在REACT本机中显式设置为`true`或`False`

在React Create App TS项目中安装material UI

Reaction-路由-DOM v6与v5

Github 操作失败:发布 NPM 包

状态改变不会触发重新渲染

React Todo List 应用程序我在实现删除功能时遇到问题

警告:遇到两个子元素拥有同一把 keys .键应该是唯一的,以便组件在更新时保持其身份

当 URL 与我声明的任何路由都不匹配时,如何使用不会重定向到错误页面的动态 URL? - react 路由 dom

如何优化 React 应用程序的性能?

根据其中的值不同地react setState 更新状态

基于标记名的博客详细信息分组没有发生

如何处理页面加载时发生的 reactjs 错误?

如何检索包含动态段的未解析路径?

渲染的钩子比预期的少.这可能是由 React Hooks 中意外的提前返回语句引起的

警告:您在没有 `onChange` 处理程序的情况下为表单字段提供了 `value` 属性