Skip to content

Commit

Permalink
Breaking changes
Browse files Browse the repository at this point in the history
- Remove initialSearch prop from FilterResults (this is now in sync with
  initialSearch from InputFilter)
- onChange callback for InputFilter can now optionally return a string,
  which overrides the search value passed to FilterResults. This allows
  special inputs/commands to be parsed out and treated differently (eg:
  "author:jdlehman", could be parsed, used to filter the items being
  passed into FilterResults, then stripped from the returned
  value so that it is not used for fuzzy searching.
  • Loading branch information
jdlehman committed Aug 2, 2016
1 parent 04a4ad3 commit eb200b5
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 86 deletions.
15 changes: 5 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ class MyComponent extends Component {

render() {
const items = [
{ name: 'first', meta: 'first|123' },
{ name: 'second', meta: 'second|443' },
{ name: 'third', meta: 'third|623' },
{ name: 'first', meta: 'first|123', tag: 'a' },
{ name: 'second', meta: 'second|443', tag: 'b' },
{ name: 'third', meta: 'third|623', tag: 'a' },
];
const fuseConfig = {
keys: ['meta']
keys: ['meta', 'tag']
};
return (
<div>
Expand Down Expand Up @@ -76,7 +76,7 @@ An input field that controls the state used to render the items in `FilterResult

### onChange

`onChange` is an optional callback function that is called BEFORE the value in the input field changes via an `onchange` event. If it returns `false`, the new value will not be propagated to the shared state. (returning nothing or any other return will propagate the state).
`onChange` is an optional callback function that is called BEFORE the value in the input field changes via an `onchange` event. It can optionally return a string, which will then be passed directly to `FilterResults` rather than the original string. This can be used to filter out special inputs (eg: `author:jdlehman`) from fuzzy searching. These special inputs could then be used to change the `items` being passed to `FilterResults`.


# FilterResults
Expand Down Expand Up @@ -105,11 +105,6 @@ Collection of fuzzy filtered items (filtered by the `InputFilter`'s value), each

`classPrefix` is a string that is used to prefix the class names in the component. It defaults to `react-fuzzy-filter`. (`react-fuzzy-filter__results-container`)


### initialSearch

`initialSearch` is a string that can override the initial search state when the component is created. It defaults to `''`.

### wrapper

`wrapper` is an optional component that will wrap the results if defined. This will be used as the wrapper around the items INSTEAD of `react-fuzzy-filter__results-container`.
Expand Down
2 changes: 1 addition & 1 deletion dist/react-fuzzy-filter.min.js

Large diffs are not rendered by default.

24 changes: 10 additions & 14 deletions src/FilterResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export default function filterResultsFactory(store) {
classPrefix: PropTypes.string,
wrapper: PropTypes.any,
wrapperProps: PropTypes.object,
initialSearch: PropTypes.string,
renderContainer: PropTypes.func,
fuseConfig: PropTypes.shape({
keys: PropTypes.array.isRequired,
Expand All @@ -35,35 +34,32 @@ export default function filterResultsFactory(store) {
static defaultProps = {
defaultAllItems: true,
classPrefix: 'react-fuzzy-filter',
wrapperProps: {},
initialSearch: ''
wrapperProps: {}
};

state = {
search: this.props.initialSearch,
fuse: new Fuse(this.props.items, this.props.fuseConfig)
search: null
};

componentDidMount() {
this.subscription = store.subscribe(search => this.setState({search}));
}

componentWillReceiveProps({items, fuseConfig}) {
this.setState({fuse: new Fuse(items, fuseConfig)});
}

componentWillUnmount() {
this.subscription.unsubscribe();
}

renderItems() {
let items;
filterItems() {
if (!this.state.search || this.state.search.trim() === '') {
items = this.props.defaultAllItems ? this.props.items : [];
return this.props.defaultAllItems ? this.props.items : [];
} else {
items = this.state.fuse.search(this.state.search);
const fuse = new Fuse(this.props.items, this.props.fuseConfig);
return fuse.search(this.state.search);
}
return items.map((item, i) => this.props.renderItem(item, i));
}

renderItems() {
return this.filterItems().map((item, i) => this.props.renderItem(item, i));
}

render() {
Expand Down
19 changes: 12 additions & 7 deletions src/InputFilter.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@ export default function inputFilterFactory(store) {
onChange: function() {}
};

state = {
search: this.props.initialSearch
};
componentDidMount() {
let value = this.props.initialSearch;
const overrideValue = this.props.onChange(value);
if (typeof overrideValue === 'string') {
value = overrideValue;
}
store.next(value);
}

handleChange = ({target: {value}}) => {
const continueChange = this.props.onChange(value);
if (continueChange !== false) {
this.setState({search: value});
store.next(value);
const overrideValue = this.props.onChange(value);
if (typeof overrideValue === 'string') {
value = overrideValue;
}
store.next(value);
};

render() {
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {Subject} from 'rxjs/Subject';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import inputFilterFactory from './InputFilter';
import filterResultsFactory from './FilterResults';

export default function fuzzyFilterFactory() {
const store = new Subject();
const store = new BehaviorSubject();
return {
InputFilter: inputFilterFactory(store),
FilterResults: filterResultsFactory(store)
Expand Down
12 changes: 0 additions & 12 deletions test/FilterResults_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,5 @@ describe('FilterResults', () => {
expect(component.find('.my-item').length).toEqual(1);
expect(component.find('.my-item').at(0).text()).toEqual('three');
});

it('can override the initial search value with initialSearch prop', () => {
const component = shallow(
<FilterResults
fuseConfig={defaultFuseConfig}
items={items}
renderItem={defaultRender}
initialSearch="hello"
/>
);
expect(component.find('.my-item').length).toEqual(2);
});
});
});
27 changes: 9 additions & 18 deletions test/InputFilter_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import inputFilterFactory from '../src/InputFilter';

describe('InputFilter', () => {
let InputFilter;
const store = new Subject();
let store;
beforeEach(() => {
store = new Subject();
InputFilter = inputFilterFactory(store);
});

describe('#render', () => {
it('renders with defaults', () => {
const component = shallow(<InputFilter />);
expect(component.find('.react-fuzzy-filter__input').length).toEqual(1);
expect(component.state()).toEqual({search: undefined});
});

it('sets classPrefix', () => {
Expand All @@ -31,7 +31,7 @@ describe('InputFilter', () => {

it('sets initialSearch', () => {
const component = shallow(<InputFilter initialSearch="first search" />);
expect(component.state()).toEqual({search: 'first search'});
expect(component.find('input').html()).toEqual('<input class="react-fuzzy-filter__input" value="first search"/>');
});
});

Expand All @@ -50,18 +50,6 @@ describe('InputFilter', () => {
expect(spy).toHaveBeenCalledWith('my string');
});

it('sets the state', () => {
component.find('input').simulate('change', {
target: {value: 'first'}
});
expect(component.state()).toEqual({search: 'first'});

component.find('input').simulate('change', {
target: {value: 'second'}
});
expect(component.state()).toEqual({search: 'second'});
});

it('passes the value to the store', (done) => {
store.subscribe(data => {
expect(data).toEqual('some input');
Expand All @@ -73,12 +61,15 @@ describe('InputFilter', () => {
});
});

it('does not set state or pass value to the store if onChange returns false', () => {
component = shallow(<InputFilter onChange={() => false} />);
it('overrides search value with any return value to onChange', (done) => {
store.subscribe(data => {
expect(data).toEqual('hello');
done();
});
component = shallow(<InputFilter onChange={() => 'hello'} />);
component.find('input').simulate('change', {
target: {value: 'some input'}
});
expect(component.state()).toEqual({search: undefined});
});
});
});
59 changes: 37 additions & 22 deletions test/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ const defaultFuseConfig = {
keys: ['searchData']
};

function defaultRender({name}, index) {
return <div key={name} className="my-item">{name}: {index}</div>;
}
defaultRender.propTypes = {
name: PropTypes.string.isRequired
};

function componentFactory(inputFilterProps, filterResultsProps) {
const {InputFilter, FilterResults} = fuzzyFilterFactory();
function MyComponent() {
return (
<div>
<h2>Separate Components</h2>
<InputFilter {...inputFilterProps} />
<h4>Any amount of content between</h4>
<FilterResults {...filterResultsProps} />
</div>
);
}

return MyComponent;
}

describe('fuzzyFilterFactory', () => {
it('returns FilterResults and InputFilter components', () => {
const {InputFilter, FilterResults} = fuzzyFilterFactory();
Expand All @@ -24,28 +47,10 @@ describe('fuzzyFilterFactory', () => {
});

it('input controls filter results', () => {
const {InputFilter, FilterResults} = fuzzyFilterFactory();
function MyComponent() {
return (
<div>
<h2>Separate Components</h2>
<InputFilter inputProps={{placeholder: 'Search'}} />
<h4>Any amount of content between</h4>
<FilterResults
items={items}
fuseConfig={defaultFuseConfig}
renderItem={defaultRender}
/>
</div>
);
}

function defaultRender({name}, index) {
return <div key={name} className="my-item">{name}: {index}</div>;
}
defaultRender.propTypes = {
name: PropTypes.string.isRequired
};
const MyComponent = componentFactory(
{placeholder: 'Search'},
{items: items, fuseConfig: defaultFuseConfig, renderItem: defaultRender}
);
const component = mount(<MyComponent />);
expect(component.find('.react-fuzzy-filter__results-container').length).toEqual(1);
expect(component.find('.my-item').length).toEqual(4);
Expand All @@ -60,4 +65,14 @@ describe('fuzzyFilterFactory', () => {
});
expect(component.find('.my-item').length).toEqual(1);
});

it('uses initialSearch', () => {
const MyComponent = componentFactory(
{placeholder: 'Search', initialSearch: 'gdbye'},
{items: items, fuseConfig: defaultFuseConfig, renderItem: defaultRender}
);
const component = mount(<MyComponent />);
expect(component.find('.react-fuzzy-filter__results-container').length).toEqual(1);
expect(component.find('.my-item').length).toEqual(1);
});
});

0 comments on commit eb200b5

Please sign in to comment.