[英]How can I use react useContext , to show data from context, in the same component
I am creating a react wizard component, and want to pass data נetween parent and children using contextץ我正在创建一个反应向导组件,并希望使用上下文在父母和孩子之间传递数据
So I created a wizard, context, provider, and custom hook, but the issue is that if I try to use the context, on the wizard component, it does not show the correct info所以我创建了一个向导、上下文、提供程序和自定义挂钩,但问题是如果我尝试在向导组件上使用上下文,它不会显示正确的信息
(see https://codesandbox.io/embed/wizardwitcontext-rfpui ) (见https://codesandbox.io/embed/wizardwitcontext-rfpui )
How to make it so that I can rely on data in context on the wizard itself so I can transfer the login to the custom hook?如何做到这一点,以便我可以依赖向导本身的上下文中的数据,以便我可以将登录名转移到自定义挂钩?
useWizard.js:使用向导.js:
import React, { useContext, useEffect } from "react";
import { WizardContext } from "./WizardContext";
const useWizard = () => {
const [state, setState] = useContext(WizardContext);
function setMaxSteps(maxSteps) {
setState(state => ({ ...state, maxSteps }));
}
function moveToStep(index) {
if (state.maxSteps && state.maxSteps > index) {
setState({ ...state, currentStep: index });
return index;
}
return state.currentStep;
}
function back() {
if (state.maxSteps) {
if (state.currentStep > 0) {
setState({ ...state, currentStep: state.currentStep - 1 });
window.scrollTo(0, 0);
}
}
}
//move back a step
function next() {
if (state.currentStep < state.maxSteps) {
setState({ ...state, currentStep: state.currentStep + 1 });
window.scrollTo(0, 0);
}
}
return {
setMaxSteps,
moveToStep,
back,
next,
maxSteps: state.maxSteps,
currentStep: state.currentStep,
state
};
};
export default useWizard;
Wizard.jsx:向导.jsx:
const { state, currentStep, back, next, maxSteps, setMaxSteps } = useWizard();
return (
<div className="wizard">
<WizardProvider
maxSteps={React.Children.count(props.children)}
currentStep={0}
>
{/* <div className="wizard__upper">
<ProgressIndicator currentIndex={selected} onChange={onClick}>
{steps}
</ProgressIndicator>
<Button id="wizardCloseBtn" kind="ghost" onClick={onClose}>
<Icon icon={iconHeaderClose} />
</Button>
</div> */}
<div className="wizard__separator" />
<div className="wizard__content">
{`in wizard: cur=${currentStep}, max=${maxSteps}`}
{/* {getContentAt(0)} */}
{stepContentWithProps}
</div>
{/* <div className="wizard__buttons">
{showBack && (
<Link id="back" onClick={back}>
back
</Link>
)}
{showNext && (
<button id="next" onClick={next} kind="secondary">
Next Step
</button>
)}
</div> */}
</WizardProvider>
</div>
);
Step2:第2步:
import React, { useState, useContext, useEffect } from "react";
import useWizard from "./useWizard";
function Step2(props) {
const {
currentStep,
moveToStep,
maxSteps,
setMaxSteps,
next,
prev
} = useWizard();
return (
<div>
<p>Step 2</p>
{`in step2 (inner child of wizard): cur=${currentStep} see that cur !== cur from wizard above`}
<br />
<button onClick={() => moveToStep(1)}>
Click me to change current step
</button>
</div>
);
}
export default Step2;
End result is:最终结果是:
in wizard: cur=undefined, max=undefined
p1
in index.js: cur=undefined
Step 2
in step2 (inner child of wizard): cur=0 see that cur !== cur from wizard above
You calling useContext
in the same level as the Context.Provider
:您在与
Context.Provider
相同的级别调用useContext
:
function Wizard(props) {
// useWizard calls useContext
const { state, currentStep, back, next, maxSteps, setMaxSteps } = useWizard();
return (
<div className="wizard">
<WizardProvider
maxSteps={React.Children.count(props.children)}
currentStep={0}
>
<div className="wizard__content">
{`in wizard: cur=${currentStep}, max=${maxSteps}`}
</div>
</WizardProvider>
</div>
);
}
You need to change your structure and call useContext
within the Provider
children.您需要更改结构并在
Provider
子级中调用useContext
。
function Wizard(props) {
// useWizard calls useContext
const { state, currentStep, back, next, maxSteps, setMaxSteps } = useWizard();
return (
// v You trying to get Provider's value here
<div className="wizard">
<WizardProvider
maxSteps={React.Children.count(props.children)}
currentStep={0}
>
// v useContext available within the children
<ComponentA />
<ComponentB />
</WizardProvider>
</div>
);
}
Refer to Context API , useContext
.参考上下文 API ,
useContext
。
Context is derived from the nearest provider above the component tree.上下文来自组件树上方最近的提供者。 From the react docs.
从反应文档。
const value = useContext(MyContext);
常量值 = useContext(MyContext);
Accepts a context object (the value returned from React.createContext) and returns the current context value for that context.
接受上下文 object(从 React.createContext 返回的值)并返回该上下文的当前上下文值。 The current context value is determined by the value prop of the nearest above the calling component in the tree.
当前上下文值由树中调用组件上方最近的值 prop 确定。
In this case you have 2 options.在这种情况下,您有 2 个选项。
1.You need to wrap your App (index.js) component in the provider. 1.您需要将您的应用程序(index.js)组件包装在提供程序中。
or或者
2.Let the Wizard component be the provider and try to use useContext hook in the child components. 2.让Wizard组件成为provider,并尝试在子组件中使用useContext hook。
Demo: https://stackblitz.com/edit/react-msac8q演示: https://stackblitz.com/edit/react-msac8q
Hope this helps希望这可以帮助
I think Since we can not use useContext() in the same Component as Provider, I think we can do something workaround, I think this will be helpful to use in the Main Components like pages/screens我认为由于我们不能在与 Provider 相同的组件中使用 useContext(),我认为我们可以做一些解决方法,我认为这将有助于在页面/屏幕等主要组件中使用
// This will be your child component
function Wizard(props) {
// useWizard calls useContext
const { state, currentStep, back, next, maxSteps, setMaxSteps } = useWizard();
return (
<div className="wizard">
<div className="wizard__content">
{`in wizard: cur=${currentStep}, max=${maxSteps}`}
</div>
</div>
);
}
// This is your main Page
export default function WizardPage(){
return <WizardProvider
maxSteps={React.Children.count(props.children)}
currentStep={0}>
<Wizard />
</WizardProvider>
}
I Found the solution, thanks to this article: https://dev.to/email2vimalraj/react-hooks-lift-up--pass-down-state-using-usecontext-and-usereducer-5ai0 The solution as described is to create a reducer on the wizard file, and so the wizard has access to its data, and also the childern:我找到了解决方案,感谢这篇文章: https://dev.to/email2vimalraj/react-hooks-lift-up--pass-down-state-using-usecontext-and-usereducer-5ai0描述的解决方案是在向导文件上创建一个 reducer,因此向导可以访问其数据,也可以访问子节点:
Wizard.jsx向导.jsx
import React, {
useState,
useEffect,
useLayoutEffect,
useContext,
useReducer
} from "react";
import PropTypes from "prop-types";
import "./wizard.scss";
import {
WizardContext,
wizardReducer,
SET_CURRENT_STEP,
SET_MAX_STEPS,
BACK,
NEXT
} from "./WizardContext";
function StepContent(props) {
const { selected, children, ...other } = props;
return (
<li {...other} selected={selected}>
{children}
</li>
);
}
function Wizard(props) {
const { onClose, onChange, pageContentClassName } = props;
function onClick(index) {
dispatch({ type: SET_CURRENT_STEP, currentStep: index });
// setSelected(index);
}
//get the progressBar steps
const steps = React.Children.map(props.children, page => {
const { id, label, description } = page.props;
return <div id={id} label={label} description={description} />;
});
function getContentAt(index) {
return stepContentWithProps[index];
}
const stepsWithProps = React.Children.map(props.children, (step, index) => {
const newStep = React.cloneElement(step, {});
return newStep;
});
const stepContentWithProps = stepsWithProps.map((step, index) => {
const { children } = step.props;
return (
<StepContent key={index} className={pageContentClassName}>
{children}
</StepContent>
);
});
const initialState = {
maxSteps: React.Children.count(props.children),
currentStep: 0
};
const [wizardData, dispatch] = useReducer(wizardReducer, initialState);
return (
<div className="wizard">
<p>This text is in wizard: currentStep={wizardData.currentStep}</p>
<WizardContext.Provider value={{ wizardData, dispatch }}>
<div className="wizard__upper">
<ul currentIndex={wizardData.currentStep} onChange={onClick}>
{steps}
</ul>
</div>
<div className="wizard__separator" />
<div className="wizard__content">{stepsWithProps}</div>
<div>
<button onClick={() => dispatch({ type: BACK })}>Back</button>
<button onClick={() => dispatch({ type: NEXT })}>Next</button>
</div>
</WizardContext.Provider>
</div>
);
}
Wizard.propTypes = {
/**
* Specify the text to be read by screen-readers when visiting the <Tabs>
* component
*/
ariaLabel: PropTypes.string,
/**
* Pass in a collection of <Tab> children to be rendered depending on the
* currently selected tab
*/
children: PropTypes.node,
/**
* Provide a className that is applied to the <PageContent> components
*/
pageContentClassName: PropTypes.string
};
export default Wizard;
WizardContext.jsx WizardContext.jsx
import React, { createContext } from "react";
export const WizardContext = React.createContext(null);
export const SET_MAX_STEPS = "SET_MAX_STEPS";
export const SET_CURRENT_STEP = "SET_CURRENT_STEP";
export const BACK = "BACK";
export const NEXT = "NEXT";
export const SHOW_BACK = "SHOW_BACK";
export const SHOW_NEXT = "SHOW_NEXT";
export function wizardReducer(state, action) {
switch (action.type) {
case SET_MAX_STEPS:
return {
...state,
maxSteps: action.maxSteps
};
case SET_CURRENT_STEP:
if (action.currentStep >= state.maxSteps) return state;
return {
...state,
currentStep: action.currentStep
};
case BACK:
if (state.currentStep === 0) return state;
return {
...state,
currentStep: state.currentStep - 1
};
case NEXT:
if (state.currentStep >= state.maxSteps - 1) return state;
return {
...state,
currentStep: state.currentStep + 1
};
default:
return state;
}
}
Index.js索引.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import Wizard from "./Wizard";
import Cmp2 from "./Cmp2";
function App() {
const [wizardVisible, setWizardVisible] = useState(false);
return (
<div className="App">
<h1>
Wizard: why cant I see currentStep in wizard
<br />
(WORKING NOW!!!)
</h1>
<Wizard>
<div label="ddd">This is step1</div>
<Cmp2 />
<div label="ddd">This is step3</div>
</Wizard>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Cmp2.jsx Cmp2.jsx
import React, { useState, useContext, useEffect } from "react";
import { WizardContext, SET_CURRENT_STEP } from "./WizardContext";
function Cmp2(props) {
const { wizardData, dispatch } = useContext(WizardContext);
return (
<div>
<br />
<p>This is Step 2</p>
{`in step2 (inner child of wizard): cur=${wizardData.currentStep}`}
<br />
<button
onClick={() => dispatch({ type: SET_CURRENT_STEP, currentStep: 1 })}
>
Click me to change current step
</button>
<br />
<br />
</div>
);
}
export default Cmp2;
Now I need to find how to make it accessible, I mean, it works nice, but when I try to create a custom hook (which imports the Context), the context is null, when trying to use the custom hook (which is understandable, since it is called in wizard BEFORE the provider), how to add better functionality here?现在我需要找到如何使其可访问,我的意思是,它工作得很好,但是当我尝试创建一个自定义钩子(导入上下文)时,上下文是 null,当尝试使用自定义钩子时(这是可以理解的,因为它是在提供者之前在向导中调用的),如何在这里添加更好的功能?
here is a working solution (without the hook):这是一个可行的解决方案(没有钩子):
https://codesandbox.io/embed/wizardwitcontext-working-3lxhd https://codesandbox.io/embed/wizardwitcontext-working-3lxhd
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.