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

React useEffect请求未完成时useSearchParams排序异常问题

问题根源

你的代码存在两个核心问题,导致携带排序参数刷新页面时排序失效:

  • 两个useEffect的执行时序没有绑定:带query参数刷新页面时,监听queryParams的effect会在组件挂载后立刻执行,此时接口请求还未返回,movies还是初始空数组,排序操作等于对空数组执行,自然不会有效果。
  • 违反React状态不可变原则:Array.sort()是原地修改数组的方法,直接对movies状态调用sort会直接修改状态原值,很容易触发渲染不更新的隐性bug。
推荐解决方案

不要把排序后的结果存入movies状态,将接口返回的原始电影数据单独存储,渲染前根据当前query参数的排序规则做派生计算即可,从根源上避免两个effect互相等待的时序问题,后续扩展筛选、分页功能时也不会出现状态同步bug。

修改后的可运行代码如下:

import { Box, Button } from '@mui/material';
import { useEffect, useMemo, 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 [originalMovies, setOriginalMovies] = useState([]);
    const [isLoading, setIsLoading] = useState(false);

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

        fetchMovies();
    }, []);

    // 通过useMemo派生排序后的列表,依赖原始数据和排序参数,任意一个变化都会自动重算
    const sortedMovies = useMemo(() => {
        const sortType = queryParams.get('sort');
        // 无排序参数或数据未加载完成时,直接返回原始数据
        if (!sortType || originalMovies.length === 0) {
            return originalMovies;
        }
        // 先拷贝原数组再排序,避免原地修改状态
        const moviesToSort = [...originalMovies];
        if (sortType === 'asc') {
            return moviesToSort.sort((a, b) => a.vote_average - b.vote_average);
        }
        if (sortType === 'desc') {
            return moviesToSort.sort((a, b) => b.vote_average - a.vote_average);
        }
        return originalMovies;
    }, [originalMovies, queryParams]);

    const setSortParam = (type) => {
        // 创建新的URLSearchParams实例,避免原地修改原对象导致更新不触发
        const newParams = new URLSearchParams(queryParams);
        newParams.set("sort", type);
        setQueryParams(newParams);
    }

    if (isLoading) {
        return <Box sx={{ mt:5 }}>电影数据加载中...</Box>
    }

    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',
            }}>
                {
                    sortedMovies.map(movie => (
                        // 用接口返回的唯一id作为key,避免重名电影导致的渲染错误
                        <MovieCard key={movie.id} movie={movie}></MovieCard>
                    ))
                }
            </Box>
        </Box>
    );
}

export default MovieList;

其他可选方案(不推荐)

如果你一定要保留双useEffect的串行执行逻辑,只需要给排序逻辑加非空判断,同时把movies加入effect依赖数组即可:

useEffect(() => {
    // 数据未加载完成时直接跳过,等movies更新后effect会自动重跑
    if (movies.length === 0) return;
    const sortType = queryParams.get('sort');
    if (!sortType) return;
    const sorted = [...movies].sort((a,b) => 
        sortType === 'asc' ? a.vote_average - b.vote_average : b.vote_average - a.vote_average
    );
    setMovies(sorted);
}, [queryParams, movies]);

这种方式需要反复同步原始数据和排序后的数据,后续加筛选、分页等功能时很容易出现状态不同步的bug,不建议长期使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 13:27:22