score:-1

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:

Wizard.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

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

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

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?

here is a working solution (without the hook):

https://codesandbox.io/embed/wizardwitcontext-working-3lxhd

score:0

Context is derived from the nearest provider above the component tree. From the react docs.

const value = useContext(MyContext);

Accepts a context object (the value returned from React.createContext) and returns the current context value for that context. The current context value is determined by the value prop of the nearest above the calling component in the tree.

In this case you have 2 options.

1.You need to wrap your App (index.js) component in the provider.

or

2.Let the Wizard component be the provider and try to use useContext hook in the child components.

Demo: https://stackblitz.com/edit/react-msac8q

Hope this helps

score:0

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

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

score:5

You calling useContext in the same level as the Context.Provider:

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.

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.


Related Query

More Query from same tag