React 设置上下文并导航

huangapple go评论62阅读模式
英文:

React set context and navigate

问题

以下是您提供的代码的翻译:

Consent.tsx

const onConsentButtonClick = () => {
  setMyContext({ userConsent: true });
  navigate("postConsent"); // 我使用的是 react-router v6。navigate = useNavigate();
}

PostConsent.tsx

useEffect(() => {
  if (!myContext.userConsent) { // 在渲染时,userConsent 总是未定义
    // 显示错误并回退到 Consent.tsx
  }
}, []);

这段代码不起作用,因为看起来 PostConsent 组件在上下文更新之前就被渲染了。我使用超时来验证了这一点。如果我将按钮点击函数更改如下,它就可以正常工作。

const onConsentButtonClick = () => {
  setMyContext({ userConsent: true });
  setTimeout(() => {
    navigate("postConsent");
  }, 1000);
}

有没有一种方法可以确保上下文更新完成后再导航到下一个屏幕?

而且我是否正确使用了上下文?有没有更好的方法来实现这一点?

在我的情况下,属性传递和通过位置传递状态都不是选项(因为我不希望这组屏幕上的浏览器后退工作)。

如果有更好的替代方法,请提出建议。

编辑:
以下是我的完整上下文文件:

const MyContext = createContext(null);

export function MyProvider({ children }) {
    const [myContext, setMyContext] = useState({});
    return (
        <MyContext.Provider value={{ myContext, setMyContext }}>
            {children}
        </MyContext.Provider>
    );
}

export const useMyContext = () => {
    return useContext(MyContext);
}

我在我的组件中使用这个钩子:

const { myContext, setMyContext } = useMyContext();

更新:
根据下面的答案,我编写了一个自定义钩子,以封装上下文设置部分和上下文实际设置后的导航部分。

type SetContextAndNavigate = {
    context: any,
    navigate: string
}

const defaultHookValues: SetContextAndNavigate = { context: { initialHookValue: "hook" }, navigate: "nowhere" };

function useSetContextAndNavigate() {
  const { myContext, setMyContext } = useMyContext();
  const [currentValues, setAndNavigate] = useState(defaultHookValues);
  const navigate = useNavigate();

  const setValuesBeforeNavigation = (args: SetContextAndNavigate) => {
    setAndNavigate(args);
    setMyContext(args.context);
  }

  useEffect(() => {
    if (currentValues.context == myContext) {
        navigate(currentValues.navigate);
    }
  }, [myContext]);

  return setValuesBeforeNavigation;
}

在组件中的使用方式:

const setContextAndNavigate = useSetContextAndNavigate();
setContextAndNavigate({ context: { ...consents }, navigate: "postConsent" });

请注意,这里提供的翻译仅包括您要求的代码部分,不包括任何其他内容。如果您需要进一步的解释或帮助,请告诉我。

英文:

I have a set of screens (3 for now) that can be accessed only if the user takes certain actions on the previous screen, like clicking on some checkboxes and a few links. I am using react-context to pass this information to these screens.

The essence of the code is below

Consent.tsx

const onConsentButtonClick = () =&gt; {
  setMyContext({userConsent: true});
  navigate(&quot;postConsent&quot;); // I am using react-router v6. navigate = useNavigate();
}

PostConsent.tsx

useEffect(() =&gt; {
  if(!myContext.userConsent) { // userConsent is always undefined on render
    // show errors and fallback to Consent.tsx
  }
}, []);

This does not work because it looks like the PostConsent component is rendered before the context could update. I verified this using a timeout. If I change the button click function as below it works properly.

const onConsentButtonClick = () =&gt; {
  setMyContext({userConsent: true});
  setTimeout(() =&gt; {
    navigate(&quot;postConsent&quot;);
  }, 1000);
}

Is there a way to ensure that the context update has finished before navigating to the next screen?

And am I using contexts right? is there a better way to achieve this?

