反应上下文状态值未在使用者中更新



通过"调度程序"设置为"搜索词"的第一个值在任何后续调用后仍然存在,我试图弄清楚为什么会这样或错误在哪里。

我有一个<ContextProvider />,其中定义了"搜索词"的状态,并且"搜索词"的值可能会因<ContextConsumer />触发的事件或由"调度程序"嵌套<ContextConsumer />组件而更改。我发现在调用"reducer"之后找不到所需的状态,即使考虑到"状态"更改不是立即的。

为简洁起见,下面发布的组件或代码经过简化以隔离主题,因此可能会有一些拼写错误,例如未声明的变量(因为我删除了不相关的代码块(。

上下文提供程序如下所示:

import React from 'react'
export const POSTS_SEARCH_RESULTS = 'POSTS_SEARCH_RESULTS'
export const GlobalStateContext = React.createContext()
export const GlobalDispatchContext = React.createContext()
const initialState = {
posts: [],
searchTerm: ''
}
const reducer = (state, action) => {
switch (action.type) {
case POSTS_SEARCH_RESULTS: {
return {
...state,
posts: action.posts,
searchTerm: action.searchTerm
}
}
default:
throw new Error('Bad Action Type')
}
}
const GlobalContextProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initialState)
return (
<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>
{children}
</GlobalDispatchContext.Provider>
</GlobalStateContext.Provider>
)
}
export default GlobalContextProvider

消费者看起来像:

const Search = () => {
const state = useContext(GlobalStateContext)
const { searchTerm, posts } = state
useEffect(() => {
console.log('[debug] <Search />: searchTerm: ', searchTerm);
}, [searchTerm])
return (  
<>
<LoadMoreScroll searchTerm={searchTerm} posts={posts} postCursor={postCursor} />
</>
)
}
export default Search

跟进的是嵌套的使用者子组件。useEffectsearchTerm有依赖关系;此值通过"调度程序"设置,并通过消费者中的 useContenxt 进行设置。

dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm: term })

并像这样消费:

const state = useContext(GlobalStateContext)
const { searchTerm, posts } = state

并传递给,例如<LoadMoreScroll searchTerm={searchTerm} />

所以,我所拥有的和它失败的是:

const LoadMoreScroll = ({ searchTerm, posts, postCursor }) => {
const dispatch = useContext(GlobalDispatchContext)
const [postsCached, setPostsCached] = useState(posts)
const [loading, setLoading] = useState(false)
const refScroll = useRef(null)
const [first] = useState(POSTS_SEARCH_INITIAL_NUMBER)
const [after, setAfter] = useState(postCursor)
const [isVisible, setIsVisible] = useState(false)
const [term, setTerm] = useState(searchTerm)
useEffect(() => {
loadMore({ first, after, term })
}, [isVisible])
useEffect(() => {
dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm })
}, [postsCached])
useEffect(() => {
setTerm(searchTerm)
const handler = _debounce(handleScroll, 1200)
window.addEventListener('scroll', handler)
return () => window.removeEventListener('scroll', handler)
}, [searchTerm])
const handleScroll = () => {
const offset = -(window.innerHeight * 0.1)
const top = refScroll.current.getBoundingClientRect().top
const isVisible = (top + offset) >= 0 && (top - offset) <= window.innerHeight
isVisible && setIsVisible(true)
}
const loadMore = async ({ first, after, term }) => {
if (loading) return
setLoading(true)
const result = await searchFor({
first,
after,
term
})
const nextPosts = result.data
setPostsCached([...postsCached, ...nextPosts])
setAfter(postCursor)
setLoading(false)
setIsVisible(false)
}
return (
<div ref={refScroll} className={style.loaderContainer}>
{ loading && <Loader /> }
</div>
)
}
export default LoadMoreScroll

预期的结果是让<LoadMoreScroll />将"调度程序"分配的"searchTerm"的最新值传递给"loadMore"函数,这将失败。相反,它的作用是消耗从第一次调用"调度程序"开始的"初始值"。这是在对"调度程序"的初始调用之后,任何后续的"调度程序"调用:

dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm: term })

这应该更新上下文"搜索词",无法做到。在上面的源代码中,loadmore 保存了设置的初始值!

