Skip to content

Commit b848557

Browse files
committed
improvement(useCookie): Adding ability to define serializer and deserializer
BREAKING CHANGE: The options object has changed radically
1 parent 8bfba2c commit b848557

File tree

14 files changed

+278
-13
lines changed

14 files changed

+278
-13
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Vue.use(VueCompositionAPI);
107107
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-usebeforeunload--demo)
108108
- [`useCookie`](./src/components/useCookie/stories/useCookie.md) — provides way to read, update and delete a cookie.
109109
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-usecookie--demo)
110+
- [`useLocalStorage`](./src/components/useLocalStorage/stories/useLocalStorage.md) — provides way to read, update and delete a localStorage key.
111+
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/side-effects-uselocalstorage--demo)
110112
- UI
111113
- [`useClickAway`](./src/components/useClickAway/stories/useClickAway.md) — triggers callback when user clicks outside target area.
112114
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/ui-useclickaway--demo)

src/components/useCookie/stories/UseCookieDemo.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export default Vue.extend({
5252
cookie: jsonCookie,
5353
setCookie: jsonSetCookie,
5454
removeCookie: jsonRemoveCookie
55-
} = useCookie('jsonCookie')
55+
} = useCookie('jsonCookie', {
56+
isParsing: true
57+
})
5658
5759
let counter = 0
5860
const handleSetCookie = () => {

src/components/useCookie/stories/useCookie.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,18 @@ Vue function that provides way to read, set and remove a cookie.
44

55
## Reference
66

7+
```typescript
8+
interface UseCookieOptions {
9+
isParsing: boolean;
10+
serializer?: (value: any) => string;
11+
deserializer?: (value: string) => any;
12+
}
13+
```
14+
715
```typescript
816
function useCookie(
917
cookieName: string,
10-
enableParseJSON?: boolean,
18+
options?: UseCookieOptions,
1119
runOnMount?: boolean
1220
): {
1321
cookie: Ref<any>
@@ -20,7 +28,10 @@ function useCookie(
2028
### Parameters
2129

2230
- `cookieName: string` the cookie name you wish to get/set/remove
23-
- `enableParseJSON: boolean` whether to enable JSON parsing or not, `false` by default
31+
- `options: UseCookieOptions`
32+
- `isParsing: boolean` whether to enable parsing the cookie value or not, `false` by default
33+
- `serializer: Function` a custom serializer, `JSON.stringify` by default
34+
- `deserializer: Function` a custom deserializer, `JSON.parse` by default
2435
- `runOnMount: boolean` whether to get the cookie on mount or not, `true` by default
2536

2637
### Returns
@@ -65,4 +76,5 @@ export default Vue.extend({
6576
}
6677
}
6778
})
79+
</script>
6880
```

src/components/useCookie/useCookie.spec.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ afterEach(() => {
99
const testComponent = (
1010
cookieName = 'cookieName',
1111
cookieValue: any = '',
12-
parseJson = false,
12+
opts = { isParsing: false } as any,
1313
onMount = true
1414
) => ({
1515
template: `
@@ -26,7 +26,7 @@ const testComponent = (
2626
setup() {
2727
const { cookie, getCookie, setCookie, removeCookie } = useCookie(
2828
cookieName,
29-
parseJson,
29+
opts,
3030
onMount
3131
)
3232

@@ -75,7 +75,9 @@ describe('useCookie', () => {
7575
it('should correctly get and set the parseToJson object', async () => {
7676
const cookieName = 'cookieName'
7777
const cookieValue = { value1: 'testValue1', value2: 'testValue2' }
78-
const wrapper = mount(testComponent(cookieName, cookieValue, true))
78+
const wrapper = mount(
79+
testComponent(cookieName, cookieValue, { isParsing: true })
80+
)
7981
wrapper.find('#setCookie').trigger('click')
8082
await wrapper.vm.$nextTick()
8183
expect(wrapper.find('#cookieJson').html()).toContain(cookieValue.value1)
@@ -87,4 +89,85 @@ describe('useCookie', () => {
8789
expect(wrapper.find('#cookieJson').html()).toContain(cookieValue.value1)
8890
expect(wrapper.find('#cookieJson').html()).toContain(cookieValue.value2)
8991
})
92+
93+
it('should correctly get using the deserializer', async () => {
94+
const cookieName = 'cookieName'
95+
const cookieValue = { value1: 'testValue1', value2: 'testValue2' }
96+
const deserializerVal = { value1: 'gatto', value2: 'topo' }
97+
const wrapper = mount(
98+
testComponent(cookieName, cookieValue, {
99+
isParsing: true,
100+
deserializer: () => deserializerVal
101+
})
102+
)
103+
await wrapper.vm.$nextTick()
104+
expect(wrapper.find('#cookieJson').html()).toContain(deserializerVal.value1)
105+
expect(wrapper.find('#cookieJson').html()).toContain(deserializerVal.value2)
106+
})
107+
108+
it('should ignore deserializer when isParsing is false', async () => {
109+
const cookieName = 'cookieName'
110+
const cookieValue = { value1: 'testValue1', value2: 'testValue2' }
111+
const deserializerVal = { value1: 'gatto', value2: 'topo' }
112+
const wrapper = mount(
113+
testComponent(cookieName, cookieValue, {
114+
isParsing: false,
115+
deserializer: () => deserializerVal
116+
})
117+
)
118+
await wrapper.vm.$nextTick()
119+
expect(wrapper.find('#cookie').html()).toContain(cookieValue.value1)
120+
expect(wrapper.find('#cookie').html()).toContain(cookieValue.value2)
121+
expect(wrapper.find('#cookieJson').html()).not.toContain(
122+
deserializerVal.value1
123+
)
124+
expect(wrapper.find('#cookieJson').html()).not.toContain(
125+
deserializerVal.value2
126+
)
127+
})
128+
129+
it('should correctly set using the serializer', async () => {
130+
const cookieName = 'cookieName'
131+
const cookieValue = { value1: 'testValue1', value2: 'testValue2' }
132+
const serializerVal = { value1: 'testValue1+1', value2: 'testValue2+1' }
133+
const wrapper = mount(
134+
testComponent(cookieName, cookieValue, {
135+
isParsing: true,
136+
serializer: (obj: any) => ({
137+
value1: `${obj.value1}+1`,
138+
value2: `${obj.value2}+1`
139+
})
140+
})
141+
)
142+
wrapper.find('#setCookie').trigger('click')
143+
wrapper.find('#getCookie').trigger('click')
144+
await wrapper.vm.$nextTick()
145+
expect(wrapper.find('#cookieJson').html()).toContain(serializerVal.value1)
146+
expect(wrapper.find('#cookieJson').html()).toContain(serializerVal.value2)
147+
})
148+
149+
it('should ignore serializer when isParsing is false', async () => {
150+
const cookieName = 'cookieName'
151+
const cookieValue = { value1: 'testValue1', value2: 'testValue2' }
152+
const serializerVal = { value1: 'testValue1+1', value2: 'testValue2+1' }
153+
const wrapper = mount(
154+
testComponent(cookieName, cookieValue, {
155+
isParsing: false,
156+
serializer: (obj: any) => ({
157+
value1: `${obj.value1}+1`,
158+
value2: `${obj.value2}+1`
159+
})
160+
})
161+
)
162+
wrapper.find('#setCookie').trigger('click')
163+
wrapper.find('#getCookie').trigger('click')
164+
await wrapper.vm.$nextTick()
165+
expect(wrapper.find('#cookie').html()).toContain('[object Object]')
166+
expect(wrapper.find('#cookieJson').html()).not.toContain(
167+
serializerVal.value1
168+
)
169+
expect(wrapper.find('#cookieJson').html()).not.toContain(
170+
serializerVal.value2
171+
)
172+
})
90173
})

src/components/useCookie/useCookie.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,54 @@
11
import Cookies from 'cookie-universal'
22
import { CookieSerializeOptions } from 'cookie'
3+
import {
4+
createSerializer,
5+
createDeserializer,
6+
trySerialize,
7+
tryDeserialize
8+
} from '@src/utils'
39
import { ref, onMounted, Ref } from '@src/api'
410

11+
export interface UseCookieOptions {
12+
isParsing: boolean
13+
serializer?: (value: any) => string
14+
deserializer?: (value: string) => any
15+
}
16+
17+
const defaultOptions = {
18+
isParsing: false
19+
}
20+
521
export function useCookie(
622
cookieName: string,
7-
enableParseJSON = false,
23+
options?: UseCookieOptions,
824
runOnMount = true
925
) {
10-
const cookieLib = Cookies(undefined, undefined, enableParseJSON)
26+
const { isParsing, ...opts } = Object.assign({}, defaultOptions, options)
27+
const cookieLib = Cookies(undefined, undefined, false)
1128
const cookie: Ref<any> = ref(null)
1229

30+
const serializer = createSerializer(opts.serializer)
31+
const deserializer = createDeserializer(opts.deserializer)
32+
1333
const getCookie = () => {
14-
const cookieVal = cookieLib.get(cookieName)
34+
const cookieVal = tryDeserialize(
35+
cookieLib.get(cookieName),
36+
deserializer,
37+
isParsing
38+
)
1539
if (typeof cookieVal !== 'undefined') cookie.value = cookieVal
1640
}
1741

1842
const setCookie = (
19-
// The user may pass a 'string', a 'number', or a valid JSON object/array
43+
// The user may pass a 'string', a 'number', a valid JSON object/array
44+
// or even a custom object when serializer/deserializer are defined
2045
// so it is better to set allowed cookie value as 'any'
2146
newVal: any,
2247
options?: CookieSerializeOptions
2348
) => {
24-
cookieLib.set(cookieName, newVal, options)
25-
cookie.value = newVal
49+
const newCookieVal = trySerialize(newVal, serializer, isParsing)
50+
cookieLib.set(cookieName, newCookieVal, options)
51+
cookie.value = tryDeserialize(newCookieVal, deserializer, isParsing)
2652
}
2753

2854
const removeCookie = (options?: CookieSerializeOptions) => {

src/components/useGeolocation/useGeolocation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function useGeolocation(
2323
options: PositionOptions = {},
2424
runOnMount = true
2525
) {
26-
options = Object.assign(defaultOpts, options)
26+
options = Object.assign({}, defaultOpts, options)
2727

2828
// Note: surprisingly the watchId can be 0 (not positive) so
2929
// we have to check if watchId !== null every time
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useLocalStorage'
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<table class="table is-fullwidth">
3+
<thead>
4+
<tr>
5+
<th>Prop</th>
6+
<th>Value</th>
7+
</tr>
8+
</thead>
9+
<tbody>
10+
<tr>
11+
<td>Sample</td>
12+
<td>{{ sample }}</td>
13+
</tr>
14+
</tbody>
15+
</table>
16+
</template>
17+
18+
<script lang="ts">
19+
import Vue from 'vue'
20+
// import { useLocalStorage } from '@src/vue-use-kit'
21+
22+
export default Vue.extend({
23+
name: 'UseLocalStorageDemo',
24+
setup() {
25+
return { sample: 'sample' }
26+
}
27+
})
28+
</script>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# useLocalStorage
2+
3+
Vue function that...
4+
5+
## Reference
6+
7+
```typescript
8+
// function useLocalStorage()
9+
```
10+
11+
### Parameters
12+
13+
- `value: string` lorem ipsa
14+
15+
### Returns
16+
17+
- `value: Ref<string>` lorem ipsa
18+
19+
## Usage
20+
21+
```html
22+
<template></template>
23+
```
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { storiesOf } from '@storybook/vue'
2+
import path from 'path'
3+
import StoryTitle from '@src/helpers/StoryTitle.vue'
4+
import UseLocalStorageDemo from './UseLocalStorageDemo.vue'
5+
6+
const functionName = 'useLocalStorage'
7+
const functionPath = path.resolve(__dirname, '..')
8+
const notes = require(`./${functionName}.md`).default
9+
10+
const basicDemo = () => ({
11+
components: { StoryTitle, demo: UseLocalStorageDemo },
12+
template: `
13+
<div class="container">
14+
<story-title
15+
function-path="${functionPath}"
16+
source-name="${functionName}"
17+
demo-name="UseLocalStorageDemo.vue"
18+
>
19+
<template v-slot:title></template>
20+
<template v-slot:intro></template>
21+
</story-title>
22+
<demo />
23+
</div>`
24+
})
25+
26+
storiesOf('side effects|useLocalStorage', module)
27+
.addParameters({ notes })
28+
.add('Demo', basicDemo)

0 commit comments

Comments
 (0)