React Matrix of Radio buttons

Viewed 289

I tried to solve this js react problem and get stuck on questions 2-4. Question 2: I don't know how to check the local state for each row in order to check for the duplicate rank select Question 3: Should I need props passed to the component to check for unique? Question 4: How do I check all rows have a select ranked and unique?

Here are the questions:

  1. Adding a class of "done" to a row will highlight it green. Provide this visual feedback on rows which have a selected rank.

  2. Adding a class of "error" to a row will highlight it red. Provide this visual feedback on rows which have duplicate ranks selected.

  3. There is a place to display an error message near the submit button. Show this error message: Ranks must be unique whenever the user has selected the same rank on multiple rows.

  4. The submit button is disabled by default. Enable it when all rows have a rank selected and all selected ranks are unique.

The orginal App.js

import React, { Component } from 'react';
import './App.css';
import MainPage from './components/MainPage';

class App extends Component {
  render() {
    return (
      <MainPage />
    );
  }
}

export default App;

MainPage.js

import React from 'react';
import _ from 'lodash';
import FormRow from './FormRow.jsx';
import Animal from './Animal.js';

class MainPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      animals: ['panda','cat','capybara','iguana','muskrat'].map((name) => {
        return new Animal(name);
      }),
      error: ''
    };
  }


  render() {
    const rows = this.state.animals.map((animal) => {
      return (
        <FormRow
          animalName={animal.name}
          key={animal.name}
        />
      );
    });

    const headers = _.range(1, 6).map((i) => <th key={`header-${i}`}>{i}</th>);

    return (
      <div>
        <table>
          <thead>
            <tr>
              <th></th>
              {headers}
            </tr>
          </thead>
          <tbody>
            {rows}
          </tbody>
        </table>
        <div>{this.state.error}</div>
        <input type="submit" />
      </div>
    );
  }
}

export default MainPage;

FormRow.jsx

import React from 'react';
import _ from 'lodash';

class FormRow extends React.Component {
  render() {
    const cells = _.range(1, 6).map((i) => {
      return (
        <td key={`${this.props.animalName}-${i}`}>
          <input
            type="radio"
            name={this.props.animalName}
            value={i}
          />
        </td>
      );
    });

    return (
      <tr>
        <th>{this.props.animalName}</th>
        {cells}
      </tr>
    )
  }
}

export default FormRow;

Animal.js

class Animal {
  constructor(name, rank) {
    this.name = name;
    this.rank = rank;
  }
}

export default Animal;

My code is at GitHub (git@github.com:HuydDo/js_react_problem-.git). Thanks for your suggestion!

FormRow.jsx

import React from 'react';
import _ from 'lodash';

class FormRow extends React.Component {

  constructor(){
    super();
    this.state = {
      rowColor : false,
      name: "",
      rank: 0
        // panda: 0,
        // cat:  0,
        // capybara: 0,
        // iguana:  0,
        // muskrat:  0
    }
  }

  handleChange = (e) => {
    if (this.state.rank === e.target.value){
      console.log("can't select same rank.")
    }
    console.log(e.target.name)
    console.log(e.target.value)
    this.setState({
      // [e.target.name]: e.target.value,
      name: e.target.name,
      rank: e.target.value,
      rowColor: true
    }, console.log(this.state))
  }
  
  handleChange2 = (e) => {
    let newName = e.target.name
    let newRank = e.target.value
    let cRank = this.state.rank
    let cName = this.state.name
    console.log(this.state)
    console.log(`${newRank} ${newName}`)

    if(cName !== newName) {
      if(cRank !== newRank) {
        this.setState({
         name : newName,
         rank: newRank,
         rowColor: true
        },()=> console.log(this.state))
      }
      else {
        console.log("can't select same rank")
      }
    }

    //  this.setState(previousState => {
       
    //    let cRank = previousState.rank
    //    let cName = previousState.name
    //    console.log(previousState) 

    //    return {
    //       rank: newRank,
    //       name: newName,
    //       rowColor: true
    //      }
    //  },console.log(this.state.rank))
  }

  render() {
    const cells = _.range(1, 6).map((i) => {
      return (
        <td key={`${this.props.animalName}-${i}`} onChange={this.handleChange2}>
          <input 
            type="radio"
            name={this.props.animalName}
            value={i}
          /> 
        </td>
      );
    });

    return (
     
      <tr className = {(this.state.rowColor) ? 'done':null}  >
      {/* <tr> */}
        <th>{this.props.animalName}</th>
        {cells}
      </tr>
    )
  }
}

export default FormRow;

MainPage.jsx

import React from 'react';
import _ from 'lodash';
import FormRow from './FormRow.jsx';
import Animal from './Animal.js';

class MainPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      animals: ['panda','cat','capybara','iguana','muskrat'].map((name) => {
        return new Animal(name);
      }),
      error: ''
    };
  }

  getValue = ({name,rank}) =>{
      console.log(`Name: ${name} rank: ${rank}`)
  }
  
  // handleSubmit = event => {
   
  //   event.preventDefault()
  //   this.props.getValue(this.state)
  // }

  checkForUnique = () => {
    // Show this error message: `Ranks must be unique` whenever the user has selected the
  //  same rank on multiple rows.
    this.setState({
      error : "Ranks must be unique"
    })  
  
  }

  isDisabled = () =>{
   // The submit button is disabled by default. Enable it when all rows have a
   // rank selected and all selected ranks are unique.
    return true
  }

  render() {

    const rows = this.state.animals.map((animal) => {
      return (
        <FormRow
          animalName={animal.name}
          key={animal.name}
          rank={animal.rank}
          handleChange={this.handleChange}
          getValue={this.getValue}
        />
      );
    });

    const headers = _.range(1, 6).map((i) => <th key={`header-${i}`}>{i}</th>);

    return (
      <div>
        {/* <form onSubmit={this.onSubmit}> */}
        <table>
          <thead>
            <tr>
              <th></th>
              {headers}
            </tr>
          </thead>
          <tbody>
            {rows}
          </tbody>
        </table>
        <div>{this.state.error}</div>
        <input type="submit" value="Submit" disabled={this.isDisabled()} />        {/* <button type="submit">Submit</button> */}
        {/* </form> */}
      </div>
    );
  }
}

