Skip to content

Commit ccd1165

Browse files
committed
feat: add delete and subscription hooks
1 parent 7ca0888 commit ccd1165

11 files changed

+166
-14
lines changed

src/hooks/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from './use-client'
2+
export * from './use-delete'
23
export * from './use-filter'
34
export * from './use-insert'
45
export * from './use-select'
6+
export * from './use-subscription'
57
export * from './use-update'

src/hooks/use-delete.ts

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { useCallback, useEffect, useRef, useState } from 'react'
2+
3+
import { Count, Filter, PostgrestError, Returning } from '../types'
4+
import { useClient } from './use-client'
5+
import { initialState } from './state'
6+
7+
export type UseDeleteState<Data = any> = {
8+
count?: number | null
9+
data?: Data | Data[] | null
10+
error?: PostgrestError | null
11+
fetching: boolean
12+
}
13+
14+
export type UseDeleteResponse<Data = any> = [
15+
UseDeleteState<Data>,
16+
(
17+
filter: Filter<Data>,
18+
options?: UseDeleteOptions,
19+
) => Promise<Pick<UseDeleteState<Data>, 'count' | 'data' | 'error'>>,
20+
]
21+
22+
export type UseDeleteOptions = {
23+
returning?: Returning
24+
count?: null | Count
25+
}
26+
27+
export type UseDeleteConfig<Data = any> = {
28+
filter?: Filter<Data>
29+
options?: UseDeleteOptions
30+
}
31+
32+
export function useDelete<Data = any>(
33+
table: string,
34+
config: UseDeleteConfig<Data> = { options: {} },
35+
): UseDeleteResponse<Data> {
36+
const client = useClient()
37+
const isMounted = useRef(false)
38+
const [state, setState] = useState<UseDeleteState>(initialState)
39+
40+
/* eslint-disable react-hooks/exhaustive-deps */
41+
const execute = useCallback(
42+
async (filter?: Filter<Data>, options?: UseDeleteOptions) => {
43+
const refine = filter ?? config.filter
44+
if (refine === undefined)
45+
throw Error('update() should always be combined with `filter`')
46+
47+
setState({ ...initialState, fetching: true })
48+
const source = client
49+
.from<Data>(table)
50+
.delete(options ?? config.options)
51+
const { count, data, error } = await refine(source)
52+
53+
const res = { count, data, error }
54+
if (isMounted.current) setState({ ...res, fetching: false })
55+
return res
56+
},
57+
[client],
58+
)
59+
/* eslint-enable react-hooks/exhaustive-deps */
60+
61+
useEffect(() => {
62+
isMounted.current = true
63+
return () => {
64+
isMounted.current = false
65+
}
66+
}, [])
67+
68+
return [state, execute]
69+
}

src/hooks/use-insert.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback, useEffect, useRef, useState } from 'react'
22

3-
import { PostgrestError } from '../types'
3+
import { Count, PostgrestError, Returning } from '../types'
44
import { useClient } from './use-client'
55
import { initialState } from './state'
66

@@ -20,8 +20,8 @@ export type UseInsertResponse<Data = any> = [
2020
]
2121

2222
export type UseInsertOptions = {
23-
returning?: 'minimal' | 'representation'
24-
count?: null | 'exact' | 'planned' | 'estimated'
23+
returning?: Returning
24+
count?: null | Count
2525
}
2626

2727
export type UseInsertConfig = {
@@ -46,8 +46,9 @@ export function useInsert<Data = any>(
4646
const { count, data, error } = await client
4747
.from<Data>(table)
4848
.insert(values, options ?? config.options)
49-
if (isMounted.current) setState({ data, error, fetching: false })
50-
return { count, data, error }
49+
const res = { count, data, error }
50+
if (isMounted.current) setState({ ...res, fetching: false })
51+
return res
5152
},
5253
[client],
5354
)

src/hooks/use-select.ts

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export type UseSelectConfig<Data = any> = {
2525
columns?: string
2626
filter?: Filter<Data> | false | null
2727
options?: UseSelectOptions
28-
single?: boolean
2928
}
3029

