0

Hello I am trying to make a step wizard component but I have the following issue. I have the following file:

import React from 'react';
import { View } from 'react-native';
import WizardStep from './WizardStep'

export default class Wizard extends React.Component {

  constructor(props){
    super(props);
    this.state = {
      questions: this.props.questions,
      answers: this.props.answers,
      totalSteps: this.props.questions.length,
      currentStep: 0,
      results: []
    }
  }

  updateStep = answer =>  {
    newResults = this.state.results
    newResults[this.state.currentStep - 1] = answer
    this.setState({
      results: newResults,
      currentStep: this.state.currentStep + 1
    }, () => {
      if (this.state.currentStep == this.state.totalSteps) {
        this.props.finish();
      }
    })
  }

  renderStep = () => {
    if (this.state.currentStep < this.state.totalSteps) {
        return (
          <View>
            <WizardStep
              question={this.state.questions[this.state.currentStep]}
              answers={this.state.answers[this.state.currentStep]}
              step={this.state.currentStep}
              totalSteps={this.state.totalSteps}
              updateStep={this.updateStep}
            />
          </View>
        );
    } else {
        return null;
    }
  }

  render(){
      return(
          <View>
            {this.renderStep()}
          </View>
      )
  }
}

questions is an array of strings and answers is an array of arrays of strings.

Anyway the first screen shows up just fine. But when I call the updateStep function the currentStep updates but it doesn't show the 2nd item from questions/answers array. Any ideas? Thank you in advance!

Adding the other components for the wizard:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Button } from "react-native-elements";
import { Constants } from 'expo';
import WizardStepButton from './WizardStepButton';

export default class WizardStep extends React.Component {

  constructor(props){
    super(props);
    this.state ={ 
      question: this.props.question,
      answers: this.props.answers,
      totalSteps: this.props.totalSteps,
      step: this.props.step,
    }
  }
  renderAnswers = () => {
    var answers = []
    for (var i = 0; i < this.state.answers.length; i++) {
        answers.push(
          <WizardStepButton 
            answer={this.state.answers[i]}
            updateStep={this.props.updateStep}
            key={i}
          />
        );
    }
    return answers;
  }

  render(){
      return(
          <View>
            <Text style={styles.title}>Step {this.state.step + 1}/{this.state.totalSteps}</Text>
            <Text style={styles.title}>{this.state.question}</Text>
            {this.renderAnswers()}
          </View>
      )
  }
}

const styles = StyleSheet.create({
  title: {
    marginTop: 30,
    marginBottom: 30,
    fontSize: 25,
    color: 'rgba(96,100,109, 1)',
    lineHeight: 24,
    textAlign: 'center',
  },
});

and the button component:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Button } from "react-native-elements";
import { Constants } from 'expo';

export default class WizardStepButton extends React.Component {

  constructor(props){
    super(props);
    this.state ={ 
    }
  }

  render(){
      return(
          <View>
            <Button 
              style={{margin: 10}}
              large
              raised
              title={this.props.answer}
              onPress={() => this.props.updateStep(this.props.answer)}
            />
          </View>
      )
  }
}

1 Answers1

1

You should only increment state values by using a state updater function. - https://stackoverflow.com/a/45196079/874027

You're not spreading this.state.results before editing and putting them back into state.

Also the currentStep checks indexing looks off.

updateStep = answer =>  {
  this.setState((state) => {
    const { results, currentStep } = state
    const newResults = [...results]
    newResults[currentStep] = answer
    return {
      results: newResults,
      currentStep: currentStep + 1,
    }
  }, () => {
    const { currentStep, totalSteps } = this.state
    if (currentStep + 1 === totalSteps) {
      this.props.finish();
    }
  })
}

EDIT: in WizardStep component you're syncing props with state in constructor so when you try to pass the new props after you update your state, they'll never get reflected in the Wizard since its constructor has already fired off. You can either fix this by using props in your WizardStep component, or by passing it a key, so the new instance gets created every time the key changes, e.g.

<WizardStep
  question={this.state.questions[this.state.currentStep]}
  answers={this.state.answers[this.state.currentStep]}
  step={this.state.currentStep}
  totalSteps={this.state.totalSteps}
  updateStep={this.updateStep}
  key={this.state.currentStep}
/>

I've tested this locally and the steps do get changed with this approach.

Eugene
  • 10,006
  • 4
  • 37
  • 55
  • Thank you for your answer. Tried your solution but it still does not render the next question. – Panos Malliakoudis Dec 15 '18 at 09:57
  • So what exactly does happen when you perform update answer? Put some console logs around render and updateStep methods and see what values are being used for currentStep and results. – Eugene Dec 15 '18 at 10:00
  • currentStep updates and shows 1(this is correct), results also updates and shows an array with the answer (a string) in the first field. But nothing else matters on my screen. The wizard stays on step 1 and still renders the first question with its answers. – Panos Malliakoudis Dec 15 '18 at 10:58
  • Also I updated my question with the code from the other components if it helps somehow. – Panos Malliakoudis Dec 15 '18 at 11:01
  • Passing a key solved the problem. Thank you very much! – Panos Malliakoudis Dec 15 '18 at 14:25