简体   繁体   English

反应上下文不更新

[英]React context not updating

I have set a basic sample project that use Context to store the page title, but when I set it the component is not rerendered.我已经设置了一个使用 Context 存储页面标题的基本示例项目,但是当我设置它时,组件不会重新呈现。

Principal files:主要文件:

Context.js上下文.js

import React from 'react'

const Context = React.createContext({})

export default Context

AppWrapper.js 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应用程序.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主页.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完整代码

What am I doing wrong?我究竟做错了什么?

Think about React context just like you would a component, if you want to update a value and show it then you need to use state .像对待组件一样考虑 React 上下文,如果你想更新一个值并显示它,那么你需要使用state In this case you're AppWrapper where you render the context provider is where you need to track 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>
  )
}

Edit - Updated working solution from linked repository编辑 - 从链接存储库更新工作解决方案

I renamed a few things to be a bit more specific, I wouldn't recommend passing setState through the context as that can be confusing and conflicting with a local state in a component.我重命名了一些更具体的东西,我不建议通过上下文传递 setState ,因为这可能会与组件中的本地状态混淆和冲突。 Also i'm omitting chunks of code that aren't necessary to the answer, just the parts I changed此外,我省略了答案中不必要的代码块,只省略了我更改的部分

src/AppContext.js源代码/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源代码/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源代码/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 src/components/DocumentMeta.js

this is a new component that you can use to update your page names in a declarative style reducing the code complexity/redundancy in each view这是一个新组件,您可以使用它以声明式样式更新页面名称,从而降低每个视图中的代码复杂性/冗余

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 usage would be something like <DocumentMeta title="Whatever Title I Want Here" />又名用法类似于<DocumentMeta title="Whatever Title I Want Here" />


src/pages/Home.js源代码/页面/Home.js

each view now just needs to import DocumentMeta and the Pages "enum" to update the title, instead of pulling the context in and manually doing it each time.每个视图现在只需要导入 DocumentMeta 和 Pages“enum”来更新标题,而不是每次都拉入上下文并手动执行。

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

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

Note: The other pages need to replicate what the home page is doing注意:其他页面需要复制主页正在做的事情

Remember this isn't how I would do this in a production environment, I'd write up a more generic helper to write data to your cache that can do more things in terms of performance, deep merging.. etc. But this should be a good starting point.请记住,这不是我在生产环境中执行此操作的方式,我会编写一个更通用的帮助程序将数据写入您的缓存,它可以在性能、深度合并等方面做更多的事情。但这应该是一个很好的起点。

Here is a working version of what you need.这是您需要的工作版本。

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>;
}

编辑 long-lake-ct7yu

Read more on Hooks API Reference .Hooks API 参考上阅读更多内容。

You may put useContext(yourContext) at wrong place.您可能将useContext(yourContext)放在错误的位置。

The right position is inner the <Context.Provider> :正确的位置在<Context.Provider>内部:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM