John Rogerson
John Rogerson

Reputation: 1183

Search Query to Filter Results in React

I'm trying to filter data based on a simple user search input.

I'm not sure if its the way i'm filtering the data, but whenever I input something in the text box, the data disappears. I can see in dev tools that the state of the query is being stored.

Here's my code in my context file. I'm planning on adding additional filters once the search function is fixed, thus the reason for the more complicated code.

    import * as React from "react";

const DefaultState = {
  cardListings: [],
  filter: {}
};

const CardListingsContext = React.createContext(DefaultState);

export const CardListingsConsumer = CardListingsContext.Consumer;

export class CardListingsProvider extends React.Component {
  static applyFilter(cards, filter) {
    const { query } = filter;
    let result = cards;
    if (query) {
      const search = query.toLowerCase();
      result = result.filter(item => item.title.indexOf(search) !== -1);
    }
    return result;
  }

  state = DefaultState;

  componentDidMount() {
    fetch("http://localhost:9000/mwBase")
      .then(res => res.json())
      .then(res => {
        this.setState({ cardListings: res });
      });
  }

  updateFilter = filter => {
    this.setState({
      filter
    });
  };

  render() {
    const { children } = this.props;
    const { cardListings, filter } = this.state;

    const filteredListings = CardListingsProvider.applyFilter(
      cardListings,
      filter
    );

    return (
      <CardListingsContext.Provider
        value={{
          allListings: cardListings,
          cardListings: filteredListings,
          updateFilter: this.updateFilter
        }}
      >
        {children}
      </CardListingsContext.Provider>
    );
  }
}

Here's my input form

<form
      className={formClasses}
      noValidate
      onChange={() =>
        setTimeout(() => this.props.updateFilter(this.state), 0)
      }
    >
      <p className="mb-1">Refine your results</p>
      <div className="form-group">
        <input
          type="text"
          className="form-control form-control-lg"
          placeholder="Search for a card..."
          name="query"
          value={this.state.query}
          onChange={event => this.setState({ query: event.target.value })}
        />
      </div>

and where the Filter is being applied on my home page:

<CardListingsProvider>
          <CardListingsConsumer>
            {function(value) {
              const { cardListings, updateFilter } = value;
              return (
                <>
                  <Filter updateFilter={updateFilter} />
                  <div className="columns">
                    {cardListings.map(item => (
                      <Card key={item.itemId} card={item} />
                    ))}
                  </div>
                </>
              );
            }}
          </CardListingsConsumer>
        </CardListingsProvider>
              </div>