3130
export function useSelect<Data = any>(

src/hooks/use-subscription.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useEffect } from 'react'
2+
import {
3+
SupabaseEventTypes,
4+
SupabaseRealtimePayload,
5+
} from '@supabase/supabase-js/dist/main/lib/types'
6+
7+
import { useClient } from './use-client'
8+
9+
export type UseSubscriptionConfig = {
10+
event?: SupabaseEventTypes
11+
table?: string
12+
}
13+
14+
export function useSubscription<Data = any>(
15+
callback: (payload: SupabaseRealtimePayload<Data>) => void,
16+
config: UseSubscriptionConfig = { event: '*', table: '*' },
17+
) {
18+
const client = useClient()
19+
20+
/* eslint-disable react-hooks/exhaustive-deps */
21+
useEffect(() => {
22+
const subscription = client
23+
.from<Data>(config.table ?? '*')
24+
.on(config.event ?? '*', callback)
25+
.subscribe()
26+
return () => {
27+
subscription.unsubscribe()
28+
}
29+
}, [])
30+
/* eslint-enable react-hooks/exhaustive-deps */
31+
}

src/hooks/use-update.ts

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback, useEffect, useRef, useState } from 'react'
22

3-
import { Count, PostgrestError, Returning } from '../types'
3+
import { Count, Filter, PostgrestError, Returning } from '../types'
44
import { useClient } from './use-client'
55
import { initialState } from './state'
66

@@ -15,6 +15,7 @@ export type UseUpdateResponse<Data = any> = [
1515
UseUpdateState<Data>,
1616
(
1717
values: Partial<Data>,
18+
filter?: Filter<Data>,
1819
options?: UseUpdateOptions,
1920
) => Promise<Pick<UseUpdateState<Data>, 'count' | 'data' | 'error'>>,
2021
]
@@ -24,27 +25,39 @@ export type UseUpdateOptions = {
2425
count?: null | Count
2526
}
2627

27-
export type UseUpdateConfig = {
28+
export type UseUpdateConfig<Data = any> = {
29+
filter?: Filter<Data>
2830
options?: UseUpdateOptions
2931
}
3032

3133
export function useUpdate<Data = any>(
3234
table: string,
33-
config: UseUpdateConfig = { options: {} },
35+
config: UseUpdateConfig<Data> = { options: {} },
3436
): UseUpdateResponse<Data> {
3537
const client = useClient()
3638
const isMounted = useRef(false)
3739
const [state, setState] = useState<UseUpdateState>(initialState)
3840

3941
/* eslint-disable react-hooks/exhaustive-deps */
4042
const execute = useCallback(
41-
async (values: Partial<Data>, options?: UseUpdateOptions) => {
43+
async (
44+
values: Partial<Data>,
45+
filter?: Filter<Data>,
46+
options?: UseUpdateOptions,
47+
) => {
48+
const refine = filter ?? config.filter
49+
if (refine === undefined)
50+
throw Error('update() should always be combined with `filter`')
51+
4252
setState({ ...initialState, fetching: true })
43-
const { count, data, error } = await client
53+
const source = client
4454
.from<Data>(table)
4555
.update(values, options ?? config.options)
46-
if (isMounted.current) setState({ data, error, fetching: false })
47-
return { count, data, error }
56+
const { count, data, error } = await refine(source)
57+
58+
const res = { count, data, error }
59+
if (isMounted.current) setState({ ...res, fetching: false })
60+
return res
4861
},
4962
[client],
5063
)

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './context'
22
export * from './hooks'
3+
export * from './types'

test/use-delete.test.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { renderHook } from '@testing-library/react-hooks'
2+
3+
import { useDelete } from '../src'
4+
5+
describe('useDelete', () => {
6+
it('should throw when not inside Provider', () => {
7+
const { result } = renderHook(() => useDelete('todos'))
8+
expect(result.error).toEqual(
9+
Error('No client has been specified using Provider.'),
10+
)
11+
})
12+
})

test/use-insert.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { renderHook } from '@testing-library/react-hooks'
22

33
import { useInsert } from '../src'
44

5-
describe('useSelect', () => {
5+
describe('useInsert', () => {
66
it('should throw when not inside Provider', () => {
77
const { result } = renderHook(() => useInsert('todos'))
88
expect(result.error).toEqual(

test/use-subscription.test.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { renderHook } from '@testing-library/react-hooks'
2+
3+
import { useSubscription } from '../src'
4+
5+
describe('useSubscription', () => {
6+
it('should throw when not inside Provider', () => {
7+
const { result } = renderHook(() => useSubscription(jest.fn()))
8+
expect(result.error).toEqual(
9+
Error('No client has been specified using Provider.'),
10+
)
11+
})
12+
})

test/use-update.test.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { renderHook } from '@testing-library/react-hooks'
2+
3+
import { useUpdate } from '../src'
4+
5+
describe('useUpdate', () => {
6+
it('should throw when not inside Provider', () => {
7+
const { result } = renderHook(() => useUpdate('todos'))
8+
expect(result.error).toEqual(
9+
Error('No client has been specified using Provider.'),
10+
)
11+
})
12+
})

0 commit comments

Comments
 (0)