Skip to content

Commit

Permalink
feat(hooks): add use-query-params
Browse files Browse the repository at this point in the history
  • Loading branch information
philibea committed Mar 12, 2021
1 parent dc04b39 commit 840be4e
Show file tree
Hide file tree
Showing 9 changed files with 699 additions and 10 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ scaleway-lib is a set of NPM packages used at Scaleway.
- [`@scaleway/countries`](./packages/countries/README.md): ISO 3166/3166-2 coutries JSON database
- [`@scaleway/eslint-config-react`](./packages/eslint-config-react/README.md): A shared eslint react opiniated configuration
- [`@scaleway/random-name`](./packages/random-name/README.md): A tiny utility to generate random names
- [`@scaleway/use-query-params`](./packages/use-query-params/README.md): A tiny react hook to read and update URLs query parameters
- [`@scaleway/regex`](./packages/regex/README.md): usefull regex named

## Development
Expand Down
2 changes: 1 addition & 1 deletion babel.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"presets": ["@babel/preset-env"],
"presets": ["@babel/preset-env","@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@babel/core": "^7.12.10",
"@babel/plugin-transform-runtime": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@babel/preset-react":"7.12.13",
"@commitlint/cli": "^12.0.0",
"@commitlint/config-conventional": "^12.0.0",
"@rollup/plugin-babel": "^5.2.2",
Expand Down
3 changes: 3 additions & 0 deletions packages/use-query-params/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/__tests__/**
src
!.npmignore
60 changes: 60 additions & 0 deletions packages/use-query-params/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# `@scaleway/use-query-params`

## A tiny hooks to handle use-query-params

## Install

```bash
$ yarn add @scaleway/use-query-params
```

## Usage

```js
import React from 'react'
import useQueryParams from '@scaleway/use-query-params'

// this component should be wrap with a Router
const Component = () => {
const { queryParams, setQueryParams } = useQueryParams()
const { user } = queryParams
const setUser = () => setQueryParams({ user: 'John' })
// ?user=John

return (
<>
<h1>User: {user}</h1>
<button onClick={setUser}>Set User John</button>
</>
)
}
```

## Usage with a custom updater

```js
import React from 'react'
import useQueryParams from '@scaleway/use-query-params'

// this component should be wrap with a Router
const Component = () => {
const updater = (prevState, nextState) => ({
...prevState,
...Object.keys(nextState).reduce(
(acc, key) => ({ ...acc, [key]: nextState[key].toUpperCase() }),
{},
),
})
const { queryParams, setQueryParams } = useQueryParams({ updater })
const { user } = queryParams
const setUser = () => setQueryParams({ user: 'John' })
// ?user=JOHN

return (
<>
<h1>User: {user}</h1>
<button onClick={setUser}>Set User John</button>
</>
)
}
```
45 changes: 45 additions & 0 deletions packages/use-query-params/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@scaleway/use-query-params",
"version": "1.0.0",
"description": "A small hook to handle params",
"keywords": [
"react",
"react-dom",
"reactjs",
"hooks",
"react-router-dom",
"params",
"query-params"
],
"main": "dist/index.js",
"module": "dist/module.js",
"browser": {
"dist/index.js": "./dist/index.browser.js",
"dist/module.js": "./dist/module.browser.js"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/scaleway/scaleway-lib",
"directory": "packages/use-query-params"
},
"license": "MIT",
"dependencies": {
"query-string": "^6.14.1"
},
"peerDependencies": {
"react": "17.x",
"react-dom": "17.x",
"react-router-dom": "5.x"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^5.1.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0"
}
}
158 changes: 158 additions & 0 deletions packages/use-query-params/src/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { act, cleanup, renderHook } from '@testing-library/react-hooks'
import React from 'react'
import { MemoryRouter } from 'react-router-dom'
import useQueryParam from '../index'

// eslint-disable-next-line react/prop-types
const wrapper = ({ pathname = 'one', search }) => ({ children }) => (
<MemoryRouter initialIndex={0} initialEntries={[{ pathname, search }]}>
{children}
</MemoryRouter>
)

describe('useQueryParam', () => {
afterEach(cleanup)
it('should set one object', async () => {
const { result } = renderHook(() => useQueryParam(), {
wrapper: MemoryRouter,
})

act(() => {
result.current.setQueryParams({ user: 'John' })
})
expect(result.current.queryParams).toEqual({ user: 'John' })
})

it('should correctly set with different value', async () => {
const { result } = renderHook(() => useQueryParam(), {
wrapper: MemoryRouter,
})

act(() => {
result.current.setQueryParams({ user: 'John' })
})
expect(result.current.queryParams).toEqual({ user: 'John' })

act(() => {
result.current.setQueryParams({ user: 'Doe' })
})
expect(result.current.queryParams).toEqual({ user: 'Doe' })

act(() => {
result.current.setQueryParams({ user: 'Scaleway' })
})
expect(result.current.queryParams).toEqual({ user: 'Scaleway' })
})

it('should set one complexe object', async () => {
const { result } = renderHook(() => useQueryParam(), {
wrapper: MemoryRouter,
})

act(() => {
result.current.setQueryParams({
user: 'John Doe',
name: 'John',
lastName: 'Doe',
version: 1234,
lib: 'useQueryParams',
})
})
expect(result.current.queryParams).toEqual({
user: 'John Doe',
name: 'John',
lastName: 'Doe',
version: 1234,
lib: 'useQueryParams',
})
})

it('should get queryParams from existing location', () => {
const { result } = renderHook(() => useQueryParam(), {
wrapper: wrapper({ search: 'user=john' }),
})

expect(result.current.queryParams).toEqual({ user: 'john' })
})
it('should should handle array, boolean, number and string from existing location', () => {
const { result } = renderHook(() => useQueryParam(), {
wrapper: wrapper({
search: 'string=john&boolean=true&number=123&array=handle,array,format',
}),
})

expect(result.current.queryParams).toEqual({
string: 'john',
boolean: true,
number: 123,
array: ['handle', 'array', 'format'],
})
})

it('should get queryParams from existing location and set new params', () => {
const { result } = renderHook(() => useQueryParam(), {
wrapper: wrapper({ search: 'user=john' }),
})

expect(result.current.queryParams).toEqual({ user: 'john' })

act(() => {
result.current.setQueryParams({
lastName: 'Doe',
version: 1234,
lib: 'useQueryParams',
})
})

expect(result.current.queryParams).toEqual({
user: 'john',
lastName: 'Doe',
version: 1234,
lib: 'useQueryParams',
})
})

it('should modify updater and erase old params', () => {
const updater = (prevState, nextState) => nextState
const { result } = renderHook(() => useQueryParam({ updater }), {
wrapper: wrapper({ search: 'user=john' }),
})

expect(result.current.queryParams).toEqual({ user: 'john' })
act(() => {
result.current.setQueryParams({
lastName: 'Doe',
})
})
expect(result.current.queryParams).toEqual({
lastName: 'Doe',
})
})

it('should modify updater and uppercase all news params', () => {
const updater = (prevState, nextState) => ({
...prevState,
...Object.keys(nextState).reduce(
(acc, key) => ({ ...acc, [key]: nextState[key].toUpperCase() }),
{},
),
})

const { result } = renderHook(() => useQueryParam({ updater }), {
wrapper: wrapper({ search: 'user=john' }),
})

expect(result.current.queryParams).toEqual({ user: 'john' })

act(() => {
result.current.setQueryParams({
lastName: 'Doe',
})
})

expect(result.current.queryParams).toEqual({
user: 'john',
lastName: 'DOE',
})
})
})
67 changes: 67 additions & 0 deletions packages/use-query-params/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { parse, stringify } from 'query-string'
import { useCallback, useEffect, useState } from 'react'
import { useHistory } from 'react-router-dom'

const useQueryParams = (options = {}) => {
const { updater = {} } = options
const {
location: { search, pathname },
replace,
} = useHistory()

const parseFormat = useCallback(
() =>
parse(search, {
parseNumbers: true,
parseBooleans: true,
arrayFormat: 'comma',
}),
[search],
)

const stringyFormat = useCallback(
params =>
stringify(params, {
arrayFormat: 'comma',
sort: (a, b) => a.localeCompare(b),
}),
[],
)

const defaultFnUpdater = useCallback(
(currentQueryParams, nextQueryParams) => ({
...currentQueryParams,
...nextQueryParams,
}),
[],
)
const [state, setState] = useState(parseFormat())

const setQueryParams = useCallback(
nextParams => {
const currentQueryParams = parseFormat()
const params =
updater instanceof Function
? updater(currentQueryParams, nextParams)
: defaultFnUpdater(currentQueryParams, nextParams)

setState(params)
},
[parseFormat, updater, defaultFnUpdater],
)

useEffect(() => {
const stringifiedParams = stringyFormat(state)
if (stringifiedParams !== search.replace('?', '')) {
replace(`${pathname}?${stringifiedParams}`)
}
}, [pathname, replace, search, state, stringyFormat])


return {
queryParams: state,
setQueryParams,
}
}

export default useQueryParams
Loading

0 comments on commit 840be4e

Please sign in to comment.