3
votes

I have a React project with a GraphQL server using Apollo client. I am trying to figure out how to change the query result based on search text and filtering selections. If someone could look at my code and give me some help, I would greatly appreciate it. Sorry about all the code, I thought maybe it all might be relevant.

server/index.js

const resolvers = {
  Query: {
    getClasses: () => classes,
    searchClasses: () => classes
  },
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
app.use(cors());
app.use(bodyParser.text({ type: 'application/graphql' }));
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
  console.log(`???? Server ready at http://localhost:4000${server.graphqlPath}`)
);

server/schema.js

const typeDefs = `
  type Class {
    entry_id: ID!
    title: String
    url: String
    thumb: String
    duration: [String]
    style: [String]
    intensity: [String]
    anatomical_focus: [String]
    level: [String]
    teacher: [String]
    authorId: Int
    live_start: Int
    live_end: Int
    type: String
    logged_in_member_id: Int
    favorite: Boolean
    favorite_order: Int
    favorited_on: Int
    body_snippet: String
  }

  input Filters {
    teacher: String
    duration: String
    level: String
    style: String
    anatomy: String
  }

  type Query {
    getClasses: [Class]!
    searchClasses(search: String, filters: Filters): [Class]!
  }
`;

module.exports = typeDefs;

client/src/index.js

const client = new ApolloClient({
  uri: 'http://localhost:4000/graphql',
  cache: new InMemoryCache()
});

ReactDOM.render(
  <ApolloProvider client={client}>
    <Header />
    <Router>
      <Route exact path='/' component={App}/>
    </Router>
  </ApolloProvider>, 
  document.getElementById('root')
);
serviceWorker.unregister();

queries/search

const SEARCH_CLASSES = gql`
  query SearchClasses($search: String, $filters: Filters) {
    searchClasses(search: $search, filters: $filters) {
      entry_id
      title
      url
      thumb
      duration
      style
      intensity
      level
      teacher
      body_snippet
    }
  }
`;

client/src/pages/Classes.js

class Classes extends Component {
    constructor(props) {
        super(props);
        this.state = { keyword: '', filters: { teacher: '', duration: '', level: '', style: '', anatomy: '' } };
        this.setSearch = this.setSearch.bind(this);
        this.setFilters = this.setFilters.bind(this);
    }
    setSearch(e) {
        this.setState({
            keyword: e.target.value
        });
    }
    setFilters(e) {
        this.setState({
            filters: e.target.value
        })
    }
    componentDidMount() {
        var ddToggle = document.querySelectorAll('.dropdown-toggle');
        ddToggle.forEach(function (dd) {
            dd.addEventListener("click", function(ev) {
                dd.nextSibling.classList.toggle('show');
            });
        });
    }

    render() {
        let text = this.state.keyword;
        let teacher = this.state.filters.teacher;
        let duration = this.state.filters.duration;
        let level = this.state.filters.level;
        let style = this.state.filters.style;
        let anatomy = this.state.filters.anatomy;
        return (
            <Query
                query={Queries.Search}
                variables={{
                    search: text,
                    filters: {
                        teacher: '',
                        duration: '',
                        level: '',
                        style: '',
                        anatomy: ''
                    }
                }}>
                {({ loading, error, data }) => {
                    if (loading) return <p>Loading...</p>;
                    if (error) return <p>Error: ${error}</p>;
                    return (
                        <div>
                            <section className="ygi-search">
                            <div className="container px-3">
                                <div className="ygi-search__wrapper">
                                    <SearchBar handleSearch={this.setSearch} search={ text } />
                                    <button className="ygi-search__filter-btn mx-auto mb-2 " aria-label="Shows Filters">Show Filters</button>
                                    <div className="yi-dropdowns__wrapper-visibility col-lg-10 ">
                                        <div className="row">
                                            <TeachersDD handleSelect={this.setFilters} selection={ teacher } />
                                            <DurationDD  handleSelect={this.setFilters} selection={ duration } />
                                            <LevelDD handleSelect={this.setFilters} selection={ level } />
                                            <StyleDD handleSelect={this.setFilters} selection={ style } />
                                            <AnatomyDD handleSelect={this.setFilters} selection={ anatomy } />
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </section>
                            <div className="flex">
                                {data.searchClasses.map(single => (
                                    <Class class={single} key={single.entry_id} />
                                ))}
                            </div>
                        </div>
                    )
                }}
            </Query>
        );
    }
}

export default Classes;

client/src/components/Search.js

class SearchBar extends Component {

    render() {
        return (
            <div className="ygi-search-bar col col-12 col-lg-2">
                <div className="ygi-search-bar__wrapper mt-2">
                    <input onChange={this.props.handleSearch} placeholder="Search" value={this.props.search} type="text" className="ygi-search-bar__input" />
                    <a href="true" className="icon-wrapper ygi-search-bar__icon-wrapper" style={{ bottom: "calc(50%)" }}>
                        <svg role="presentation" className="icon-search-grey">
                            <use xlinkHref="#icon-search-grey"></use>
                        </svg>
                    </a>
                </div>
            </div>
        );
    }
}

SearchBar.propTypes = {
    handleSearch: PropTypes.func,
    text: PropTypes.string
};

export default SearchBar;
1
Please post your code in your question, not in an external linkJoel

1 Answers

1
votes

It looks like the problem is with your resolver for the searchClasses query. Judging by the code in your question, your resolver is always going to return the same thing (classes):

const resolvers = {
    Query: {
        getClasses: () => classes,
        searchClasses: () => classes // always returning all classes!
    }
};

You need to update your resolver to filter the classes based on the passed in search string. Try something like this (I don't know what your class data structure looks like, so I'm just guessing here):

const resolvers = {
    Query: {
        getClasses: () => classes,
        searchClasses: (obj, args, context, info) => classes.filter(
            class => class.title === args.search
        )
    }
};