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

子组件更新Context后如何通过router.push()实现路由跳转?

如何在React Context状态更新后执行路由跳转(避免首次渲染触发)

问题场景

我有一个Context Provider用于管理列表状态,子组件通过点击按钮更新该状态后需要跳转到/another页面(必须确保状态更新完成后再跳转)。但当前使用useEffect监听myList变化时,会在组件首次渲染时就触发路由跳转,不符合预期。

Context Provider代码

// Context Provider
import { createContext, useContext, useState } from 'react';
export let MyContext = createContext();

let initList = ['1']; // initial state

export default function MyProvider({ children }) {
  const [myList, setMyList] = useState(initList);

  const context = { myList, setMyList };
  return (
    <MyContext.Provider value={context}>
      {children}
    </MyContext.Provider>
  );
}

子组件代码(存在问题)

// Child Component
'use client';
import { useRouter } from 'next/navigation';
import { useContext, useEffect, useState } from 'react';
import { MyContext } from '../context-provider';

export default function Child() {
  let { myList, setMyList } = useContext(MyContext);
  const router = useRouter();
  console.log(myList);

  useEffect(() => {
    router.push('/another');
  }, [myList, router]);

  return (
    <div>
      <button type='button' onClick={() => setMyList([...myList, '2'])}>
        Update state and Navigate
      </button>
    </div>
  );
}

我目前想到的方案是比较myList更新前后的长度,但想知道有没有其他更优雅的实现方式?


解决方案

方案1:添加触发标记,区分首次渲染与用户操作

在子组件中新增一个状态标记是否是用户点击触发的更新,仅当该标记为true且状态更新后才执行跳转:

// Child Component
'use client';
import { useRouter } from 'next/navigation';
import { useContext, useEffect, useState } from 'react';
import { MyContext } from '../context-provider';

export default function Child() {
  let { myList, setMyList } = useContext(MyContext);
  const router = useRouter();
  const [isTriggeredByClick, setIsTriggeredByClick] = useState(false);

  useEffect(() => {
    if (isTriggeredByClick) {
      router.push('/another');
      // 重置标记,避免后续状态更新重复触发跳转
      setIsTriggeredByClick(false);
    }
  }, [myList, router, isTriggeredByClick]);

  return (
    <div>
      <button 
        type='button' 
        onClick={() => {
          setMyList(prev => [...prev, '2']); // 使用函数式更新确保取到最新状态
          setIsTriggeredByClick(true);
        }}
      >
        Update state and Navigate
      </button>
    </div>
  );
}

方案2:使用useRef记录初始状态,跳过首次渲染

用useRef存储初始列表状态,在useEffect中判断当前状态是否与初始状态一致,只有不一致时才执行跳转:

// Child Component
'use client';
import { useRouter } from 'next/navigation';
import { useContext, useEffect, useRef } from 'react';
import { MyContext } from '../context-provider';

export default function Child() {
  let { myList, setMyList } = useContext(MyContext);
  const router = useRouter();
  const initialListRef = useRef(myList);

  useEffect(() => {
    // 仅当状态与初始状态不同时才跳转
    if (myList !== initialListRef.current) {
      router.push('/another');
    }
  }, [myList, router]);

  return (
    <div>
      <button 
        type='button' 
        onClick={() => setMyList(prev => [...prev, '2'])}
      >
        Update state and Navigate
      </button>
    </div>
  );
}

方案3:在点击事件的状态更新回调中标记跳转

利用函数式更新的特性,在更新状态的同时标记需要跳转,确保状态更新完成后触发路由:

// Child Component
'use client';
import { useRouter } from 'next/navigation';
import { useContext, useEffect, useState } from 'react';
import { MyContext } from '../context-provider';

export default function Child() {
  let { myList, setMyList } = useContext(MyContext);
  const router = useRouter();
  const [shouldNavigate, setShouldNavigate] = useState(false);

  useEffect(() => {
    if (shouldNavigate) {
      router.push('/another');
      setShouldNavigate(false);
    }
  }, [shouldNavigate, router]);

  return (
    <div>
      <button 
        type='button' 
        onClick={() => {
          setMyList(prev => {
            // 更新状态后标记需要跳转
            setShouldNavigate(true);
            return [...prev, '2'];
          });
        }}
      >
        Update state and Navigate
      </button>
    </div>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:35:00