swr
Version:
React Hooks library for remote data fetching
203 lines (196 loc) • 7.84 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var useSWR = require('../index/index.js');
var index_js = require('../_internal/index.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
const startTransition = index_js.IS_REACT_LEGACY ? (cb)=>{
cb();
} : React__default.default.startTransition;
/**
* An implementation of state with dependency-tracking.
* @param initialState - The initial state object.
*/ const useStateWithDeps = (initialState)=>{
const [, rerender] = React.useState({});
const unmountedRef = React.useRef(false);
const stateRef = React.useRef(initialState);
// If a state property (data, error, or isValidating) is accessed by the render
// function, we mark the property as a dependency so if it is updated again
// in the future, we trigger a rerender.
// This is also known as dependency-tracking.
const stateDependenciesRef = React.useRef({
data: false,
error: false,
isValidating: false
});
/**
* Updates state and triggers re-render if necessary.
* @param payload To change stateRef, pass the values explicitly to setState:
* @example
* ```js
* setState({
* isValidating: false
* data: newData // set data to newData
* error: undefined // set error to undefined
* })
*
* setState({
* isValidating: false
* data: undefined // set data to undefined
* error: err // set error to err
* })
* ```
*/ const setState = React.useCallback((payload)=>{
let shouldRerender = false;
const currentState = stateRef.current;
for(const key in payload){
if (Object.prototype.hasOwnProperty.call(payload, key)) {
const k = key;
// If the property has changed, update the state and mark rerender as
// needed.
if (currentState[k] !== payload[k]) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
currentState[k] = payload[k];
// If the property is accessed by the component, a rerender should be
// triggered.
if (stateDependenciesRef.current[k]) {
shouldRerender = true;
}
}
}
}
if (shouldRerender && !unmountedRef.current) {
rerender({});
}
}, []);
index_js.useIsomorphicLayoutEffect(()=>{
unmountedRef.current = false;
return ()=>{
unmountedRef.current = true;
};
});
return [
stateRef,
stateDependenciesRef.current,
setState
];
};
const mutation = ()=>(key, fetcher, config = {})=>{
const { mutate } = useSWR.useSWRConfig();
const keyRef = React.useRef(key);
const fetcherRef = React.useRef(fetcher);
const configRef = React.useRef(config);
// Ditch all mutation results that happened earlier than this timestamp.
const ditchMutationsUntilRef = React.useRef(0);
const [stateRef, stateDependencies, setState] = useStateWithDeps({
data: index_js.UNDEFINED,
error: index_js.UNDEFINED,
isMutating: false
});
const currentState = stateRef.current;
const trigger = React.useCallback(async (arg, opts)=>{
const [serializedKey, resolvedKey] = index_js.serialize(keyRef.current);
if (!fetcherRef.current) {
throw new Error('Can’t trigger the mutation: missing fetcher.');
}
if (!serializedKey) {
throw new Error('Can’t trigger the mutation: missing key.');
}
// Disable cache population by default.
const options = index_js.mergeObjects(index_js.mergeObjects({
populateCache: false,
throwOnError: true
}, configRef.current), opts);
// Trigger a mutation, and also track the timestamp. Any mutation that happened
// earlier this timestamp should be ignored.
const mutationStartedAt = index_js.getTimestamp();
ditchMutationsUntilRef.current = mutationStartedAt;
setState({
isMutating: true
});
try {
const data = await mutate(serializedKey, fetcherRef.current(resolvedKey, {
arg
}), // We must throw the error here so we can catch and update the states.
index_js.mergeObjects(options, {
throwOnError: true
}));
// If it's reset after the mutation, we don't broadcast any state change.
if (ditchMutationsUntilRef.current <= mutationStartedAt) {
startTransition(()=>setState({
data,
isMutating: false,
error: undefined
}));
options.onSuccess == null ? void 0 : options.onSuccess.call(options, data, serializedKey, options);
}
return data;
} catch (error) {
// If it's reset after the mutation, we don't broadcast any state change
// or throw because it's discarded.
if (ditchMutationsUntilRef.current <= mutationStartedAt) {
startTransition(()=>setState({
error: error,
isMutating: false
}));
options.onError == null ? undefined : options.onError.call(options, error, serializedKey, options);
if (options.throwOnError) {
throw error;
}
}
}
}, // eslint-disable-next-line react-hooks/exhaustive-deps
[]);
const reset = React.useCallback(()=>{
ditchMutationsUntilRef.current = index_js.getTimestamp();
setState({
data: index_js.UNDEFINED,
error: index_js.UNDEFINED,
isMutating: false
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
index_js.useIsomorphicLayoutEffect(()=>{
keyRef.current = key;
fetcherRef.current = fetcher;
configRef.current = config;
});
// We don't return `mutate` here as it can be pretty confusing (e.g. people
// calling `mutate` but they actually mean `trigger`).
// And also, `mutate` relies on the useSWR hook to exist too.
return {
trigger,
reset,
get data () {
stateDependencies.data = true;
return currentState.data;
},
get error () {
stateDependencies.error = true;
return currentState.error;
},
get isMutating () {
stateDependencies.isMutating = true;
return currentState.isMutating;
}
};
};
/**
* A hook to define and manually trigger remote mutations like POST, PUT, DELETE and PATCH use cases.
*
* @link https://swr.vercel.app/docs/mutation
* @example
* ```jsx
* import useSWRMutation from 'swr/mutation'
*
* const {
* data,
* error,
* trigger,
* reset,
* isMutating
* } = useSWRMutation(key, fetcher, options?)
* ```
*/ const useSWRMutation = index_js.withMiddleware(useSWR__default.default, mutation);
exports.default = useSWRMutation;