reactjs - 反应上下文状态值未在消费者中更新
问题描述
通过“调度程序”设置为“搜索词”的第一个值在任何后续调用后仍然存在,我试图弄清楚为什么会这样或错误在哪里。
我已经<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
接下来是嵌套的 Consumer Children 组件。useEffect
具有对searchTerm
;的依赖项 这个值是通过“dispatcher”设置的,并通过 Consumer 中的 useContentxt 获取。
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 />
“dispatcher”分配的“searchTerm”的最新值传递给“loadMore”函数,但失败了。相反,它使用从第一次调用“调度程序”开始的“初始值”。这是在对“dispatcher”的初始调用之后的任何后续“dispatcher”调用:
dispatch({ type: POSTS_SEARCH_RESULTS, posts: postsCached, searchTerm: term })
那应该更新上下文“searchTerm”,但没有做到。在上面的源代码中,loadmore 保存了设置的初始值!
单独的示例具有类似的逻辑,可以正常工作(https://codesandbox.io/s/trusting-booth-1w40e?fontsize=14&hidenavigation=1&theme=dark)
希望尽快用解决方案更新上述问题,如果有人发现问题,请告诉我!
解决方案
代码沙盒链接有效,但在创建和使用context
.
在提供的代码中,您创建了两个单独的提供程序。一个具有 state 的值,一个具有 dispatch 的值。
<GlobalStateContext.Provider value={state}>
<GlobalDispatchContext.Provider value={dispatch}>
但是,codesandbox 使用两者state
并dispatch
在同一个provider
.
<Application.Provider value={{ state, dispatch }}>
而且它似乎GlobalContextProvider
是出口的,但我不确定它是否用于包装任何消费者。
由于 和 是分开的dispatch
,state
我将把它用于我提出的解决方案。
该实现似乎是正确的,但在我看来,您可以更进一步并创建两个自定义钩子,它们仅公开一种提供上下文值的方式和一种使用它的方式。
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 };
我在这里添加的只是几个自定义钩子,它们公开了每个上下文,即GlobalStateContext
并将GlobalDispatchContext
它们与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
由于问题中提供的代码框缺少一些部分,因此我已将其重构为此概念的简化工作版本,希望能帮助解决您的问题。
当我遇到 Context API 和钩子问题时,我还发现这篇文章很有帮助。
它遵循相同的模式,我一直在生产中使用它并且对结果非常满意。
希望有帮助:)
推荐阅读
- flutter - 如何在颤振中使用地理定位器设置用户之间的距离
- html - CSS - 制作半圆和四分之一圆
- python - 我可以在初始化属性时传递属性的名称吗?
- r - 使用列表操作数据框
- zend-framework2 - Zend 2 转义单引号
- .net - 在 Onion 架构中使用 AutoMapper
- html - 如何在 Tableau 中从数据库表中呈现 html 代码
- python - 为什么 with 语句适用于 sqlite3 但不适用于 mysql.connection?
- c# - ASPNET Owin 登录 - 重定向到 Azure 前门而不是后门
- laravel - Laravel 数据表原始列