Skip to content

Commit

Permalink
mark data and error as readonly (#1514)
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Oct 3, 2021
1 parent b7e131b commit af6353c
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 21 deletions.
13 changes: 10 additions & 3 deletions infinite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const infinite = ((<Data, Error, Args extends Arguments>(
): SWRInfiniteResponse<Data, Error> => {
const rerender = useState({})[1]
const didMountRef = useRef<boolean>(false)
const dataRef = useRef<Data[]>()
const dataRef = useRef<Readonly<Data[]>>()

const {
cache,
Expand Down Expand Up @@ -167,7 +167,12 @@ export const infinite = ((<Data, Error, Args extends Arguments>(

const mutate = useCallback(
(
data: Data[] | undefined | Promise<Data[]> | MutatorCallback<Data[]>,
data?:
| Data[]
| Readonly<Data[]>
| Promise<Data[]>
| Promise<Readonly<Data[]>>
| MutatorCallback<Data[]>,
shouldRevalidate = true
) => {
// It is possible that the key is still falsy.
Expand All @@ -190,7 +195,9 @@ export const infinite = ((<Data, Error, Args extends Arguments>(
)

// Function to load pages data from the cache based on the page size.
const resolvePagesFromCache = (pageSize: number): Data[] | undefined => {
const resolvePagesFromCache = (
pageSize: number
): Readonly<Data[]> | undefined => {
// return an array of page data
const data: Data[] = []

Expand Down
38 changes: 21 additions & 17 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as revalidateEvents from './constants/revalidate-events'

export type FetcherResponse<Data = unknown> = Data | Promise<Data>
type Async<Data> = Data | Promise<Data>
export type FetcherResponse<Data = unknown> = Async<Data>

export type Fetcher<Data = unknown, SWRKey extends Key = Key> =
/**
Expand Down Expand Up @@ -142,8 +143,8 @@ export type Arguments = string | null | ArgumentsTuple | Record<any, any>
export type Key = Arguments | (() => Arguments)

export type MutatorCallback<Data = any> = (
currentValue?: Data
) => Promise<undefined | Data> | undefined | Data
currentValue?: Readonly<Data>
) => Async<undefined | Data | Readonly<Data>>

export type Broadcaster<Data = any, Error = any> = (
cache: Cache<Data>,
Expand All @@ -160,32 +161,35 @@ export type State<Data, Error> = {
isValidating?: boolean
}

type MutatorData<Data> =
| Async<Data>
| Async<Readonly<Data>>
| MutatorCallback<Data>

export type Mutator<Data = any> = (
cache: Cache,
key: Key,
data?: Data | Promise<Data> | MutatorCallback<Data>,
data?: MutatorData<Data>,
shouldRevalidate?: boolean
) => Promise<Data | undefined>
) => Promise<Readonly<Data> | undefined>

export interface ScopedMutator<Data = any> {
/** This is used for bound mutator */
(
key: Key,
data?: Data | Promise<Data> | MutatorCallback<Data>,
shouldRevalidate?: boolean
): Promise<Data | undefined>
(key: Key, data?: MutatorData<Data>, shouldRevalidate?: boolean): Promise<
Readonly<Data> | undefined
>
/** This is used for global mutator */
<T = any>(
key: Key,
data?: T | Promise<T> | MutatorCallback<T>,
data?: MutatorData<T>,
shouldRevalidate?: boolean
): Promise<T | undefined>
): Promise<Readonly<T> | undefined>
}

export type KeyedMutator<Data> = (
data?: Data | Promise<Data> | MutatorCallback<Data>,
data?: MutatorData<Data>,
shouldRevalidate?: boolean
) => Promise<Data | undefined>
) => Promise<Readonly<Data> | undefined>

// Public types

Expand All @@ -196,10 +200,10 @@ export type SWRConfiguration<
> = Partial<PublicConfiguration<Data, Error, SWRKey>>

export interface SWRResponse<Data, Error> {
data?: Data
error?: Error
mutate: KeyedMutator<Data>
data?: Readonly<Data>
error?: Readonly<Error>
isValidating: boolean
mutate: KeyedMutator<Data>
}

export type KeyLoader<Args extends Arguments = Arguments> =
Expand Down
35 changes: 35 additions & 0 deletions test/type/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import useSWR from 'swr'

type ExpectType = <T>(value: T) => void
const expectType: ExpectType = () => {}

export function useInferDataType() {
// Should infer the data type
expectType<string | undefined>(useSWR('foo', i => i).data)
expectType<number | undefined>(useSWR('foo', () => 1).data)
expectType<{ foo: number } | undefined>(useSWR({ foo: 1 }, i => i).data)
expectType<{ foo: 1 } | undefined>(useSWR({ foo: 1 } as const, i => i).data)
expectType<string | undefined>(useSWR(['foo'], i => i[0]).data)

// Should mark data and error as readonly
expectType<Readonly<{}> | undefined>(useSWR({}, i => i).data)
expectType<Readonly<{}> | undefined>(useSWR<any, {}>('').error)
}

export function useInferMutateType() {
// Should infer the mutate argument type
useSWR('foo', i => i).mutate(v => {
expectType<string | undefined>(v)
return v
})

// Should mark it as readonly
useSWR({ foo: 1 }, i => i).mutate(v => {
expectType<Readonly<{ foo: number }> | undefined>(v)
return v
})
useSWR({ foo: 1 } as const, i => i).mutate(v => {
expectType<Readonly<{ foo: 1 }> | undefined>(v)
return v
})
}
2 changes: 1 addition & 1 deletion test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('useSWR - local mutation', () => {
const { data: state, mutate: setState } = useSWR(`${baseKey}--${key}`, {
fallbackData
})
return [state, setState]
return [state, setState] as const
}

function Page() {
Expand Down

0 comments on commit af6353c

Please sign in to comment.