Skip to content

Commit f9ddd85

Browse files
authored
Merge pull request #13 from microcipcip/feature/useLocalStorage
Feature/use local storage
2 parents 8bfba2c + c03d6c5 commit f9ddd85

File tree

15 files changed

+673
-37
lines changed

15 files changed

+673
-37
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: 30 additions & 19 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?: SerializerFunction
11+
deserializer?: DeserializerFunction
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: SerializerFunction` a custom serializer, `JSON.stringify` by default
34+
- `deserializer: DeserializerFunction` 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
@@ -38,31 +49,31 @@ function useCookie(
3849
```html
3950
<template>
4051
<div>
41-
Cookie: {{ cookie }}
42-
52+
<div>Cookie: {{ cookie }}</div>
4353
<button @click="getCookie">Get cookie</button>
4454
<button @click="setCookie('Value here')">Set cookie</button>
4555
<button @click="removeCookie">Remove cookie</button>
4656
</div>
4757
</template>
4858

4959
<script lang="ts">
50-
import Vue from 'vue'
51-
import { useCookie } from 'vue-use-kit'
60+
import Vue from 'vue'
61+
import { useCookie } from 'vue-use-kit'
5262
53-
export default Vue.extend({
54-
name: 'UseCookieDemo',
55-
setup() {
56-
const {
57-
cookie, getCookie, setCookie, removeCookie
58-
} = useCookie('i_love_cookies')
63+
export default Vue.extend({
64+
name: 'UseCookieDemo',
65+
setup() {
66+
const { cookie, getCookie, setCookie, removeCookie } = useCookie(
67+
'i_love_cookies'
68+
)
5969
60-
return {
61-
cookie,
62-
getCookie,
63-
setCookie,
64-
removeCookie,
70+
return {
71+
cookie,
72+
getCookie,
73+
setCookie,
74+
removeCookie
75+
}
6576
}
66-
}
67-
})
77+
})
78+
</script>
6879
```

src/components/useCookie/useCookie.spec.ts

Lines changed: 88 additions & 4 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

@@ -72,10 +72,12 @@ describe('useCookie', () => {
7272
expect(document.cookie).not.toContain(cookieValue)
7373
})
7474

75-
it('should correctly get and set the parseToJson object', async () => {
75+
it('should correctly get and set the isParsing 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,86 @@ 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 the object 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+
JSON.stringify({
138+
value1: `${obj.value1}+1`,
139+
value2: `${obj.value2}+1`
140+
})
141+
})
142+
)
143+
wrapper.find('#setCookie').trigger('click')
144+
wrapper.find('#getCookie').trigger('click')
145+
await wrapper.vm.$nextTick()
146+
expect(wrapper.find('#cookieJson').html()).toContain(serializerVal.value1)
147+
expect(wrapper.find('#cookieJson').html()).toContain(serializerVal.value2)
148+
})
149+
150+
it('should ignore serializer when isParsing is false', async () => {
151+
const cookieName = 'cookieName'
152+
const cookieValue = { value1: 'testValue1', value2: 'testValue2' }
153+
const serializerVal = { value1: 'testValue1+1', value2: 'testValue2+1' }
154+
const wrapper = mount(
155+
testComponent(cookieName, cookieValue, {
156+
isParsing: false,
157+
serializer: (obj: any) => ({
158+
value1: `${obj.value1}+1`,
159+
value2: `${obj.value2}+1`
160+
})
161+
})
162+
)
163+
wrapper.find('#setCookie').trigger('click')
164+
wrapper.find('#getCookie').trigger('click')
165+
await wrapper.vm.$nextTick()
166+
expect(wrapper.find('#cookie').html()).toContain('[object Object]')
167+
expect(wrapper.find('#cookieJson').html()).not.toContain(
168+
serializerVal.value1
169+
)
170+
expect(wrapper.find('#cookieJson').html()).not.toContain(
171+
serializerVal.value2
172+
)
173+
})
90174
})

src/components/useCookie/useCookie.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,57 @@
11
import Cookies from 'cookie-universal'
22
import { CookieSerializeOptions } from 'cookie'
3+
import {
4+
createSerializer,
5+
createDeserializer,
6+
SerializerFunction,
7+
DeserializerFunction,
8+
trySerialize,
9+
tryDeserialize,
10+
isNullOrUndefined
11+
} from '@src/utils'
312
import { ref, onMounted, Ref } from '@src/api'
413

14+
export interface UseCookieOptions {
15+
isParsing: boolean
16+
serializer?: SerializerFunction
17+
deserializer?: DeserializerFunction
18+
}
19+
20+
const defaultOptions = {
21+
isParsing: false
22+
}
23+
524
export function useCookie(
625
cookieName: string,
7-
enableParseJSON = false,
26+
options?: UseCookieOptions,
827
runOnMount = true
928
) {
10-
const cookieLib = Cookies(undefined, undefined, enableParseJSON)
29+
const { isParsing, ...opts } = Object.assign({}, defaultOptions, options)
30+
const serializer = createSerializer(opts.serializer)
31+
const deserializer = createDeserializer(opts.deserializer)
32+
33+
const cookieLib = Cookies(undefined, undefined, false)
1134
const cookie: Ref<any> = ref(null)
1235

1336
const getCookie = () => {
14-
const cookieVal = cookieLib.get(cookieName)
15-
if (typeof cookieVal !== 'undefined') cookie.value = cookieVal
37+
const cookieVal = tryDeserialize(
38+
cookieLib.get(cookieName),
39+
deserializer,
40+
isParsing
41+
)
42+
if (!isNullOrUndefined(cookieVal)) cookie.value = cookieVal
1643
}
1744

1845
const setCookie = (
19-
// The user may pass a 'string', a 'number', or a valid JSON object/array
46+
// The user may pass a 'string', a 'number', a valid JSON object/array
47+
// or even a custom object when serializer/deserializer are defined
2048
// so it is better to set allowed cookie value as 'any'
2149
newVal: any,
2250
options?: CookieSerializeOptions
2351
) => {
24-
cookieLib.set(cookieName, newVal, options)
25-
cookie.value = newVal
52+
const newCookieVal = trySerialize(newVal, serializer, isParsing)
53+
cookieLib.set(cookieName, newCookieVal, options)
54+
cookie.value = tryDeserialize(newCookieVal, deserializer, isParsing)
2655
}
2756

2857
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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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>item</td>
12+
<td>{{ item }}</td>
13+
</tr>
14+
<tr>
15+
<td colspan="2">
16+
<button class="button is-primary" @click="handleSetItem">
17+
Set / Update item
18+
</button>
19+
<button class="button is-danger" @click="removeItem()">
20+
Remove item
21+
</button>
22+
</td>
23+
</tr>
24+
<tr>
25+
<td>jsonItem</td>
26+
<td>{{ jsonItem }}</td>
27+
</tr>
28+
<tr>
29+
<td colspan="2">
30+
<button class="button is-primary" @click="handleSetJsonItem">
31+
Set / Update JSON item
32+
</button>
33+
<button class="button is-danger" @click="jsonRemoveItem()">
34+
Remove JSON item
35+
</button>
36+
</td>
37+
</tr>
38+
</tbody>
39+
</table>
40+
</template>
41+
42+
<script lang="ts">
43+
import Vue from 'vue'
44+
import { useLocalStorage } from '@src/vue-use-kit'
45+
46+
export default Vue.extend({
47+
name: 'useLocalStorageDemo',
48+
setup() {
49+
const { item, setItem, removeItem } = useLocalStorage('normalItem')
50+
51+
const {
52+
item: jsonItem,
53+
setItem: jsonSetItem,
54+
removeItem: jsonRemoveItem
55+
} = useLocalStorage('jsonItem', {
56+
isParsing: true
57+
})
58+
59+
let counter = 0
60+
const handleSetItem = () => {
61+
counter++
62+
setItem(`count${counter}`)
63+
}
64+
65+
const handleSetJsonItem = () => {
66+
counter++
67+
jsonSetItem({
68+
counter: counter,
69+
counterTest: `test${counter}`
70+
})
71+
}
72+
73+
return {
74+
item,
75+
handleSetItem,
76+
removeItem,
77+
jsonItem,
78+
handleSetJsonItem,
79+
jsonRemoveItem,
80+
counter
81+
}
82+
}
83+
})
84+
</script>

0 commit comments

Comments
 (0)