3

I'm trying to make a react component that would print out the total sum of selected values in 2 ChoiceGroup components. It must evaluate on every choice change. I'm updating state with SetState, but something is not right, as it calculates and shows value correctly, but choice is not being selected on the UI. In other words, I have to double click the choice to work correctly. I think it's related to SetState being asyncronous? I'm complete newbie in react, there must be a better way than making new handlers for each ChoiceGroup?

Here is my code:

import * as React from 'react';
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { autobind } from 'office-ui-fabric-react/lib/Utilities';
import styles from './ProjectSizingTool.module.scss';
import { IProjectSizingToolProps } from './IProjectSizingToolProps';
import { escape } from '@microsoft/sp-lodash-subset';

export default class ProjectSizingTool extends React.Component<IProjectSizingToolProps, {}> {
  state = {
    scores: [],
    score: 0
  }
  constructor (props){
    super(props);
    this.state = {
      scores : [0, 0, 0, 0, 0, 0, 0, 0, 0],
      score: 0
    }
  }


  public render(): React.ReactElement<IProjectSizingToolProps> {
    var score = this.state.score;

    return (

      <div className={ styles.projectSizingTool }>
        <div className={ styles.container }>
          <div className={ styles.row }>
            <div className={ styles.column }>
              <ChoiceGroup label='Project Complexity'
                options={[
                  {key: '1', text: 'Well Mastered Routine'},
                  {key: '3', text: 'Many similar projects done before'},
                  {key: '5', text: 'Some Experience / High Bid Value'},
                  {key: '8', text: 'Complex / Bespoke Solution'}
                ]} 
                onChange={ this._onChoice1Change } required = {true}
              />
              <ChoiceGroup label='Bid Value / Contract'
                options={[
                  {key: '1', text: '0K – £100K / Cost + fee'},
                  {key: '3', text: '£0K - £100K / Time & Material'},
                  {key: '5', text: '75K - £1M / Lump Sum'},
                  {key: '8', text: '£750K + / Lump Sum'}
                ]} 
                onChange={ this._onChoice2Change } required = {true}
              />
              <a href="#" className={ styles.button }>
                <span className={ styles.label } >{score}</span>
              </a>

            </div>
          </div>
        </div>
      </div>
    )

  }

  private calculateScore(scores) {
    let score = 0;
    console.log ("scores: " + scores)
    for (let i in scores) {
      score += parseInt(scores[i]);
    }
    return score;
  }

  @autobind
  private _onChoice1Change(ev: React.FormEvent<HTMLInputElement>, option: any) {
    var tempScores = this.state.scores.slice();
    tempScores[0] = option.key;
    const newScore = this.calculateScore(tempScores);
    this.setState({
       score : newScore
    });  
  }


  @autobind
  private _onChoice2Change(ev: React.FormEvent<HTMLInputElement>, option: any) {
    var tempScores = this.state.scores.slice();
    tempScores[1] = option.key;
    const newScore = this.calculateScore(tempScores);
    this.setState({
       score : newScore
    });  
  }


}

2 Answers 2

5

Thanks for the suggestion but the issue was elsewhere. We must re-render control's value after setting state. What I was missing was selectedKey property of ChoiceGroup:

<ChoiceGroup label='Project Complexity'
            options={[
              {key: '1', text: 'Well Mastered Routine'},
              {key: '3', text: 'Many similar projects done before'},
              {key: '5', text: 'Some Experience / High Bid Value'},
              {key: '8', text: 'Complex / Bespoke Solution'}
            ]} selectedKey={this.state.scores[0]} 
            onChange={ this._onChoice1Change }
          />
1
  • How to re-render the control?
    – Ramesh
    Commented May 31, 2019 at 5:35
2

Could you try this one and let me know if it works:

import * as React from 'react';
import { ChoiceGroup, IChoiceGroupOption } from 'office-ui-fabric-react/lib/ChoiceGroup';
import { autobind } from 'office-ui-fabric-react/lib/Utilities';
import styles from './ProjectSizingTool.module.scss';
import { IProjectSizingToolProps } from './IProjectSizingToolProps';
import { escape } from '@microsoft/sp-lodash-subset';

export interface IProjectSizingToolState {
    scores: number[];
    score: number;
}

export default class ProjectSizingTool extends React.Component<IProjectSizingToolProps, IProjectSizingToolState> {

    constructor (props: IProjectSizingToolProps, state: IProjectSizingToolState){
        super(props, state);
        this.state = {
            scores : [0, 0, 0, 0, 0, 0, 0, 0, 0],
            score: 0
        }
    }


    public render(): React.ReactElement<IProjectSizingToolProps> {
        var score = this.state.score;

        return (
            <div>
                <div className={ styles.projectSizingTool }>
                    <div className={ styles.container }>
                        <div className={ styles.row }>
                            <div className={ styles.column }>
                                <ChoiceGroup label='Project Complexity'
                                    options={[
                                        {key: '1', text: 'Well Mastered Routine'},
                                        {key: '3', text: 'Many similar projects done before'},
                                        {key: '5', text: 'Some Experience / High Bid Value'},
                                        {key: '8', text: 'Complex / Bespoke Solution'}
                                    ]} 
                                    onChange={ this._onChoice1Change } required = {true}
                                />
                                <ChoiceGroup label='Bid Value / Contract'
                                    options={[
                                        {key: '1', text: '0K – £100K / Cost + fee'},
                                        {key: '3', text: '£0K - £100K / Time & Material'},
                                        {key: '5', text: '75K - £1M / Lump Sum'},
                                        {key: '8', text: '£750K + / Lump Sum'}
                                    ]} 
                                    onChange={ this._onChoice2Change } required = {true}
                                />
                                <a href="#" className={ styles.button }>
                                    <span className={ styles.label } >{score}</span>
                                </a>

                            </div>
                        </div>
                    </div>
                </div>
            </div>
        );

    }

    private calculateScore(scores) {
        let score = 0;
        console.log ("scores: " + scores)
        for (let i in scores) {
            score += parseInt(scores[i]);
        }
        return score;
    }

    @autobind
    private _onChoice1Change(ev: React.FormEvent<HTMLInputElement>, option: any) {
        var tempScores = this.state.scores.slice();
        tempScores[0] = option.key;
        const newScore = this.calculateScore(tempScores);
        this.setState({
            score : newScore
        });  
    }


    @autobind
    private _onChoice2Change(ev: React.FormEvent<HTMLInputElement>, option: any) {
        var tempScores = this.state.scores.slice();
        tempScores[1] = option.key;
        const newScore = this.calculateScore(tempScores);
        this.setState({
            score : newScore
        });  
    }


}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.