简体   繁体   English

如何在同一个组件中使用 react useContext 来显示上下文中的数据

[英]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 .参考上下文 APIuseContext

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.

相关问题 我无法在 React 的 useContext 挂钩中访问 Context Provider - I can't access the Context Provider in useContext hook in React 在React.js的父组件中使用react-router时,如何使用react context API将数据从父组件传递到子组件? - How do I use react context API to pass data from parent component to child component when using react-router in the parent component in React.js? 如何在旧的反应组件中使用上下文? - how to useContext in old react component? 如何在同一页面上的多个实例上使用React组件? - How can I use a React component on multiple instances on the same page? 我如何从 function 组件传递 state 以便我可以在 use-Context 中使用它 - How can I pass a state from a function component so i can use it in the use-Context 如何使用这个 React 组件来收集表单数据? - How can I use this React component to collect form data? 我可以在组件内部使用相同的组件吗? - Can i use the same component inside component in react? 如何使用 React 将数据从列表传递到另一个组件? - How can I pass a data to another component from a list with React? 我在 React 组件上进行了一个 API 调用,从中获取数据并将其传递给一个状态。 我也想在另一个组件上使用相同的数据 - I made an API call fetching data from it on a React component and passed it on to a state. I want to use the same data on another component too 如何将数据从 React 组件传递到主文件? - How can I pass data from a React component to main file?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM