1

I am using reactjs 16.

I am making a wizard and I am trying to design it in away that I can reuse it anywhere on my site I need it.

Here is my code

export default class App extends Component {
  constructor(props) {
    super(props);
  }

  render() {

    const steps = [ {name: 'Step 1', component:   <Step1 /> }, 
                    {name: 'Step 2', component:   <Step2 /> }];
    return (
      <React.Fragment>
            <WizardComponent  steps={steps}/>
      </React.Fragment>
    );
  }
}



import React, { Component } from "react";
import ReactDOM from "react-dom";


import WizardStepCompoent from "./WizardStepCompoent";


export default class WizardComponent extends Component {
  constructor(props) {
    super();
    this.state = {
      currentStep: 1
    };
  }
  next(e) {
    // need to update the current step look 
    // const step =  this.props.steps.find(x => x.name == "Step 1")
    // step.changeToCompleted() ----> go form {name: "Step 1",  component: <Step 1/>} to {name: "Step 1",  component: <Step 1/>, isCompleted: true}
  }
  render() {
    const props = this.props;
    return (
      <div>
        <div className="steps">
          {props.steps.map(step => {
            return <WizardStepCompoent step={step} />;
          })}
        </div>
         {props.steps.map(step => {
            return step.component; // later on use current state to figure out which one to render ie if currentStep == 1 then only render the 1st component 
          })} 


        <button onClick={ (e) => this.next(e) }>Next</button>
      </div>
    );
  }
}


export default class WizardStepCompoent extends Component {
    render() {
      const props = this.props;
      const step = props.step;
      var completed = "";
      if(step.isCompleted)
      {
          completed = "step-item is-completed";
      } else {
            completed = "step-item";
      }
      return (
        <div className={completed}>      
          <div className="step-marker">
            <span className="icon">
              <i className="fa fa-check" />
            </span>
          </div>
          <div className="step-details">
            <p className="step-title">{step.name}</p>
            <p>This is the first step of the process.</p>
          </div>
        </div>
      );
    }
chobo2
  • 83,322
  • 195
  • 530
  • 832
  • Possible duplicate of [How to pass props to {this.props.children}](https://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children) – John Ruddell Jun 07 '18 at 22:57
  • since you arent using redux, use the state of the component that is rendering all of this.. store "props" for each wizard in the state, update the state and pass that to each wizard – John Ruddell Jun 07 '18 at 23:06
  • I am not following how to I store props for each wizard in the state. Are you saying to loop through each prop and put it in the WizardStep state? If so that is sort of my questions of where do I put that code and what does it look like. – chobo2 Jun 07 '18 at 23:08
  • no, right now you are rendering the wizards yes? `;` so what you should do is use the state of this component that is rendering the wizard to pass the completed flag. aka `const wizProps = this.state.wizards[step.name]; return ;` – John Ruddell Jun 07 '18 at 23:14
  • where wizards is a map keyd by soemthing specific for each wizard, and the value is an object that would be the "props" you want to pass to that wizard – John Ruddell Jun 07 '18 at 23:15
  • Yes the Wizard is being rendered inside another component(that represents a page) . There is only 1 of those though, it takes an array in that each individual step in the wizard, when someone hits next I want to go to the next step and mark it as complete(so then the little progress bar I have can get updated(which is in the WizardStepComponent). I don't want any code in that Page component changing the flag, I more want it all to live now in the WizardComponent/WizardStepComponent. – chobo2 Jun 07 '18 at 23:18
  • I'm sorry its not going to be easy for me to help you without knowing more of the situation. can you setup a codepen or something and point out where your issue is? seems like i dont understand the whole situation – John Ruddell Jun 07 '18 at 23:19
  • I can try to set one up. But I am using this extension for bulma: https://wikiki.github.io/components/steps/. Each of the circles is generated in WizardStepCompoent. Now when the next button, I need to go into update that step to say it is completed(ie just put a class on it). This is what I am trying to do. I am trying to add a property onto each of the "steps" to have another flag so that when changed it will go back and rerender it and add that class. – chobo2 Jun 07 '18 at 23:24
  • @JohnRuddell - The Bulmia extension css is not hosted anywhere so I just posted my complete code in my OP with comments. Hopefully it will be clearer. – chobo2 Jun 07 '18 at 23:35
  • ok, and what other props do you want to send to the component? just `completed` ? – John Ruddell Jun 07 '18 at 23:44
  • do you have any questions or issues with my answer? I'd be happy to explain more if needed – John Ruddell Jun 08 '18 at 20:13

1 Answers1

2

To solve what you are doing you need to clone the element and pass the appropriate props to it. here is an example in a codepen to see it live

class App extends React.Component {
  render() {
    const steps = [ {id: 'first', name: 'Step 1', component:   <Step1 /> }, 
                    {id: 'second', name: 'Step 2', component:   <Step2 /> }];
    return (
      <div>
            <WizardComponent  steps={steps}/>
      </div>
    );
  }
}

class WizardComponent extends React.Component {
  constructor(props) {
    super();
    this.state = {
      currentStep: 0,
      completedSteps: {
        'first': {completed: false},
        'second': {completed: false}
      }
    };
  }
  next(e) {
    const {currentStep} = this.state
    const completedSteps = {...this.state.completedSteps}
    const current = this.props.steps[currentStep]
    completedSteps[current.id].completed = true
    this.setState({currentStep: this.state.currentStep + 1, completedSteps})
  }
  render() {
    const props = this.props;
    const {currentStep, completedSteps} = this.state
    return (
      <div>
        <div className="steps">
          {props.steps.map(step => {
            return <WizardStepCompoent step={step} />;
          })}
        </div>
         {props.steps.map((step, i) => {
            return React.cloneElement(step.component, completedSteps[step.id]); // later on use current state to figure out which one to render ie if currentStep == 1 then only render the 1st component 
          })} 


        <button onClick={ (e) => this.next(e) }>Next</button>
      </div>
    );
  }
}

the meat of what is happening is at the clone and also when you press next. I added to your steps an id as a way to cross reference each step, then you lookup the current step by its id and set a completed to true. This is a fairly basic example so please bear with me on that.

John Ruddell
  • 25,283
  • 6
  • 57
  • 86
  • Thanks, it does make sense what your doing and I learned alot, though I am realized this is a bigger task then I was hoping it would be as not so big. I now am using a prebuild that gives a starting base that I can customize the look of. – chobo2 Jun 08 '18 at 23:01
  • great! glad i could be of assistance. hmm not completely sure i follow what you mean by a prebuild, but if it is smaller and works for your use case then awesome! I'm happy to help more with react questions you may have, just let me know :) – John Ruddell Jun 08 '18 at 23:28
  • sorry I meant prebuilt component from Github: https://github.com/jcmcneal/react-step-wizard . I used it as a starting point and hooked it up with https://wikiki.github.io/components/steps/ . Cool, you don't happen to know anything about Mbox State tree do you? – chobo2 Jun 10 '18 at 04:37
  • I don't, but I can look it up if you have questions regarding that. You can also post a link to a new question if you need help answering that :) – John Ruddell Jun 11 '18 at 18:50