• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ckeditor / ckeditor5-react / ca6e1779-8fe0-4d9b-af01-91100ac4e4bc

24 Sep 2024 01:45PM UTC coverage: 100.0%. Remained the same
ca6e1779-8fe0-4d9b-af01-91100ac4e4bc

Pull #536

circleci

martnpaneq
Updated dependency versions to remove warnings.
Pull Request #536: Updated dependency versions to remove warnings.

281 of 281 branches covered (100.0%)

Branch coverage included in aggregate %.

582 of 582 relevant lines covered (100.0%)

70.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/hooks/useAsyncCallback.ts
1
/**
2
 * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
 * For licensing, see LICENSE.md.
4
 */
5

6
import { useState, useRef } from 'react';
7
import { uid, isSSR } from '@ckeditor/ckeditor5-integrations-common';
8

9
import { useIsUnmountedRef } from './useIsUnmountedRef.js';
10
import { useRefSafeCallback } from './useRefSafeCallback.js';
11

12
/**
13
 * A hook that allows to execute an asynchronous function and provides the state of the execution.
14
 *
15
 * @param callback The asynchronous function to be executed.
16
 * @returns A tuple with the function that triggers the execution and the state of the execution.
17
 *
18
 * @example
19
 * ```tsx
20
 * const [ onFetchData, fetchDataStatus ] = useAsyncCallback( async () => {
21
 *         const response = await fetch( 'https://api.example.com/data' );
22
 *         const data = await response.json();
23
 *         return data;
24
 * } );
25
 *
26
 * return (
27
 *         <div>
28
 *                 <button onClick={ onFetchData }>Fetch data</button>
29
 *                 { fetchDataStatus.status === 'loading' && <p>Loading...</p> }
30
 *                 { fetchDataStatus.status === 'success' && <pre>{ JSON.stringify( fetchDataStatus.data, null, 2 ) }</pre> }
31
 *                 { fetchDataStatus.status === 'error' && <p>Error: { fetchDataStatus.error.message }</p> }
32
 *         </div>
33
 * );
34
 * ```
35
 */
36
export const useAsyncCallback = <A extends Array<unknown>, R>(
5✔
37
        callback: ( ...args: Array<A> ) => Promise<R>
38
): AsyncCallbackHookResult<A, R> => {
39
        // The state of the asynchronous callback.
40
        const [ asyncState, setAsyncState ] = useState<AsyncCallbackState<R>>( {
59✔
41
                status: 'idle'
42
        } );
43

44
        // A reference to the mounted state of the component.
45
        const unmountedRef = useIsUnmountedRef();
59✔
46

47
        // A reference to the previous execution UUID. It is used to prevent race conditions between multiple executions
48
        // of the asynchronous function. If the UUID of the current execution is different than the UUID of the previous
49
        // execution, the state is not updated.
50
        const prevExecutionUIDRef = useRef<string | null>( null );
59✔
51

52
        // The asynchronous executor function, which is a wrapped version of the original callback.
53
        const asyncExecutor = useRefSafeCallback( async ( ...args: Array<any> ) => {
59✔
54
                if ( unmountedRef.current || isSSR() ) {
22✔
55
                        return null;
1✔
56
                }
57

58
                const currentExecutionUUID = uid();
21✔
59
                prevExecutionUIDRef.current = currentExecutionUUID;
21✔
60

61
                try {
21✔
62
                        // Prevent unnecessary state updates, keep loading state if the status is already 'loading'.
63
                        if ( asyncState.status !== 'loading' ) {
21✔
64
                                setAsyncState( {
20✔
65
                                        status: 'loading'
66
                                } );
67
                        }
68

69
                        // Execute the asynchronous function.
70
                        const result = await callback( ...args );
21✔
71

72
                        // Update the state if the component is still mounted and the execution UUID matches the previous one, otherwise
73
                        // ignore the result and keep the previous state.
74
                        if ( !unmountedRef.current && prevExecutionUIDRef.current === currentExecutionUUID ) {
17✔
75
                                setAsyncState( {
15✔
76
                                        status: 'success',
77
                                        data: result
78
                                } );
79
                        }
80

81
                        return result;
17✔
82
                } catch ( error: any ) {
83
                        console.error( error );
4✔
84

85
                        // Update the state if the component is still mounted and the execution UUID matches the previous one, otherwise
86
                        if ( !unmountedRef.current && prevExecutionUIDRef.current === currentExecutionUUID ) {
4✔
87
                                setAsyncState( {
3✔
88
                                        status: 'error',
89
                                        error
90
                                } );
91
                        }
92
                }
93

94
                return null;
4✔
95
        } );
96

97
        return [ asyncExecutor, asyncState ] as AsyncCallbackHookResult<A, R>;
59✔
98
};
99

100
/**
101
 * Represents the result of the `useAsyncCallback` hook.
102
 */
103
export type AsyncCallbackHookResult<A extends Array<unknown>, R> = [
104
        ( ...args: Array<A> ) => Promise<R | null>,
105
        AsyncCallbackState<R>
106
];
107

108
/**
109
 * Represents the state of an asynchronous callback.
110
 */
111
export type AsyncCallbackState<T> =
112
        | {
113
                status: 'idle';
114
        }
115
        | {
116
                status: 'loading';
117
        }
118
        | {
119
                status: 'success';
120
                data: T;
121
        }
122
        | {
123
                status: 'error';
124
                error: any;
125
        };
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc