为什么无法将状态的获取器/设置器存储在全局上下文中?

huangapple go评论114阅读模式
英文:

Why can't I store a state getter/setter in a global context?

问题

以下是您要翻译的内容:

  • Page1 使用 useState 创建了一个名为 "num" 的状态(初始值为 0),并将其 getter/setter 存储在 App 中的上下文中。
  • Page2 通过上下文访问 num 的 setter,并将 num 设置为 1。
  • Page3 通过上下文访问 num 的值并显示它。

我期望 Page3 显示 num=1,但实际上它显示的是 num=0。似乎 Page2 上的 setNum 调用并不按照我期望的方式工作。

英文:

Can anyone tell me why this won't work?

  • Page1 creates a state "num" (initialized to 0) with useState and stores the getter/setter in a context held in App
  • Page2 accesses the num setter through context and sets num to 1
  • Page3 accesses the num value through context and displays it

I expect Page3 to display num=1, but in fact it displays num=0. It seems that the setNum call on Page2 is not working as I expect it to.

Downloadable example here:

App.js

import { SharedContext } from './context';

function App() {
  const [context, setContext] = useState(null);

  return (
    <BrowserRouter>
      <SharedContext.Provider value={{ context, setContext }}>
        <Routes>
          <Route path="/" element={<Page1 />} />
          <Route path="/page2" element={<Page2 />} />
          <Route path="/page3" element={<Page3 />} />
        </Routes>
      </SharedContext.Provider>
    </BrowserRouter>
  );
}

Page1.js

export default function Page1() {
    const navigate = useNavigate();
    const { setContext } = useContext(SharedContext);
    const numState = useState(0);

    useEffect(() => {
        setContext(numState);
    }, []);

    const onClick = () => {
        navigate('/page2');
    };

    return <button onClick={onClick}>Next</button>;
}

Page2.js

export default function Page2() {
    const navigate = useNavigate();
    const { context } = useContext(SharedContext);
    const [num, setNum] = context;

    const onClick = () => {
        setNum(1);
        navigate('/page3');
    };

    return <button onClick={onClick}>Next</button>;
}

Page3.js

export default function Page3() {
    const { context } = useContext(SharedContext);
    const [num, setNum] = context;

    return <div>num = {num}</div>;
}

答案1

得分: 1

Edit: 看起来你在访问上下文 API 中的状态时遇到了一些问题。友情提醒,只有在 Page1.js 被渲染后状态才会被定义。但不用担心!我有一个解决方案供你尝试,它应该能完成任务 💪

context.js

import { createContext, useState } from 'react';

export const SharedContext = createContext({
    num: null,
    setNum: () => {}
});

export const SharedContextProvider = ({ children }) => {
    const [num, setNum] = useState(null);
    
    return <SharedContext.Provider value={{num, setNum}}>{children}</SharedContext.Provider>;
}

App.js

import { SharedContext, SharedContextProvider } from './context';

function App() {
  return (
    <BrowserRouter>
      <SharedContextProvider>
        <Routes>
          <Route path="/" element={<Page1 />} />
          <Route path="/page2" element={<Page2 />} />
          <Route path="/page3" element={<Page3 />} />
        </Routes>
      </SharedContextProvider>
    </BrowserRouter>
  );
}

Page1.js

export default function Page1() {
    const navigate = useNavigate();
    const { setNum } = useContext(SharedContext);

    useEffect(() => {
        setNum(0);
    }, []);

    const onClick = () => {
        navigate('/page2');
    };

    return <button onClick={onClick}>Next</button>;
}

Page2.js

export default function Page2() {
    const navigate = useNavigate();
    const { setNum } = useContext(SharedContext);

    const onClick = () => {
        setNum(1);
        navigate('/page3');
    };

    return <button onClick={onClick}>Next</button>;
}

Page3.js

export default function Page3() {
    const { num, setNum } = useContext(SharedContext);

    return <div>num = {num}</div>;
}

另外,我想提醒你,使用 React.JS 上下文 API 时,请确保为 createContext 函数内部使用的属性声明默认值。这将有助于在尝试访问状态时避免潜在问题。祝你有一个美好的一天! 💻🔥

import { createContext } from 'react';

export const SharedContext = createContext({
    context: null,
    setContext: () => {};
});

createContext 的文档

英文:

