【问题标题】:React context not updating反应上下文不更新
【发布时间】:2020-06-27 01:54:06
【问题描述】:

我已经设置了一个基本的示例项目,它使用 Context 来存储页面标题,但是当我设置它时,组件不会重新呈现。

主要文件:

上下文.js

import React from 'react'

const Context = React.createContext({})

export default Context

AppWrapper.js

import React from 'react'
import App from './App'
import Context from './Context'

function AppWrapper () {
  return (
    <Context.Provider value={{page: {}}}>
      <App />
    </Context.Provider>
  )
}

export default AppWrapper

App.js

import React, { useContext } from 'react';
import Context from './Context';
import Home from './Home';

function App() {
  const { page } = useContext(Context)
  return (
    <>
      <h1>Title: {page.title}</h1>
      <Home />
    </>
  );
}

export default App;

Home.js

import React, { useContext } from 'react'
import Context from './Context'

function Home () {
  const { page } = useContext(Context)
  page.title = 'Home'

  return (
    <p>Hello, World!</p>
  )
}

export default Home

full code

我做错了什么?

【问题讨论】:

  • 很确定您需要将值放在state 中的App 组件中。当你想改变它时,你需要setState,如果你想让其他组件设置值&lt;Context.Provider value={{setContext: mySetter, {...state}}&gt;,你可以向下传递一个setter。
  • 你不能通过page.title = 'Home'改变page.title
  • @JohnRuddell 在 App 或 AppWrapper 中?我做了类似的事情,但后来在控制台中我反应抱怨孩子正在修改父组件 iirc 的状态
  • @kuroneko 渲染上下文提供者的地方。这是一个例子。 codesandbox.io/s/dreamy-darkness-vi76j如果你想让我把它写成答案,我会的:)
  • @JohnRuddell 是的,请

标签: javascript reactjs react-context


【解决方案1】:

像对待组件一样考虑 React 上下文,如果你想更新一个值并显示它,那么你需要使用state。在这种情况下,您呈现上下文提供程序的AppWrapper 是您需要跟踪状态的位置。

import React, {useContext, useState, useCallback, useEffect} from 'react'

const PageContext = React.createContext({})

function Home() {
  const {setPageContext, page} = useContext(PageContext)
  // essentially a componentDidMount
  useEffect(() => {
    if (page.title !== 'Home')
      setPageContext({title: 'Home'})
  }, [setPageContext])
  return <p>Hello, World!</p>
}

function App() {
  const {page} = useContext(PageContext)
  return (
    <>
      <h1>Title: {page.title}</h1>
      <Home />
    </>
  )
}

function AppWrapper() {
  const [state, setState] = useState({page: {}})
  const setPageContext = useCallback(
    newState => {
      setState({page: {...state.page, ...newState}})
    },
    [state, setState],
  )
  const getContextValue = useCallback(
    () => ({setPageContext, ...state}),
    [state, updateState],
  )
  return (
    <PageContext.Provider value={getContextValue()}>
      <App />
    </PageContext.Provider>
  )
}

编辑 - 从链接存储库更新工作解决方案

我重命名了一些更具体的东西,我不建议通过上下文传递 setState,因为这可能会与组件中的本地状态混淆和冲突。另外我省略了答案不需要的代码块,只是我更改的部分

src/AppContext.js

export const updatePageContext = (values = {}) => ({ page: values })
export const updateProductsContext = (values = {}) => ({ products: values })

export const Pages = {
  help: 'Help',
  home: 'Home',
  productsList: 'Products list',
  shoppingCart: 'Cart',
}

const AppContext = React.createContext({})

export default AppContext

src/AppWrapper.js

const getDefaultState = () => {
  // TODO rehydrate from persistent storage (localStorage.getItem(myLastSavedStateKey)) ?
  return {
    page: { title: 'Home' },
    products: {},
  }
}

function AppWrapper() {
  const [state, setState] = useState(getDefaultState())

  // here we only re-create setContext when its dependencies change ([state, setState])
  const setContext = useCallback(
    updates => {
      setState({ ...state, ...updates })
    },
    [state, setState],
  )

  // here context value is just returning an object, but only re-creating the object when its dependencies change ([state, setContext])
  const getContextValue = useCallback(
    () => ({
      ...state,
      setContext,
    }),
    [state, setContext],
  )
  return (
    <Context.Provider value={getContextValue()}>
      ...

src/App.js

...
import AppContext, { updateProductsContext } from './AppContext'

function App() {
  const [openDrawer, setOpenDrawer] = useState(false)
  const classes = useStyles()
  const {
    page: { title },
    setContext,
  } = useContext(Context)

  useEffect(() => {
    fetch(...)
      .then(...)
      .then(items => {
        setContext(updateProductsContext({ items }))
      })
  }, [])

src/components/DocumentMeta.js

这是一个新组件,您可以使用它以声明式样式更新页面名称,从而减少每个视图中的代码复杂性/冗余

import React, { useContext, useEffect } from 'react'
import Context, { updatePageContext } from '../Context'

export default function DocumentMeta({ title }) {
  const { page, setContext } = useContext(Context)

  useEffect(() => {
    if (page.title !== title) {
      // TODO use this todo as a marker to also update the actual document title so the browser tab name changes to reflect the current view
      setContext(updatePageContext({ title }))
    }
  }, [title, page, setContext])
  return null
}

aka 用法类似于&lt;DocumentMeta title="Whatever Title I Want Here" /&gt;


src/pages/Home.js

现在每个视图只需要导入 DocumentMeta 和 Pages“枚举”来更新标题,而不是每次都将上下文拉入并手动执行。

import { Pages } from '../Context'
import DocumentMeta from '../components/DocumentMeta'

function Home() {
  return (
    <>
      <DocumentMeta title={Pages.home} />
      <h1>WIP</h1>
    </>
  )
}

注意:其他页面需要复制主页正在做的事情

请记住,这不是我在生产环境中执行此操作的方式,我会编写一个更通用的帮助程序来将数据写入缓存,它可以在性能、深度合并等方面做更多的事情。但是这应该是一个很好的起点。

【讨论】:

  • 如果我在我的上下文中有更多的值会是相似的吗?喜欢页面和产品?
  • 我的意思是,我的基本示例在其上下文中只有一个值,但在我的实际项目中,我有各种各样的,我不确定如何使用具有多个值的 useCallback
  • 顺便说一句,@strdr4605 的解决方案看起来更容易,但我想知道你的解决方案是否更正确
  • 能否请您更新您的示例以获取包含多个字段的上下文?比如 page.title 和 page.subtitle。谢谢
  • @kuroneko 啊,所以它不仅仅是页面信息,好吧,你可以调整它来满足你的需要,我试图让更新页面数据变得更容易,这样你不必每次更新都包含page: { 嵌套。在这里使用您的最佳判断来判断在您的用例中有效的方法。这些答案不应该直接复制到您的代码中,而是如何解决您的特定问题的指南。
【解决方案2】:

这是您需要的工作版本。

import React, { useState, useContext, useEffect } from "react";
import "./styles.css";

const Context = React.createContext({});

export default function AppWrapper() {
  // creating a local state
  const [state, setState] = useState({ page: {} });

  return (
    <Context.Provider value={{ state, setState }}> {/* passing state to in provider */}
      <App />
    </Context.Provider>
  );
}

function App() {
  // getting the state from Context
  const { state } = useContext(Context);
  return (
    <>
      <h1>Title: {state.page.title}</h1>
      <Home />
    </>
  );
}

function Home() {
  // getting setter function from Context
  const { setState } = useContext(Context);
  useEffect(() => {
    setState({ page: { title: "Home" } });
  }, [setState]);

  return <p>Hello, World!</p>;
}

阅读更多Hooks API Reference

【讨论】:

    【解决方案3】:

    你可能把useContext(yourContext)放错地方了。

    正确的位置在&lt;Context.Provider&gt;内部:

    // Right: context value will update
    <Context.Provider>
      <yourComponentNeedContext />
    </Context.Provider>
    
    // Bad: context value will NOT update
    <yourComponentNeedContext />
    <Context.Provider>
    </Context.Provider>
    

    【讨论】:

      猜你喜欢
      • 2023-03-08
      • 2021-09-18
      • 2023-01-17
      • 2021-10-26
      • 1970-01-01
      • 2020-08-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多