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

时间:2019-12-02 18:50:57

标签: reactjs react-hooks react-context

在随后的任何调用之后,通过“调度程序”设置为“搜索项”的第一个值仍然存在,我试图找出原因或错误所在。

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

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

上下文提供者看起来像:

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的依赖关系;此值是通过“分发程序”设置的,并通过Consumer中的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 })

该应更新上下文“ searchTerm”,但无法执行。在上面的源代码中,loadmore保存了设置的初始值!

单独的示例具有类似的逻辑,可以正常工作(https://codesandbox.io/s/trusting-booth-1w40e?fontsize=14&hidenavigation=1&theme=dark

希望尽快用解决方案更新上述问题,以防万一有人发现该问题,请告诉我!

1 个答案:

答案 0 :(得分:1)

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

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

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

codesandbox在同一state中同时使用dispatchprovider

 <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

由于问题中提供的代码和框中缺少一些内容,因此我将其重构为该概念here的简化工作版本,希望可以帮助您解决问题。

当我对Context API和挂钩有问题时,我还发现this article很有帮助。

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

希望有帮助:)

相关问题