How to Implement React Redux Search and Checkbox Filter

Viewed 292

I am trying to implement search/filter in my react project but can't seem to get it right. Screenshot

This is what the user should be able to do:

  1. Filter by text
  2. Filter by location using the checkbox
  3. or filter by job category

What I have right now is that I can filter by both text and checkbox, but I want the user to be able to search by text, e.g., "Dallas", "information technology", "sales" and then filter further by category or by location using the returned data from the text search.

This is my set up:

  1. Searching by text uses a reducer to filter the array by search text
  2. Checking a location or category checkbox stores the value in the category or keyword array
  3. In the combine reducer file, I filter the jobs array in the store and memorize it using reselect.

I have a couple of glitches with the search text and checkbox functionalities. The filter by location checkbox functionality works fine by itself; however, when a checkbox is selected, and I enter search text, checkbox gets deselected. I want the checkbox to be still checked and displayed in the window when text is entered in the input field.

I'm a newbie, any help, suggestions, or a better approach to this functionality will be greatly appreciated.

SEARCHFORM.JS:

import React, {Component} from 'react';
//import {FormGroup, FormControl, InputGroup} from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { searchJobs, getJobs } from '../actions/jobaction';
import {selectKeywordCity,deselectKeywordCity} from '../actions/selectDeselectCityAction';
import {selectKeywordCategory,deselectKeywordCategory} from '../actions/selectDeselectCategoryAction';

class Search extends Component{
    constructor(props){
        super();
        this.state = {
            keyword:'',
            checked:[]
        };
        this.handleChk = this.handleChk.bind(this);
    }

    handleInput = (event) => {
        this.setState({
            keyword: event.target.value
        });
        this.props.searchJobs(this.state.keyword);
    }

/*  handleSearch = (event) => {
        event.preventDefault();
        this.props.getJobs(this.state.keyword);
    } */

    clearForm = () => {
        this.inputSearch.value = "";
        this.setState({
            keyword: this.inputSearch.value
        });
        this.props.getJobs();
    }

    handleChk = (e, keyword, type) => {
        if(type==='city'){
            e.target.checked ? this.props.selectKeywordCity(keyword) : this.props.deselectKeywordCity(keyword);
        }else if(type==='category'){
            e.target.checked ? this.props.selectKeywordCategory(keyword) : this.props.deselectKeywordCategory(keyword);
        }

    }

    //Render Cities
    renderCities(data){
        if(data){
            const cities = Object.keys(data).map(el => {
                    return data[el].city
                }).sort()
                .reduce((city,name) => {
                    const cityCount = city[name] ? city[name] + 1 : 1;

                    return{
                        ...city, [name]:cityCount,
                    };

                },{});
            return Object.keys(cities).map((keyname, index) => {
                    return (
                        <li key={keyname}>
                            <input type="checkbox" 
                            onChange={e=>this.handleChk(e,keyname,'city')}
                            className="chkbox"
                        />
                                {keyname}
                            <span className="pull-right">{cities[keyname]}</span> 
                        </li>)
                    })
        }else{
                return("nada")
        }
    }

    //Render Job Categories

    renderCategories(data){
        if(data){
            const categories = Object.keys(data).map(el => {
                    return data[el].category
                }).sort()
                .reduce((category,name) => {
                    const categoryCount = category[name] ? category[name] + 1 : 1;

                    return{
                        ...category, [name]:categoryCount,
                    };

                },{});
            return Object.keys(categories).map((keyname, index) => {
                    return (
                        <li key={keyname}>
                            <input type="checkbox" 
                            onChange={e=>this.handleChk(e,keyname,'category')}
                            className="chkbox"
                        />
                                {keyname}
                            <span className="pull-right">{categories[keyname]}</span> 
                        </li>)
                    })
        }else{
                return("nada")
        }
    }

    render(){

        let closeBtn = null;

        if(this.state.keyword){
            closeBtn = (<i className="fa fa-times" onClick={this.clearForm}></i>);
        }

        return(
            <div>
                <div className="side-search">
                    <i className="fa fa-search"></i>
                    <input type="text" 
                            placeholder="Keyword Search" 
                            ref={el => this.inputSearch = el}
                            onChange={this.handleInput}
                            value={this.state.keyword}
                    />

                    {closeBtn}
                </div>
                <div className="chk_boxes">
                    <h4>Location</h4>
                    <ul>
                        {
                            this.renderCities(this.props.jobs)
                        }
                    </ul>
                </div>
                <div className="chk_boxes">
                    <h4>Category</h4>
                    <ul>
                        {
                            this.renderCategories(this.props.jobs)
                        }
                    </ul>
                </div>
            </div>
            )
    }
}