具有类似逻辑的单独示例,工作没有任何问题(https://codesandbox.io/s/trusting-booth-1w40e?fontsize=14&hidenavigation=1&theme=dark(

希望尽快用解决方案更新上面的问题,如果有人发现问题,请告诉我!

代码沙盒链接有效,但在创建和使用context时似乎没有使用与上述代码相同的模式。

在提供的代码中,您创建了两个单独的提供程序。一个具有状态值,另一个具有调度值。

<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>

但是,代码沙箱在同一provider中同时使用statedispatch

<Application.Provider value={{ state, dispatch }}>

似乎GlobalContextProvider被导出了,但我不确定它是否用于包装任何消费者。

由于存在dispatchstate分离,我将将其用于我提出的解决方案。

实现似乎是正确的,但在我看来,您可以更进一步并创建两个自定义钩子,它们仅公开一种提供上下文值的方法,并且仅公开一种使用它的方式。

import React from "react";
export const POSTS_SEARCH_RESULTS = "POSTS_SEARCH_RESULTS";
// 
// notice that we don't need to export these anymore as we are going to be 
//
// using them in our custom hooks useGlobalState and useGlobalDispatch
//
//
const GlobalStateContext = React.createContext();
const GlobalDispatchContext = React.createContext();
const initialState = {
posts: [],
searchTerm: "",
};
const reducer = (state, action) => {
switch (action.type) {
case POSTS_SEARCH_RESULTS: {
return {
...state,
posts: action.posts,
searchTerm: action.searchTerm
};
}
default:
throw new Error("Bad Action Type");
}
};
const GlobalContextProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>
{children}
</GlobalDispatchContext.Provider>
</GlobalStateContext.Provider>
);
};

// If any of these hooks is not being called within a function component 
// that is rendered within the `GlobalContextProvider`, 
// we throw an error
const useGlobalState = () => {
const context = React.useContext(GlobalStateContext);
if (context === undefined) {
throw new Error(
"useGlobalState must be used within a GlobalContextProvider"
);
}
return context;
};
const useGlobalDispatch = () => {
const context = React.useContext(GlobalDispatchContext);
if (context === undefined) {
throw new Error(
"useGlobalDispatch must be used within a GlobalContextProvider"
);
}
return context;
};
// We only export the custom hooks for state and dispatch 
// and of course our`GlobalContextProvider`, which we are 
// going to wrap any part of our app that 
// needs to make use of this state
export { GlobalContextProvider, useGlobalState, useGlobalDispatch };

我在这里添加的只是几个自定义钩子,它们公开了每个上下文,即GlobalStateContextGlobalDispatchContext并将它们与GlobalContextProvider一起导出。

如果我们想在整个应用程序中全局可用,我们可以将GlobalContextProvider包装在App组件周围。

function App() {
return (
<div className="App">
<Search />
</div>
);
}
// If you forget to wrap the consumer with your provider, the custom hook will 
// throw an error letting you know that the hook is not being called 
// within a function component that is rendered within the 
// GlobalContextProvider as it's supposed to
const AppContainer = () => (
<GlobalContextProvider>
<App />
</GlobalContextProvider>
);
export default AppContainer;

如果要在应用的任何部分使用该state,或dispatch任何操作,则需要导入之前创建的相关自定义挂钩。

在搜索组件中,这类似于以下示例:

import { useGlobalState, useGlobalDispatch } from "./Store";
const Search = () => {
// Since we are doing this in our custom hook that is not needed anymore
// const state = useContext(GlobalStateContext)
// if you need to dispatch any actions you can 
// import the useGlobalDispatch hook and use it like so: 
// const dispatch = useGlobalDispatch();

const state = useGlobalState(); 
const { searchTerm, posts } = state
useEffect(() => {
console.log('[debug] <Search />: searchTerm: ', searchTerm);
}, [searchTerm])
return (  
<>
<LoadMoreScroll searchTerm={searchTerm} posts={posts} postCursor={postCursor} />
</>
)
}
export default Search

由于问题中提供的代码沙箱中缺少一些部分,因此我已将其重构为此概念的简化工作版本,希望能帮助您解决问题。

当我遇到上下文 API 和钩子问题时,我也发现这篇文章非常有用。

它遵循相同的模式,我一直在生产中使用它,并且对结果非常满意。

希望对:)有所帮助

相关内容

  • 没有找到相关文章

最新更新