尝试使用React Router useSearchParams对数据进行排序时尚未完成的React使用效果尚未完成

发布于 2025-02-11 07:53:50 字数 3147 浏览 1 评论 0原文

我有一个React组件,可以尝试先从TMDB API加载电影。然后,我创建一个使用查询参数对电影进行分类的按钮。我想使用查询参数,因为我想保留URL,以便我可以与排序值共享URL。 (这也是出于我的学习目的)。

当页面已经渲染并单击按钮时,它可以正常工作。

但是,当我尝试使用查询参数重新加载页面时,查询参数使用效果在电影完成之前先运行。

这是我的代码:

import { Box, Button } from '@mui/material';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

import tmdb from '../apis/tmdb';
import MovieCard from '../components/MovieCard';

const MovieList = () => {
    const [queryParams, setQueryParams] = useSearchParams();
    const [movies, setMovies] = useState([]);

    useEffect(() => {
        const fetchMovies = async () => {
            try {
                const fetchedMovies = await tmdb.get("trending/movie/week");
                setMovies(fetchedMovies.data.results);
            } catch (error) {
                console.log(error);
            }
        }

        fetchMovies();
    }, []);

    useEffect(() => {
        const sortMovies = (type) => {
            if (type === 'asc') {
                const sorted = movies.sort((a, b) => a.vote_average - b.vote_average);
                setMovies(sorted);
            }
            if (type === 'desc') {
                const sorted = movies.sort((a, b) => b.vote_average - a.vote_average);
                setMovies(sorted);
            }
        }

        sortMovies(queryParams.get('sort'));
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [queryParams]);

    const setSortParam = (type) => {
        queryParams.set("sort", type);
        setQueryParams(queryParams);
    }

    return (
        <Box sx={{
            display: 'flex',
            flexDirection: 'column',
            mt: 5,
        }}>
            <Box sx={{
                mt: 5,
                display: 'flex',
                flexDirection: 'row',
                justifyContent: 'flex-end',
                alignItems: 'center',
            }}>
                Sort by Rating
                <Button
                    variant="contained"
                    sx={{ ml: 2 }}
                    onClick={() => setSortParam("asc")}
                >
                    Asc
                </Button>
                <Button
                    variant="contained"
                    sx={{ ml: 2, mr: 2 }}
                    onClick={() => setSortParam("desc")}
                >
                    Desc
                </Button>
            </Box>
            <Box sx={{
                display: 'flex',
                flexDirection: 'row',
                flexWrap: 'wrap',
                justifyContent: 'space-between',
            }}>
                {
                    movies.map(movie => (
                        <MovieCard key={movie.title} movie={movie}></MovieCard>
                    ))
                }
            </Box>
        </Box>
    );
}

export default MovieList;

我尝试创建新状态以跟踪电影长度&gt; 0,但有时是越野车。我还尝试让USEREF跟踪电影长度&gt; 0但是它不起作用。

无论如何,是否可以等待首先使用效果完成?还是我应该使用其他方法来读取查询参数和排序?

I have a React component that try to load movies first from tmdb API. Then, I create a button to sort the movies using query params. I want to use query params because I want to preserve the URL so that I can share the URL with sort value. (This is for my learning purpose also).

When the page is already rendered and I click the button, it's work fine.

But when I try to reload the page with query params, the query params useEffect is run first before the movies finished loaded.

Here is my code:

import { Box, Button } from '@mui/material';
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

import tmdb from '../apis/tmdb';
import MovieCard from '../components/MovieCard';

const MovieList = () => {
    const [queryParams, setQueryParams] = useSearchParams();
    const [movies, setMovies] = useState([]);

    useEffect(() => {
        const fetchMovies = async () => {
            try {
                const fetchedMovies = await tmdb.get("trending/movie/week");
                setMovies(fetchedMovies.data.results);
            } catch (error) {
                console.log(error);
            }
        }

        fetchMovies();
    }, []);

    useEffect(() => {
        const sortMovies = (type) => {
            if (type === 'asc') {
                const sorted = movies.sort((a, b) => a.vote_average - b.vote_average);
                setMovies(sorted);
            }
            if (type === 'desc') {
                const sorted = movies.sort((a, b) => b.vote_average - a.vote_average);
                setMovies(sorted);
            }
        }

        sortMovies(queryParams.get('sort'));
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [queryParams]);

    const setSortParam = (type) => {
        queryParams.set("sort", type);
        setQueryParams(queryParams);
    }

    return (
        <Box sx={{
            display: 'flex',
            flexDirection: 'column',
            mt: 5,
        }}>
            <Box sx={{
                mt: 5,
                display: 'flex',
                flexDirection: 'row',
                justifyContent: 'flex-end',
                alignItems: 'center',
            }}>
                Sort by Rating
                <Button
                    variant="contained"
                    sx={{ ml: 2 }}
                    onClick={() => setSortParam("asc")}
                >
                    Asc
                </Button>
                <Button
                    variant="contained"
                    sx={{ ml: 2, mr: 2 }}
                    onClick={() => setSortParam("desc")}
                >
                    Desc
                </Button>
            </Box>
            <Box sx={{
                display: 'flex',
                flexDirection: 'row',
                flexWrap: 'wrap',
                justifyContent: 'space-between',
            }}>
                {
                    movies.map(movie => (
                        <MovieCard key={movie.title} movie={movie}></MovieCard>
                    ))
                }
            </Box>
        </Box>
    );
}

export default MovieList;

I try to create new state to track if the movies length > 0, but sometimes it's buggy. I also try to useRef to track if the movies length > 0 but it's not working.

Is there anyway to wait for first useEffect to finish? Or should I use other approach than useEffect to read query params and sort?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

初见你 2025-02-18 07:53:50

我认为您正在尝试合并电影状态( ie真实)与派生状态( ie电影的排序顺序)) 。派生状态通常不属于状态。您可以轻松地从电影状态和当前sort querystring参数值中得出排序的电影数组。

示例:

import { Box, Button } from "@mui/material";
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";

import tmdb from "../apis/tmdb";
import MovieCard from "../components/MovieCard";

// Sort comparators
const sortVoteAverageAsc = (a, b) => a.vote_average - b.vote_average;
const sortVoteAverageDesc = (a, b) => sortVoteAverageAsc(b, a);

// Sorting utility function
const sortVoteAverage = (sort = "asc") =>
  sort === 'asc' ? sortVoteAverageAsc : sortVoteAverageDesc;

const MovieList = () => {
  const [queryParams, setQueryParams] = useSearchParams();
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    const fetchMovies = async () => {
      try {
        const fetchedMovies = await tmdb.get("trending/movie/week");
        setMovies(fetchedMovies.data.results);
      } catch (error) {
        console.log(error);
      }
    };

    fetchMovies();
  }, []);

  const setSortParam = (type) => {
    queryParams.set("sort", type);
    setQueryParams(queryParams);
  };

  // create shallow copy of movies state, then sort by sort query parameter
  const sortedMovies = movies
    .slice()
    .sort(sortVoteAverage(queryParams.get("sort")));

  return (
    <Box
      ...
    >
      <Box
        ...
      >
        Sort by Rating
        <Button
          variant="contained"
          sx={{ ml: 2 }}
          onClick={() => setSortParam("asc")}
        >
          Asc
        </Button>
        <Button
          variant="contained"
          sx={{ ml: 2, mr: 2 }}
          onClick={() => setSortParam("desc")}
        >
          Desc
        </Button>
      </Box>
      <Box
        sx={{
          display: "flex",
          flexDirection: "row",
          flexWrap: "wrap",
          justifyContent: "space-between"
        }}
      >
        {sortedMovies.map((movie) => (
          <MovieCard key={movie.title} movie={movie} />
        ))}
      </Box>
    </Box>
  );
};