Edit: Looks like you're running into some trouble accessing the state in the context API. Just a friendly reminder that the state will only be defined once Page1.js is rendered. But don't worry! I've got a solution for you. Give this implementation a try and it should get the job done 💪

context.js

import { createContext, useState } from &#39;react&#39;;

export const SharedContext = createContext({
    num: null,
    setNum: () =&gt; {}
});

export const SharedContextProvider = ({ children }) =&gt; {
    const [num, setNum] = useState(null);
    
    return &lt;SharedContext.Provider value={{num, setNum}}&gt;{children}&lt;/SharedContext.Provider&gt;
}

App.js

import { SharedContext, SharedContextProvider } from &#39;./context&#39;;

function App() {
  return (
    &lt;BrowserRouter&gt;
      &lt;SharedContextProvider&gt;
        &lt;Routes&gt;
          &lt;Route path=&quot;/&quot; element={&lt;Page1 /&gt;} /&gt;
          &lt;Route path=&quot;/page2&quot; element={&lt;Page2 /&gt;} /&gt;
          &lt;Route path=&quot;/page3&quot; element={&lt;Page3 /&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/SharedContextProvider&gt;
    &lt;/BrowserRouter&gt;
  );
}

Page1.js

export default function Page1() {
    const navigate = useNavigate();
    const { setNum } = useContext(SharedContext);

    useEffect(() =&gt; {
        setNum(0);
    }, []);

    const onClick = () =&gt; {
        navigate(&#39;/page2&#39;);
    };

    return &lt;button onClick={onClick}&gt;Next&lt;/button&gt;;
}

Page2.js

export default function Page2() {
    const navigate = useNavigate();
    const { setNum } = useContext(SharedContext);

    const onClick = () =&gt; {
        setNum(1);
        navigate(&#39;/page3&#39;);
    };

    return &lt;button onClick={onClick}&gt;Next&lt;/button&gt;;
}

Page3.js

export default function Page3() {
    const { num, setNum } = useContext(SharedContext);

    return &lt;div&gt;num = {num}&lt;/div&gt;;
}

Also, Just wanted to give you a friendly reminder that when using the React.JS Context API, make sure to declare default values for the properties you're using inside the createContext function. This will help avoid any potential issues when trying to access the state. Have a great day! 💻🔥

import { createContext } from &#39;react&#39;;

export const SharedContext = createContext({
    context: null,
    setContext: () =&gt; {};
});

Docs on createContext

答案2

得分: 1

以下是代码部分的翻译:

context.js

import { createContext } from "react";

export const SharedContext = createContext();

app.js

import { useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Page1 from "./Page1";
import Page2 from "./Page2";
import Page3 from "./Page3";
import { SharedContext } from "./context";

function App() {
  const [state, setState] = useState({ num: 0 });

  return (
    <BrowserRouter>
      <SharedContext.Provider value={{ state, setState }}>
        <Routes>
          <Route path="/" element={<Page1 />} />
          <Route path="/page2" element={<Page2 />} />
          <Route path="/page3" element={<Page3 />} />
        </Routes>
      </SharedContext.Provider>
    </BrowserRouter>
  );
}

export default App;

Page1.js

import { useNavigate } from "react-router-dom";

export default function Page1() {
  const navigate = useNavigate();

  const onClick = () => {
    navigate("/page2");
  };

  return <button onClick={onClick}>Next</button>;
}

Page2.js

import { useContext } from "react";
import { useNavigate } from "react-router-dom";
import { SharedContext } from "./context";

export default function Page2() {
  const navigate = useNavigate();
  const context = useContext(SharedContext);

  const onClick = () => {
    context.setState({
      ...context.state,
      num: 1,
    });
    navigate("/page3");
  };

  return <button onClick={onClick}>Next</button>;
}

Page3.js

import { useContext } from "react";
import { SharedContext } from "./context";

export default function Page3() {
  const context = useContext(SharedContext);

  return <div>num = {context?.state?.num}</div>;
}

请注意,上述内容只是代码的翻译,不包含其他信息。

英文:

The core issue is you are trying to set a state setter inside of a state setter on a global context. The much simpler way to do it is:

context.js

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { createContext } from &quot;react&quot;;

export const SharedContext = createContext();

<!-- end snippet -->

app.js

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { useState } from &quot;react&quot;;
import { BrowserRouter, Routes, Route } from &quot;react-router-dom&quot;;
import Page1 from &quot;./Page1&quot;;
import Page2 from &quot;./Page2&quot;;
import Page3 from &quot;./Page3&quot;;
import { SharedContext } from &quot;./context&quot;;

function App() {
  const [state, setState] = useState({ num: 0 });

  return (
    &lt;BrowserRouter&gt;
      &lt;SharedContext.Provider value={{ state, setState }}&gt;
        &lt;Routes&gt;
          &lt;Route path=&quot;/&quot; element={&lt;Page1 /&gt;} /&gt;
          &lt;Route path=&quot;/page2&quot; element={&lt;Page2 /&gt;} /&gt;
          &lt;Route path=&quot;/page3&quot; element={&lt;Page3 /&gt;} /&gt;
        &lt;/Routes&gt;
      &lt;/SharedContext.Provider&gt;
    &lt;/BrowserRouter&gt;
  );
}

export default App;

<!-- end snippet -->

Page1.js

Notice the removal of the unnecessary useState.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { useNavigate } from &quot;react-router-dom&quot;;

export default function Page1() {
  const navigate = useNavigate();

  const onClick = () =&gt; {
    navigate(&quot;/page2&quot;);
  };

  return &lt;button onClick={onClick}&gt;Next&lt;/button&gt;;
}

<!-- end snippet -->

Page2.js

Notice how we use one global state setter, and not a nested setter setter? You set the global state and destructure the previous state so you don't lose any other state variables that are not num.

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { useContext } from &quot;react&quot;;
import { useNavigate } from &quot;react-router-dom&quot;;
import { SharedContext } from &quot;./context&quot;;

export default function Page2() {
  const navigate = useNavigate();
  const context = useContext(SharedContext);

  const onClick = () =&gt; {
    context.setState({
      ...context.state,
      num: 1,
    });
    navigate(&quot;/page3&quot;);
  };

  return &lt;button onClick={onClick}&gt;Next&lt;/button&gt;;
}

<!-- end snippet -->

Page3.js

<!-- begin snippet: js hide: false console: true babel: false -->

<!-- language: lang-js -->

import { useContext } from &quot;react&quot;;
import { SharedContext } from &quot;./context&quot;;

export default function Page3() {
  const context = useContext(SharedContext);

  return &lt;div&gt;num = {context?.state?.num}&lt;/div&gt;;
}

<!-- end snippet -->

BTW, using your code as-is in a sandbox, I see several errors thrown, because in several areas you are trying to destructure { setContext } when the parent object is initially undefined - which is why Typescript users here are recommending you set the state values initially. It actually does matter.

Slava and other users made good points.

Working Sandbox

https://codesandbox.io/p/sandbox/trusting-neco-svgp1z

I will provide a Typescript-version along with more proof as to why it's incredibly important to define a context initially, OR be prepared to handle for undefined in your code with ? checks which is not ideal.

为什么无法将状态的获取器/设置器存储在全局上下文中?

答案3

得分: -2

你访问上下文的方式是不正确的,因为你在模仿useState的模式,但这不是你要传递给上下文提供者的内容。

Page1 需要修正:

const [numState, setNumState] = useState(0);

useEffect(() => {
    setContext(numState);
}, []);

Page2 应该是:

const { setContext } = useContext(SharedContext);

const onClick = () => {
    setContext(1);
    navigate('/page3');
};

Page3 应该是:

const { context } = useContext(SharedContext);

return <div>num = {context}</div>;

你目前的做法看起来像:

// "context"字段是你设置的值,是一个数字。
const { context } = useContext(SharedContext);
// 这里的`num`和`setNum`是没有意义的。
const [num, setNum] = context;
英文:

The way you access your context is incorrect, because you are mirroring the useState pattern while that's not what you want to pass into the context provider.

Page1 is needs a fix:

const [numState, setNumState] = useState(0);

useEffect(() =&gt; {
    setContext(numState);
}, []);

Page2 should be:

const { setContext } = useContext(SharedContext);

const onClick = () =&gt; {
    setContext(1);
    navigate(&#39;/page3&#39;);
};

Page3 should be:

const { context } = useContext(SharedContext);

return &lt;div&gt;num = {context}&lt;/div&gt;;

You are currently doing a pattern that looks like:

// The &quot;context&quot; field is the value that you set. It is a number.
const { context } = useContext(SharedContext);
// `num` and `setNum` here are non-sensical
const [num, setNum] = context;

huangapple
  • 本文由 发表于 2023年2月9日 01:39:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/75389713.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定