Here's example of my dataset:

   [
{
itemId: [
"120901386991"
],
title: [
"1952 Topps Mickey Mantle Chase Card Box 18 packs 5 1950s or 1960's cards per box"
],
globalId: [
"EBAY-US"
],
subtitle: [
"3 BX LOT. 1 VINTAGE PK PER 25 BOXES* LOOK 4 1952 MANTLE"
],
primaryCategory: [
{
categoryId: [
"213"
],
categoryName: [
"Baseball Cards"
]
}
],
secondaryCategory: [
{
categoryId: [
"156521"
],
categoryName: [
"Vintage Non-Sport Cards"
]
}
],
galleryURL: [
"https://thumbs4.ebaystatic.com/m/m1mtMB65mAApWQ2EhJy4qWA/140.jpg"
],
viewItemURL: [
"https://rover.ebay.com/rover/1/711-53200-19255-0/1?ff3=2&toolid=10044&campid=5338164673&customid=watchbask&lgeo=1&vectorid=229466&item=120901386991"
],
paymentMethod: [
"PayPal"
],
autoPay: [
"true"
],
location: [
"USA"
],
country: [
"US"
],
shippingInfo: [
{
shippingServiceCost: [
{
@currencyId: "USD",
__value__: "0.0"
}
],
shippingType: [
"Free"
],
shipToLocations: [
"Worldwide"
],
expeditedShipping: [
"false"
],
oneDayShippingAvailable: [
"false"
],
handlingTime: [
"1"
]
}
],
sellingStatus: [
{
currentPrice: [
{
@currencyId: "USD",
__value__: "118.0"
}
],
convertedCurrentPrice: [
{
@currencyId: "USD",
__value__: "118.0"
}
],
sellingState: [
"Active"
],
timeLeft: [
"P10DT14H59M31S"
]
}
],
listingInfo: [
{
bestOfferEnabled: [
"false"
],
buyItNowAvailable: [
"false"
],
startTime: [
"2012-04-23T16:52:17.000Z"
],
endTime: [
"2019-10-23T16:52:17.000Z"
],
listingType: [
"FixedPrice"
],
gift: [
"false"
],
watchCount: [
"443"
]
}
],
returnsAccepted: [
"false"
],
condition: [
{
conditionId: [
"1000"
],
conditionDisplayName: [
"Brand New"
]
}
],
isMultiVariationListing: [
"false"
],
pictureURLLarge: [
"https://i.ebayimg.com/00/s/NTAwWDMxNA==/z/sT8AAOSw62VZv9qQ/$_1.JPG"
],
topRatedListing: [
"false"
]
},

Upvotes: 1

Views: 1964

Answers (1)

Nithin Thampi
Nithin Thampi

Reputation: 3679

In your case title is an array of string. If it is supposed to contain only one element. You can change your filter function from

result.filter(item => item.title.indexOf(search) !== -1);

to

result.filter(item => item.title[0].indexOf(search) !== -1);

If the title array contains multiple items, You could do use Array.some

result.filter(item =>
  item.title.some(eachTitle => {
    return eachTitle.indexOf(search) !== -1
  })
)

And if you need case insensitive filter, you might need to change the filter function on that aspect too.

const search = query.toLowerCase();
result.filter(item => item.title[0].toLowerCase().indexOf(search) !== -1);

Looks like the code snippet you have posted might not be complete. I see some unbalanced parentheses for applyFilter Function in your Provider component.

  static applyFilter(cards, filter) {
    const { query } = filter;
    let result = cards;
    if (query) {
      const search = query.toLowerCase();
      result = result.filter(item => item.title.indexOf(search) !== -1);
    }

  state = DefaultState;

Also I'm wondering why would you need a setTimeout to call setState function in Filter component. The below

onChange={() =>
          setTimeout(() => this.props.updateFilter(this.state), 0)
        }

You can get rid of that as well.

I have made some edits to complete applyFilter function to return the filtered data. Please have a look at the below code and Run Code Snippet to see the code in action. Hope this helps!

// Provider Class

const DefaultState = {
  cardListings: [],
  filter: {}
};

const CardListingsContext = React.createContext(DefaultState);

const CardListingsConsumer = CardListingsContext.Consumer;

class CardListingsProvider extends React.Component {
  static applyFilter(cards, filter) {
    const {
      query
    } = filter;
    let result = cards;
    if (query) {
      const search = query.toLowerCase();
      result = result.filter(item => item.title[0].toLowerCase().indexOf(search) !== -1);
    }
    return result;
  }

  state = DefaultState;

  componentDidMount() {
    Promise.resolve([
  {
    itemId: ['1'],
    title: ['Apple']
  },
  {
    itemId: ['2'],
    title: ['Orange']
  },
  {
    itemId: ['3'],
    title: ['Peach']
  }
]).then(res => {
      this.setState({
        cardListings: res
      });
    });
  }

  updateFilter = filter => {
    this.setState({
      filter
    });
  };

  render() {
    const {
      children
    } = this.props;
    const {
      cardListings,
      filter
    } = this.state;

    const filteredListings = CardListingsProvider.applyFilter(
      cardListings,
      filter
    );

    return ( <
      CardListingsContext.Provider value = {
        {
          allListings: cardListings,
          cardListings: filteredListings,
          updateFilter: this.updateFilter
        }
      } >
      {
        children
      } 
      </CardListingsContext.Provider>
    );
  }
}



class Filter extends React.Component {
  state = { query: "" };
  render() {
    return (
      <form
        noValidate
        onChange={() =>
          setTimeout(() => this.props.updateFilter(this.state), 0)
        }
      >
        <p className="mb-1">Refine your results</p>
        <div className="form-group">
          <input
            type="text"
            className="form-control form-control-lg"
            placeholder="Search for a card..."
            name="query"
            value={this.state.query}
            onChange={event => this.setState({ query: event.target.value })}
          />
        </div>
      </form>
    );
  }
}




class Home extends React.Component {
  render() {
    return (
      <div>
        <CardListingsProvider>
          <CardListingsConsumer>
            {function(value) {
              const { cardListings, updateFilter } = value;
              return (
                  <React.Fragment>
                  <Filter updateFilter={updateFilter} />
                  <div className="columns">
                    {cardListings.map(item => (
                      <div key={item.itemId}>{JSON.stringify(item)}</div>
                    ))}
                  </div>
                  </React.Fragment>
              );
            }}
          </CardListingsConsumer>
        </CardListingsProvider>
      </div>
    );
  }
}





ReactDOM.render( <Home /> , document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Upvotes: 1

Related Questions