如果需要,您可以使用usememo hook nock 值 hook nock nock,依赖于电影状态>状态和当前sot querystring参数值。

例子:

const sort = queryParams.get("sort");

const sortedMovies = useMemo(() => {
  return movies
    .slice()
    .sort(sortVoteAverage(sort))
}, [movies, sort]);

I think you are trying to merge the movies state (i.e. the source of truth) with the derived state (i.e. the sorting order of movies). Derived state generally doesn't belong in state. You can easily derive the sorted movies array from the movies state and the current sort queryString parameter value.

Example:

import { Box, Button } from "@mui/material";
import { useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";

import tmdb from "../apis/tmdb";
import MovieCard from "../components/MovieCard";

// Sort comparators
const sortVoteAverageAsc = (a, b) => a.vote_average - b.vote_average;
const sortVoteAverageDesc = (a, b) => sortVoteAverageAsc(b, a);

// Sorting utility function
const sortVoteAverage = (sort = "asc") =>
  sort === 'asc' ? sortVoteAverageAsc : sortVoteAverageDesc;

const MovieList = () => {
  const [queryParams, setQueryParams] = useSearchParams();
  const [movies, setMovies] = useState([]);

  useEffect(() => {
    const fetchMovies = async () => {
      try {
        const fetchedMovies = await tmdb.get("trending/movie/week");
        setMovies(fetchedMovies.data.results);
      } catch (error) {
        console.log(error);
      }
    };

    fetchMovies();
  }, []);

  const setSortParam = (type) => {
    queryParams.set("sort", type);
    setQueryParams(queryParams);
  };

  // create shallow copy of movies state, then sort by sort query parameter
  const sortedMovies = movies
    .slice()
    .sort(sortVoteAverage(queryParams.get("sort")));

  return (
    <Box
      ...
    >
      <Box
        ...
      >
        Sort by Rating
        <Button
          variant="contained"
          sx={{ ml: 2 }}
          onClick={() => setSortParam("asc")}
        >
          Asc
        </Button>
        <Button
          variant="contained"
          sx={{ ml: 2, mr: 2 }}
          onClick={() => setSortParam("desc")}
        >
          Desc
        </Button>
      </Box>
      <Box
        sx={{
          display: "flex",
          flexDirection: "row",
          flexWrap: "wrap",
          justifyContent: "space-between"
        }}
      >
        {sortedMovies.map((movie) => (
          <MovieCard key={movie.title} movie={movie} />
        ))}
      </Box>
    </Box>
  );
};

If needed, you can memoize the sortedMovies value using the useMemo hook with a dependency on the movies state and the current sort queryString parameter value.

Example:

const sort = queryParams.get("sort");

const sortedMovies = useMemo(() => {
  return movies
    .slice()
    .sort(sortVoteAverage(sort))
}, [movies, sort]);
梦纸 2025-02-18 07:53:50

我认为您需要一个州来跟踪电影是否已加载

const [moviesHasLoaded, setMoviesHasLoaded] = useState(false);

useEffect(() => {
    const fetchMovies = async () => {
        try {
            const fetchedMovies = await tmdb.get("trending/movie/week");
            setMovies(fetchedMovies.data.results);
            setMoviesHasLoaded(true);
        } catch (error) {
            console.log(error);
        }
    }

    fetchMovies();
}, []);

useEffect(() => {
    if (!moviesHasLoaded) return;
    const sortMovies = (type) => {
        if (type === 'asc') {
            const sorted = movies.sort((a, b) => a.vote_average - b.vote_average);
            setMovies(sorted);
        }
        if (type === 'desc') {
            const sorted = movies.sort((a, b) => b.vote_average - a.vote_average);
            setMovies(sorted);
        }
    }

    sortMovies(queryParams.get('sort'));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryParams, moviesHasLoaded]);

I think you need a state to track if the movies has been loaded

const [moviesHasLoaded, setMoviesHasLoaded] = useState(false);

useEffect(() => {
    const fetchMovies = async () => {
        try {
            const fetchedMovies = await tmdb.get("trending/movie/week");
            setMovies(fetchedMovies.data.results);
            setMoviesHasLoaded(true);
        } catch (error) {
            console.log(error);
        }
    }

    fetchMovies();
}, []);

useEffect(() => {
    if (!moviesHasLoaded) return;
    const sortMovies = (type) => {
        if (type === 'asc') {
            const sorted = movies.sort((a, b) => a.vote_average - b.vote_average);
            setMovies(sorted);
        }
        if (type === 'desc') {
            const sorted = movies.sort((a, b) => b.vote_average - a.vote_average);
            setMovies(sorted);
        }
    }

    sortMovies(queryParams.get('sort'));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryParams, moviesHasLoaded]);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文