英文:
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 = () => {
setMyContext({userConsent: true});
navigate("postConsent"); // I am using react-router v6. navigate = useNavigate();
}
PostConsent.tsx
useEffect(() => {
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 = () => {
setMyContext({userConsent: true});
setTimeout(() => {
navigate("postConsent");
}, 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 (
<MyContext.Provider value={{myContext, setMyContext}}>
{children}
</MyContext.Provider>
);
}
export const useMyContext = () => {
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: "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;
}
Usage in component:
const setContextAndNavigate = useSetContextAndNavigate();
setContextAndNavigate({context: {...consents}, navigate: "postConsent"});
答案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(() => {
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(() => {
if (typeof myContext.userConsent === 'boolean') {
// 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(() => {
if (myContext.userConsent) {
navigate("postConsent");
}
}, [myContext]);
...
const onConsentButtonClick = () => {
setMyContext({userConsent: true});
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论