Skip to content

Commit

Permalink
fix(rangeinput): allow input of numbers with precision (#5541)
Browse files Browse the repository at this point in the history
Co-authored-by: Fabien Motte <fabien.motte@algolia.com>
Co-authored-by: Dhaya <154633+dhayab@users.noreply.github.com>
  • Loading branch information
3 people authored May 11, 2023
1 parent 30edccd commit fb48951
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 38 deletions.
2 changes: 1 addition & 1 deletion examples/react-hooks/default-theme/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function App() {
/>
</Panel>
<Panel header="Price">
<RangeInput attribute="price" />
<RangeInput attribute="price" precision={1} />
</Panel>
<Panel header="Free Shipping">
<ToggleRefinement
Expand Down
19 changes: 19 additions & 0 deletions packages/instantsearch.js/src/__tests__/common.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
createMenuTests,
createInfiniteHitsTests,
createHitsTests,
createRangeInputTests,
} from '@instantsearch/tests';

import instantsearch from '../index.es';
Expand All @@ -22,6 +23,7 @@ import {
searchBox,
hits,
index,
rangeInput,
} from '../widgets';

createHierarchicalMenuTests(({ instantSearchOptions, widgetParams }) => {
Expand Down Expand Up @@ -243,3 +245,20 @@ createHitsTests(({ instantSearchOptions, widgetParams }) => {
})
.start();
});

createRangeInputTests(({ instantSearchOptions, widgetParams }) => {
instantsearch(instantSearchOptions)
.addWidgets([
rangeInput({
container: document.body.appendChild(document.createElement('div')),
...widgetParams,
}),
])
.on('error', () => {
/*
* prevent rethrowing InstantSearch errors, so tests can be asserted.
* IRL this isn't needed, as the error doesn't stop execution.
*/
})
.start();
});
32 changes: 22 additions & 10 deletions packages/instantsearch.js/src/components/RangeInput/RangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,43 @@ export type RangeInputProps = {
refine: (rangeValue: RangeBoundaries) => void;
};

class RangeInput extends Component<RangeInputProps, Partial<Range>> {
// Strips leading `0` from a positive number value
function stripLeadingZeroFromInput(value: string): string {
return value.replace(/^(0+)\d/, (part) => Number(part).toString());
}

class RangeInput extends Component<
RangeInputProps,
{ min?: string; max?: string }
> {
public state = {
min: this.props.values.min,
max: this.props.values.max,
min: this.props.values.min?.toString(),
max: this.props.values.max?.toString(),
};

public componentWillReceiveProps(nextProps: RangeInputProps) {
this.setState({
min: nextProps.values.min,
max: nextProps.values.max,
min: nextProps.values.min?.toString(),
max: nextProps.values.max?.toString(),
});
}

private onInput = (key: string) => (event: Event) => {
private onInput = (key: keyof typeof this.state) => (event: Event) => {
const { value } = event.currentTarget as HTMLInputElement;

this.setState({
[key]: Number(value),
[key]: value,
});
};

private onSubmit = (event: Event) => {
event.preventDefault();

this.props.refine([this.state.min, this.state.max]);
const { min, max } = this.state;
this.props.refine([
min ? Number(min) : undefined,
max ? Number(max) : undefined,
]);
};

public render() {
Expand All @@ -79,7 +91,7 @@ class RangeInput extends Component<RangeInputProps, Partial<Range>> {
min={min}
max={max}
step={step}
value={minValue ?? ''}
value={stripLeadingZeroFromInput(minValue ?? '')}
onInput={this.onInput('min')}
placeholder={min?.toString()}
disabled={isDisabled}
Expand All @@ -102,7 +114,7 @@ class RangeInput extends Component<RangeInputProps, Partial<Range>> {
min={min}
max={max}
step={step}
value={maxValue ?? ''}
value={stripLeadingZeroFromInput(maxValue ?? '')}
onInput={this.onInput('max')}
placeholder={max?.toString()}
disabled={isDisabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ describe('RangeInput', () => {

expect(component).toMatchSnapshot();
expect(component.state()).toEqual({
min: 20,
max: 480,
min: '20',
max: '480',
});
});

Expand Down Expand Up @@ -92,8 +92,8 @@ describe('RangeInput', () => {

expect(component).toMatchSnapshot();
expect(component.state()).toEqual({
min: 20,
max: 480,
min: '20',
max: '480',
});
});

Expand All @@ -120,8 +120,8 @@ describe('RangeInput', () => {

expect(component).toMatchSnapshot();
expect(component.state()).toEqual({
min: 20,
max: 480,
min: '20',
max: '480',
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ exports[`RangeInput expect to render with values 1`] = `
placeholder="0"
step={1}
type="number"
value={20}
value="20"
/>
</label>
<Template
Expand Down Expand Up @@ -213,7 +213,7 @@ exports[`RangeInput expect to render with values 1`] = `
placeholder="500"
step={1}
type="number"
value={480}
value="480"
/>
</label>
<Template
Expand Down Expand Up @@ -340,7 +340,7 @@ exports[`RangeInput willReceiveProps expect to update the empty state from given
placeholder="0"
step={1}
type="number"
value={20}
value="20"
/>
</label>
<Template
Expand Down Expand Up @@ -373,7 +373,7 @@ exports[`RangeInput willReceiveProps expect to update the empty state from given
placeholder="500"
step={1}
type="number"
value={480}
value="480"
/>
</label>
<Template
Expand Down Expand Up @@ -420,7 +420,7 @@ exports[`RangeInput willReceiveProps expect to update the state from given props
placeholder="0"
step={1}
type="number"
value={40}
value="40"
/>
</label>
<Template
Expand Down Expand Up @@ -453,7 +453,7 @@ exports[`RangeInput willReceiveProps expect to update the state from given props
placeholder="500"
step={1}
type="number"
value={460}
value="460"
/>
</label>
<Template
Expand Down Expand Up @@ -500,7 +500,7 @@ exports[`RangeInput willReceiveProps expect to update the state from given props
placeholder="0"
step={1}
type="number"
value={20}
value="20"
/>
</label>
<Template
Expand Down Expand Up @@ -533,7 +533,7 @@ exports[`RangeInput willReceiveProps expect to update the state from given props
placeholder="500"
step={1}
type="number"
value={480}
value="480"
/>
</label>
<Template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
createPaginationTests,
createInfiniteHitsTests,
createHitsTests,
createRangeInputTests,
} from '@instantsearch/tests';
import { act, render } from '@testing-library/react';
import React from 'react';
Expand All @@ -25,6 +26,7 @@ import {
useInstantSearch,
Hits,
Index,
RangeInput,
} from '..';

import type { Hit } from 'instantsearch.js';
Expand Down Expand Up @@ -204,3 +206,12 @@ createHitsTests(({ instantSearchOptions, widgetParams }) => {
</InstantSearch>
);
}, act);

createRangeInputTests(({ instantSearchOptions, widgetParams }) => {
render(
<InstantSearch {...instantSearchOptions}>
<RangeInput {...widgetParams} />
<GlobalErrorSwallower />
</InstantSearch>
);
}, act);
42 changes: 29 additions & 13 deletions packages/react-instantsearch-hooks-web/src/ui/RangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,12 @@ export type RangeInputTranslations = {
};

// if the default value is undefined, React considers the component uncontrolled initially, which we don't want 0 or NaN as the default value
const unsetNumberInputValue = '' as unknown as number;
const unsetNumberInputValue = '';

// Strips leading `0` from a positive number value
function stripLeadingZeroFromInput(value: string): string {
return value.replace(/^(0+)\d/, (part) => Number(part).toString());
}

export function RangeInput({
classNames = {},
Expand All @@ -91,12 +96,12 @@ export function RangeInput({
const [prevValues, setPrevValues] = useState(values);

const [{ from, to }, setRange] = useState({
from: values.min,
to: values.max,
from: values.min?.toString(),
to: values.max?.toString(),
});

if (values.min !== prevValues.min || values.max !== prevValues.max) {
setRange({ from: values.min, to: values.max });
setRange({ from: values.min?.toString(), to: values.max?.toString() });
setPrevValues(values);
}

Expand All @@ -114,7 +119,10 @@ export function RangeInput({
className={cx('ais-RangeInput-form', classNames.form)}
onSubmit={(event) => {
event.preventDefault();
onSubmit([from, to]);
onSubmit([
from ? Number(from) : undefined,
to ? Number(to) : undefined,
]);
}}
>
<label className={cx('ais-RangeInput-label', classNames.label)}>
Expand All @@ -128,13 +136,17 @@ export function RangeInput({
type="number"
min={min}
max={max}
value={from?.toString()} // Strips leading `0` from a positive number value
value={stripLeadingZeroFromInput(from || unsetNumberInputValue)}
step={step}
placeholder={min?.toString()}
disabled={disabled}
onInput={({ currentTarget }) =>
setRange({ from: Number(currentTarget.value), to })
}
onInput={({ currentTarget }) => {
const value = currentTarget.value;
setRange({
from: value || unsetNumberInputValue,
to,
});
}}
/>
</label>
<span className={cx('ais-RangeInput-separator', classNames.separator)}>
Expand All @@ -151,13 +163,17 @@ export function RangeInput({
type="number"
min={min}
max={max}
value={to?.toString()} // Strips leading `0` from a positive number value
value={stripLeadingZeroFromInput(to || unsetNumberInputValue)}
step={step}
placeholder={max?.toString()}
disabled={disabled}
onInput={({ currentTarget }) =>
setRange({ from, to: Number(currentTarget.value) })
}
onInput={({ currentTarget }) => {
const value = currentTarget.value;
setRange({
from,
to: value || unsetNumberInputValue,
});
}}
/>
</label>
<button
Expand Down
18 changes: 18 additions & 0 deletions packages/vue-instantsearch/src/__tests__/common.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
createPaginationTests,
createInfiniteHitsTests,
createHitsTests,
createRangeInputTests,
} from '@instantsearch/tests';

import { nextTick, mountApp } from '../../test/utils';
Expand All @@ -25,6 +26,7 @@ import {
createWidgetMixin,
AisHits,
AisIndex,
AisRangeInput,
} from '../instantsearch';
jest.unmock('instantsearch.js/es');

Expand Down Expand Up @@ -280,3 +282,19 @@ createHitsTests(async ({ instantSearchOptions, widgetParams }) => {

await nextTick();
});

createRangeInputTests(async ({ instantSearchOptions, widgetParams }) => {
mountApp(
{
render: renderCompat((h) =>
h(AisInstantSearch, { props: instantSearchOptions }, [
h(AisRangeInput, { props: widgetParams }),
h(GlobalErrorSwallower),
])
),
},
document.body.appendChild(document.createElement('div'))
);

await nextTick();
});
1 change: 1 addition & 0 deletions tests/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './widgets/menu';
export * from './widgets/pagination';
export * from './widgets/infinite-hits';
export * from './widgets/hits';
export * from './widgets/range-input';
Loading

0 comments on commit fb48951

Please sign in to comment.