1

In my react app, I have 3 dropdown menus, the first selects a few properties with ascending and descending options, the second filter by hair color and the third filter by professions. However, depending on which dropdown i use first, the other 2 won't work afterwards unless i refresh the browser. The data is fetched from somewhere using axios and the dropdown menus are child components and props passed back as callback functions.

I think I need to reset the state for the other search criteria, have tried but that didn't work. I have also look up for solutions for multiple filters to work, but I don't seem to be able to apply to my problems.

Here is my initial state of component:

class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
  data: [],
  imageIsLoaded: false,
  orderBy: "",
  order: "",
  profession: "",
  hairColor: "",
};
this.doOrderBy = this.doOrderBy.bind(this);
this.doOrder = this.doOrder.bind(this);
this.handleColor = this.handleColor.bind(this);
this.handlePro = this.handlePro.bind(this);
}

Here are the handlers

doOrderBy(e) {
const newOrderBy = e.target.value;
this.setState({
  orderBy: newOrderBy,
});
 }

doOrder(e) {
const newOrder = e.target.getAttribute("data-value");
this.setState({ order: newOrder });
}

doOrderBy is an array ["name", "age", weight"..] doOrder is ["asc", "desc"]

handleColor(e) {
const uniqueHairColor = [
  ...new Set(this.state.data.map(item => item.hair_color)),
];
const newColor = [...e.target.selectedOptions].map(opt => opt.value);
const newValue = newColor == " " ? uniqueHairColor : newColor;
this.setState({
  hairColor: newValue,
});
}

handlePro(e) {
const newProfession = [...e.target.selectedOptions].map(opt => 
opt.value);
const allProfessions = this.state.data
  .map(item => item.professions)
  .flat(1);
const uniqueProfession = [...new Set(allProfessions)].sort();
const newValue = newProfession == " " ? uniqueProfession : 
newProfession;
this.setState({
  profession: newValue,
});
}

Here is the sorting logic:

render() {
const {data, imageIsLoaded, hairColor, orderBy, order, profession} 
=this.state;
let sorted = data;

if (order) {
  if (orderBy !== ("number of friends" && "number of professions")) 
   { sorted = _.orderBy(sorted, item => {
        return item[orderBy]}, order);
   } else if (orderBy === "number of friends") {
    sorted = _.orderBy(sorted, item => {
        return item.friends.length},order);
   } else {sorted = _.orderBy(sorted, item => {
       return item.professions.length}, order);
  }
} else if (hairColor) {
sorted = _.filter(sorted, item => _.includes(hairColor, 
item.hair_color));
} else if (profession) {
  sorted = _.filter(sorted, { professions: profession });
} else {
  sorted = data;
}

here are the dropdown menus as components in the render:

         <OrderMenu
          doOrder={this.doOrder}
          order={order}
          orderBy={orderBy}
          doOrderBy={this.doOrderBy}
          placeholder="Select by..."
        />

        <HairMenu
          data={data}
          handleHairInput={this.handleColor}
          placeholder="Choose hair color..."
        />

        <ProfessionMenu
          data={data}
          handleInputPro={this.handlePro}
          placeholder="Choose profession..."
        />

and the data is rendered as

     <AnimalCards sorted={sorted}/>

Ideally, I would like to have the filters working together, doing multiple filtering at the same time. But at the moment, when i use the first filter, the second or third won't work at all unless I refresh the browser. Any help to point out what and where I am missing would be great. Thanks.

import React, { Component, Fragment } from "react";

class AnimalCards extends Component {
render() {
const { sorted } = this.props;
const Jobless = <div>No profession to show</div>;
const NoFriends = <div>No friends to show</div>;
return (
  <Fragment>
    <div className="flip-card-container">
      {sorted.map(animal => {
        return (
          <div className="flip-card" key={animal.id}>
            <div className="flip-card-inner">
              {/* ---------------START front ----------- */}
              <div className="flip-card-front">
                <div
                  className="flip-card-front-top"
                  style={{ backgroundColor: animal.hair_color }}
                />
                <div className="img-container">
                  <img
                    className="img-circle"
                    src={animal.thumbnail}
                    alt={`${animal.name}`}
                    style={{ border: "5px solid white" }}
                  />
                </div>
                <div className="flip-card-front-bottom">
                  <div className="name">{animal.name}</div>
                  <div className="attributes">
                    <div className="age-group">
                      <img
                        className="ageIcon"
                        src={require("../images/axe.png")}
                        alt="ageIcon"
                      />
                      <div className="age">{animal.age + " "} 
      yrs</div>
                    </div>
                    <div className="weight-group">
                      <img
                        className="weightIcon"
                        src={require("../images/weight.png")}
                        alt="weightIcon"
                      />
                      <div className="weight"> {animal.weight} kg 
    </div>
                    </div>
                    <div className="height-group">
                      <img
                        className="ageIcon"
                        src={require("../images/height.png")}
                        alt="heightIcon"
                      />
                      <div className="height">{animal.height} cm 
</div>
                    </div>
                  </div>
                  <div className="professions">
                    Number of professions:
                    <div className="professions-num">
                      {animal.professions.length === 0
                        ? Jobless
                        : animal.professions.length}
                    </div>
                  </div>
                  <div className="friends">Number of friends:</div>
                  <div className="friends-num">
                    {animal.friends.length === 0
                      ? NoFriends
                      : animal.friends.length}
                  </div>
                </div>
              </div>
              {/* ------------START front ----------- */}
              {/* ------------START back ------------- */}
              <div className="flip-card-back">
                <div className="back-container">
                  <div className="back-header">Professions</div>
                  <div className="back-list">
                    {animal.professions.length === 0
                      ? Jobless
                      : animal.professions.join(", ")}
                  </div>
                </div>
                <div className="back-container">
                  <div className="back-header">Friends</div>
                  <div className="back-list">
                    {animal.friends.length === 0
                      ? NoFriends
                      : animal.friends.join(", ")}
                  </div>
                </div>
              </div>
              {/* -------------END back ------------- */}
            </div>
          </div>
        );
      })}
    </div>
  </Fragment>
 );
 }
 }
 export default AnimalCards;

Here is the data (in json) that i fetched:

fetchAnimals() {
const apiUrl =
 "https://raw.githubusercontent.com/rrafols/ 
 mobile_test/master/data.json";

 axios.get(apiUrl).then(({ data }) => {
  localStorage.setItem("data", data);
  this.setState({
    data: data.Brastlewark,
    imageIsLoaded: true,
  });
  });
}

componentDidMount() {
this.fetchAnimals();
}
2
  • Can you share the data structure you are trying to sort? Also the code for the animal cards component? Commented Mar 24, 2019 at 15:22
  • 1
    Hi, thanks, I have updated with more information. Commented Mar 24, 2019 at 15:52

1 Answer 1

2

The problem was actually trivial. You had used an if-else-if block!

if (order) { //If this is true the other else ifs will not be executed
  if (orderBy !== ("number of friends" && "number of professions")) 
   { sorted = _.orderBy(sorted, item => {
        return item[orderBy]}, order);
   } else if (orderBy === "number of friends") {
    sorted = _.orderBy(sorted, item => {
        return item.friends.length},order);
   } else {sorted = _.orderBy(sorted, item => {
       return item.professions.length}, order);
  }
} else if (hairColor) { // If the above condition was met this will not be checked
sorted = _.filter(sorted, item => _.includes(hairColor, 
item.hair_color));
} else if (profession) { //If any of the above was true this would not be checked
  sorted = _.filter(sorted, { professions: profession });
} else {
  sorted = data;
}

So... turn the if-else-if blocks to individual/independant if blocks! Also, small advice on the algorithm too, always do your filtering before the sorting!

Here is the fixed logic.

render() {
  const {
    data,
    imageIsLoaded,
    hairColor,
    orderBy,
    order,
    profession
  } = this.state;

  let sorted = data;

  if (hairColor) {
    sorted = _.filter(sorted, item => _.includes(hairColor,
      item.hair_color));
  }

  if (profession) {
    sorted = _.filter(sorted, {
      professions: profession
    });
  }

  if (order) {
    if (orderBy !== ("number of friends" && "number of professions")) {
      sorted = _.orderBy(sorted, item => {
        return item[orderBy]
      }, order);
    } else if (orderBy === "number of friends") {
      sorted = _.orderBy(sorted, item => {
        return item.friends.length
      }, order);
    } else {
      sorted = _.orderBy(sorted, item => {
        return item.professions.length
      }, order);
    }
  }


}
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks so much for pointing that out! I also needed to breakup the (else if) inside the third block. I just started learning web programming a few months ago, still trying to get my head around all these stuff. Now the filters also work in combination. Thanks very much for your help. Will be more attentive to the logic next time...
Pleas be kind enough to upvote the answer and mark it as answered if it helped you out! :)
yes, I already upvoted, but I can't find where to marked it as answered?
Hmm you haven't upvoted. There is a small tick next to the answer that will mark it as answered
I have marked it as the answer and also I upvoted with the up arrow but an alert message said that it only record but not show if reputation is less than 15 as I am new, so currently only have 10 reputations. I will come back and upvote when I get 15. As a beginner, I really appreciate the support and help from the tech community here, thanks!

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.