export default MainPage;

enter image description here

I tried to add handleChange and handleAnimalSelect methods, but I get an error. The new name and rank are not added to the arrays.

enter image description here

MainPage.jsx

import React from 'react';
import _ from 'lodash';
import FormRow from './FormRow.jsx';
import Animal from './Animal.js';

class MainPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      animals: ['panda','cat','capybara','iguana','muskrat'].map((name) => {
        return new Animal(name);
      }),
      error: ''
    };
  }


  isDisabled = () =>{
   // The submit button is disabled by default. Enable it when all rows have a
   // rank selected and all selected ranks are unique.
    return true
  }

  render() {

    const rows = this.state.animals.map((animal) => {
      return (
        <FormRow
          animalName={animal.name}
          key={animal.name}
          rank={animal.rank}
         
          getValue={this.getValue}
          handleAnimalSelect={this.handleAnimalSelect}
        />
      );
    });

    const headers = _.range(1, 6).map((i) => <th key={`header-${i}`}>{i}</th>);

    return (
      <div>
        {/* <form onSubmit={this.onSubmit}> */}
        <table>
          <thead>
            <tr>
              <th></th>
              {headers}
            </tr>
          </thead>
          <tbody>
            {rows}
          </tbody>
        </table>
        <div>{this.state.error}</div>
        <input type="submit" value="Submit" disabled={this.isDisabled()} />        
        {/* <button type="submit">Submit</button> */}
        {/* </form> */}
      </div>
    );
  }
}

export default MainPage;

FormRow.jsx

import React from 'react';
import _ from 'lodash';
import FormRow from './FormRow.jsx';
import Animal from './Animal.js';

class MainPage extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      animals: ['panda','cat','capybara','iguana','muskrat'].map((name) => {
        return new Animal(name);
      }),
      error: ''
    };
  }


  isDisabled = () =>{
   // The submit button is disabled by default. Enable it when all rows have a
   // rank selected and all selected ranks are unique.
    return true
  }

  render() {

    const rows = this.state.animals.map((animal) => {
      return (
        <FormRow
          animalName={animal.name}
          key={animal.name}
          rank={animal.rank}
         
          getValue={this.getValue}
          handleAnimalSelect={this.handleAnimalSelect}
        />
      );
    });

    const headers = _.range(1, 6).map((i) => <th key={`header-${i}`}>{i}</th>);

    return (
      <div>
        {/* <form onSubmit={this.onSubmit}> */}
        <table>
          <thead>
            <tr>
              <th></th>
              {headers}
            </tr>
          </thead>
          <tbody>
            {rows}
          </tbody>
        </table>
        <div>{this.state.error}</div>
        <input type="submit" value="Submit" disabled={this.isDisabled()} />        
        {/* <button type="submit">Submit</button> */}
        {/* </form> */}
      </div>
    );
  }
}

export default MainPage;
2 Answers

You're pretty much making a form with a few rows of multiple choice answers.

One way of simplifying everything is to have all the logic in the top component, in your case I think MainPage would be where it would be. Pass down a function as a prop to all the descendants that allows them to update the form data upstream.

In Q2, how do intend to check the state for each row? Perhaps you can use arrays or objects to keep track of the status of each question. The arrays/objects are stored in state, and you just check them to see what the status is.

I'm actually not clear what your app looks like - what does a row look like? (You might want to post a screenshot) And I don't see any way for rank to be selected - I don't even see what the ranks are for, or how they are used in the form. So perhaps your form design needs to be tweaked. You should begin the form design with a clear picture in YOUR mind about how the app will work. Maybe start by drawing the screens on paper and drawing little boxes that will represent the objects/array variables and go through the process of a user using your app. What happens to the various boxes when they click radio buttons and so on. How will you know if the same rank is selected twice - where are the selected ranks stored? What animals are clicked/selected? Where are those stored? Draw it all on paper first.

Array or objects: If you want to keep it simple, you can do the whole project just using arrays. You can have one array that stores all the animals. You can have a different array that stores which animals are selected right NOW (use .includes() to test if an animal is in that array). You can have another array that stores the rows that have a rank selected. When the number of elements in that row === the number of rows (is that the same as the number of animals? If yes, then you can use the length of the animals array for that test)

How do you know if the rows with a rank selected are unique? One way is to DISALLOW selected a rank that has already been selected. Again, use .includes() (e.g. arrSelectedRanks.includes(num)) to check if a rank has already been selected.

SO what do one of these checks look like?

const handleAnimalSelect = (animal) => {
   const err = this.state.selectedAnimals.includes(animal);
   if (err === true){
       this.setState(error: animal);
   }
}

return (
  <input
     type="radio"
     name={this.props.animalName}
     value={i}
     onClick={this.handleAnimalSelect}
  />

  { error !== undefined && (
    <div class={style.errorMessage}>{`Animal ${} was chosen twice`}</div>
  )}

);


};

Remember: State is what you use for remembering the value of variables in a given component. Every component has its own state. But Props are for data/functions/elements that are passed into the component. You don't update the values of props in the component (prop values are stored in another component. If you need to update them, you use functions to pass data back to the parent component where that variable is in state, and update the value there).

Here is an example that uses .includes() to check for the presence/absence of something:

https://stackoverflow.com/a/64486351/1447509

Related