Skip to content

Commit

Permalink
Pass query params to Switch components as props
Browse files Browse the repository at this point in the history
  • Loading branch information
jdlehman committed Jul 19, 2017
1 parent f9146cd commit 797be1b
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 61 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ You can try out `switcheroo` now on [jsbin](https://jsbin.com/qusomol/edit?js,ou
- Supports hashChange and pushState
- Provides callbacks including when the path [changes](docs/Switcher.md#onchange)
- Supports [dynamic path segments](docs/dynamic_segments.md) and passes dynamic segment data to "Switch" component as props.
- Passes query parameters to "Switch" component as props.
- Supports [React animations](https://facebook.github.io/react/docs/animation.html) via [`wrapper`](docs/Switcher.md#wrapper) prop
- Highly configurable via props
- Lightweight ~2k gzipped
Expand Down
6 changes: 3 additions & 3 deletions docs/Switcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,18 @@ By default `window.location.hash` is used to match paths. If `location` is set t

### onChange

`onChange` enables a hook to call the provided function whenever the path changes. The function is provided 3 arguments, the first being a boolean of whether the path had a match, the second being the path as a string, the third being the values of the matched [dynamic segments](./dynamic_segments) (`/:id`, etc), and the fourth being the activePath (which is the matching path RegEx string specified on the Switch).
`onChange` enables a hook to call the provided function whenever the path changes. The function is provided 5 arguments, the first being a boolean of whether the path had a match, the second being the path as a string, the third being the values of the matched [dynamic segments](./dynamic_segments) (`/:id`, etc), the fourth being the activePath (which is the matching path RegEx string specified on the Switch), and the fifth being an object containing the keys and values of the query parameters in the path.

```js
onChange(match, pathname, dynamicSegments, activePath) { ... }
onChange(match, pathname, dynamicSegments, activePath, queryParams) { ... }
```

### renderSwitch

A function that receives the selected element as well as the dynamic values to render and the activePath. If this prop is passed, the wrapper prop will not be used.

```js
renderSwitch(selectedElement, dynamicData, activePath) {
renderSwitch(selectedElement, dynamicData, activePath, queryParams) {
return (<div>{selectedElement}</div>) //the rendered output of the Switcher
}
```
Expand Down
29 changes: 16 additions & 13 deletions src/Switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,19 @@ export default class Switcher extends Component {
constructor(props) {
super(props);

const currPath = currentPath(props.location);
const visibleSwitch = getSwitch(currPath, props);
const activePath = getActivePath(currPath, props.basePath, visibleSwitch);
const { path, params } = currentPath(props.location);
const visibleSwitch = getSwitch(path, props);
const activePath = getActivePath(path, props.basePath, visibleSwitch);
const dynamicValues = getDynamicSegments(
currPath,
path,
props.basePath,
visibleSwitch
);
this.state = {
visibleSwitch,
dynamicValues,
activePath
activePath,
params
};
}

Expand Down Expand Up @@ -136,20 +137,20 @@ export default class Switcher extends Component {
}

handleSwitchChange = props => {
const currPath = currentPath(props.location);
const visibleSwitch = getSwitch(currPath, props);
const activePath = getActivePath(currPath, props.basePath, visibleSwitch);
const { path, params } = currentPath(props.location);
const visibleSwitch = getSwitch(path, props);
const activePath = getActivePath(path, props.basePath, visibleSwitch);
const dynamicValues = getDynamicSegments(
currPath,
path,
props.basePath,
visibleSwitch
);

if (typeof props.onChange === 'function') {
props.onChange(!!visibleSwitch, currPath, dynamicValues, activePath);
props.onChange(!!visibleSwitch, path, dynamicValues, activePath, params);
}

this.setState({ visibleSwitch, dynamicValues, activePath });
this.setState({ visibleSwitch, dynamicValues, activePath, params });
};

handleRouteChange = ev => {
Expand All @@ -163,14 +164,16 @@ export default class Switcher extends Component {
React.cloneElement(this.state.visibleSwitch, {
...props,
...this.props.mapDynamicSegments(this.state.dynamicValues),
activePath: this.state.activePath
activePath: this.state.activePath,
params: this.state.params
});

if (this.props.renderSwitch) {
return this.props.renderSwitch(
visibleSwitch,
this.state.dynamicValues,
this.state.activePath
this.state.activePath,
this.state.params
);
}

Expand Down
21 changes: 15 additions & 6 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ export function generateGuid() {
}

export function currentPath(location) {
const path = decodeURI(window.location[location].slice(1).split('?')[0]);
if (path.charAt(0) !== '/') {
return `/${path}`;
} else {
return path;
}
const [windowLocation, paramString] = window.location[location]
.slice(1)
.split('?');
const params = (paramString || '')
.split('&')
.map(param => param.split('='))
.reduce((obj, [key, val]) => {
obj[key] = val;
return obj;
}, {});

const path = decodeURI(windowLocation);
return path.charAt(0) !== '/'
? { path: `/${path}`, params }
: { path, params };
}

export function removeTrailingSlash(path) {
Expand Down
10 changes: 5 additions & 5 deletions test/SwitcherProvider_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@ import * as helpers from '../src/helpers';

describe('SwitcherProvider', () => {
it('renders correctly', () => {
sinon.stub(helpers, 'currentPath').returns('/');
sinon.stub(helpers, 'currentPath').returns({ path: '/', params: {} });
const component = renderComponent();
expect(component.text()).toEqual('HomeHome');
helpers.currentPath.restore();

sinon.stub(helpers, 'currentPath').returns('/second');
sinon.stub(helpers, 'currentPath').returns({ path: '/second', params: {} });
component.update();
expect(component.text()).toEqual('Second');
helpers.currentPath.restore();
});

it('wraps a span around multiple children', () => {
sinon.stub(helpers, 'currentPath').returns('/');
sinon.stub(helpers, 'currentPath').returns({ path: '/', params: {} });
const component = renderComponentWithManyChildren();
expect(component.find('.switcher-provider').length).toEqual(1);
expect(component.text()).toEqual('Another ChildHomeHome');
helpers.currentPath.restore();
});

it('removes event listeners when a child Switcher is unmounted', () => {
sinon.stub(helpers, 'currentPath').returns('/');
sinon.stub(helpers, 'currentPath').returns({ path: '/', params: {} });
const component = renderNested();
const innerSwitcher = component.find('Switcher').last();
const listenerId = innerSwitcher.node._id;
Expand All @@ -46,7 +46,7 @@ describe('SwitcherProvider', () => {
).not.toEqual(-1);
helpers.currentPath.restore();

sinon.stub(helpers, 'currentPath').returns('/hello');
sinon.stub(helpers, 'currentPath').returns({ path: '/hello', params: {} });
component.update();
expect(instance.switcherProvider.loadListeners.length).toEqual(1);
expect(instance.switcherProvider.hashChangeListeners.length).toEqual(1);
Expand Down
Loading

0 comments on commit 797be1b

Please sign in to comment.