function mapStateToProps(state){
    return{
        jobs: state.jobs.jobs
    }
}

function mapDispatchToProps(dispatch){
    return bindActionCreators({searchJobs, 
                               getJobs,
                               selectKeywordCity,
                               deselectKeywordCity,
                               selectKeywordCategory,
                               deselectKeywordCategory},
                               dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(Search);

JOBACTION:

import axios from 'axios';
const FETCH_URL = 'http://localhost:3006';

/* GET ALL JOB LISTINGS */
export function getJobs(){
    return function(dispatch){
        axios.get(`${FETCH_URL}/jobs`)
        .then(function(response){
            dispatch({type:'GET_JOBS',payload:response.data})
        }).catch(function(err){
            dispatch({type:'GET_JOBS_ERROR', payload:err});
        })
    }
}

/* GET ALL JOB LISTINGS */
export function searchJobs(keyword){
    return function(dispatch){
        axios.get(`${FETCH_URL}/jobs?q=${keyword}`)
        .then(function(response){
            dispatch({type:'SEARCH_JOBS',payload:response.data})
        }).catch(function(err){
            dispatch({type:'SEARCH_JOBS_ERROR', payload:err});
        })
    }
}

JOBSLIST REDUCER:

export function Listings (state={}, action){
    switch(action.type){
        case 'GET_JOBS':
        return{...state, jobs:action.payload}
        case 'SEARCH_JOBS':
        return{...state, jobs:action.payload}
        default:
        return state;
    }
}

SELECTDESELECT(CHECKBOX) REDUCER:

const INITIAL_STATE = {
    keywords:[]
}
export function filterCity(state=INITIAL_STATE, action){
    switch(action.type){
        case 'SELECT_CITY':
        return{
            keywords:[...state.keywords, action.payload]
        }
        case 'DESELECT_CITY':
        const currentKeywordToDelete = [...state.keywords];
        const indexOfKeyword = currentKeywordToDelete.findIndex(function(keyword){
            return keyword === action.payload
        });
        return{
            keywords:[...state.keywords.slice(0,indexOfKeyword), ...currentKeywordToDelete.slice(indexOfKeyword+1) ]
        }
        default:
            return state;
    }
}

COMBINEREDUCER:

    import {combineReducers} from 'redux';
import {createSelector} from 'reselect'; 
import {Listings} from './listingsReducer';
import {filterCity} from './select-deselect-city';
import {filterCategory} from './select-deselect-category';

const reducer = combineReducers({
    jobs: Listings,
    keywords: filterCity,
    category: filterCategory
});

export const selectJobList = (state) => state.jobs.jobs;
export const selectCities = (state) => state.keywords.keywords;
//export const selectCategories = (state) => state.categories.categories;

export  const selectFilteredJobs = createSelector(
    selectJobList,selectCities,
    (jobs,cities) => {

        if(cities && cities.length > 0){
            const filteredData = Object.keys(jobs)
                .filter(key => cities.includes(jobs[key].city))
                .reduce((obj, key) => {
                    return [...obj, {...jobs[key]}]
                  }, []);
                //console.log(filteredData);
            return filteredData;
        }else{
            return jobs
        }
    } 
)

export default reducer;

JSON DATA:

{
    "jobs": [
        {
          "id": 1,
          "title": "Financial Analyst II",
          "description": "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. .",
          "category": "Sales",
          "dept": "Finance",
          "date": "07/11/2018",
          "experience": ["Bachelors Degree","Five years of sales experience"],
          "city": "Dallas",
          "state":"Texas",
          "salary": "10,000"
       },
       {
          "id": 2,
          "title": "Application Support Speacialist",
          "description": "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.",
          "category": "Information Technology",
          "dept": "Accounting",
          "date": "07/10/2018",
          "experience": ["Bachelors Degree","Two years of sales experience"],
          "city": "Dallas",
          "state":"Texas",
          "salary": "20,000"
       }
}
0 Answers
Related