Prop drilling is not an option in my case and neither is passing state via location (because I don't want browser back to work on these set of screens).

Please do suggest if there are better alternative ways to achieve this.

EDIT:
Here is my complete context file

const MyContext = createContext(null);

export function MyProvider({ children }) {
    const [myContext, setMyContext] = useState({});
    return (
   &lt;MyContext.Provider value={{myContext, setMyContext}}&gt;
            {children}
    &lt;/MyContext.Provider&gt;
    );
}

export const useMyContext = () =&gt; {
    return useContext(MyContext);
}

and I use this hook in my components

const {myContext, setMyContext} = useMyContext();

UPDATE:
Based on the answer below, I wrote a custom hook to encapsulate the context setting part and the navigation after the context is actually set

type SetContextAndNavigate = {
    context: any,
    navigate: string
}

const defaultHookValues: SetContextAndNavigate = { context: {initialHookValue: &quot;hook&quot;}, navigate: &quot;nowhere&quot; }; 

function useSetContextAndNavigate() {
  const { myContext, setMyContext } = useMyContext();
  const [currentValues, setAndNavigate] = useState(defaultHookValues);
  const navigate = useNavigate();

  const setValuesBeforeNavigation = (args: SetContextAndNavigate) =&gt; {
    setAndNavigate(args);
    setMyContext(args.context);
  }

  useEffect(() =&gt; {
    if(currentValues.context == myContext) {
        navigate(currentValues.navigate);
    }
  }, [myContext]);

  return setValuesBeforeNavigation;
}

Usage in component:

const setContextAndNavigate = useSetContextAndNavigate();
setContextAndNavigate({context: {...consents}, navigate: &quot;postConsent&quot;});

答案1

得分: 0

你目前对上下文的使用大部分都是正确的。

问题在于,正如你所说,React 中设置状态是异步的,所以它在下一个时钟周期中进行。

在 Vue.js 中,有一个名为 nextTick() 的函数可以用来处理这种情况,但在 React 中没有。在 React 中最接近 nextTick() 的方法是:

setMyContext(...);
setTimeout(() => {
  navigate(...);
}, 0);

上面的方法可行,但不够优雅。更合适的处理方式是使用 useEffect 监听状态变化。

在你的情况下,你想要追踪用户同意状态,无论是未完成、已同意还是未同意。我们可以将 undefined 用作 "未完成" 状态,例如:

像这样(在 PostConsent 内部):

useEffect(() => {
  if (typeof myContext.userConsent === 'boolean') {
    // 如果已同意,执行某些操作。否则,重定向到失败页面
  } else {
    // 等待中... 显示一些加载指示器,或者在页面加载时只显示加载指示器,然后在这里什么都不做,或者在`myContext.userConsent` 可用之前只呈现 `null`
  }
}, [myContext]); // 这是重要的一行,用来监听 `myContext` 的变化

最后,最佳的方法是在导航之前检查上下文状态的更新:

useEffect(() => {
  if (myContext.userConsent) {
    navigate("postConsent");
  }
}, [myContext]);

...

const onConsentButtonClick = () => {
  setMyContext({userConsent: true});
}

希望这能帮助你解决问题。

英文:

The way you're using context is mostly correct.

Problem is as you said, setting state in React is asynchronous so it takes place in the next tick.

In Vue.js, there's a nextTick() function for that but in React, there's not. The closest to nextTick() in React:

setMyContext(...);
setTimeout(() =&gt; {
  navigate(...);
}, 0);

The above works but it's not elegant. A more proper way to handle it is to listen for the state change with useEffect.

In your case, you want to track the state of user's consent, whether it's not done, consented or not consented. We could use undefined as the "not done" state, for example.

Like this (inside PostConsent):

useEffect(() =&gt; {
  if (typeof myContext.userConsent === &#39;boolean&#39;) {
    // if consented, do something. otherwise, redirect to failure screen
  } else {
    // waiting... show some loading indicator or simply show loading indicator when page mounts and do nothing here, or simply render `null` until `myContext.userConsent` becomes available
  }
}, [myContext]); // This is the important line, to listen for `myContext`

Finally, the best approach is to check the context state update before navigating:

useEffect(() =&gt; {
  if (myContext.userConsent) {
    navigate(&quot;postConsent&quot;);
  }
}, [myContext]);

...

const onConsentButtonClick = () =&gt; {
  setMyContext({userConsent: true});
}

huangapple
  • 本文由 发表于 2023年6月13日 04:12:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76460025.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定