Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/fetchye/__tests__/defaultMapOptionsToKey.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import { defaultMapOptionsToKey } from '../src/defaultMapOptionsToKey';

describe('defaultMapOptionsToKey', () => {
it('should return an object without passed signal, defer, or mapOptionsToKey', () => {
it('should return an object without passed signal, defer, mapOptionsToKey, or forceInitialFetch', () => {
expect(defaultMapOptionsToKey({
signal: {}, defer: true, mapOptionsToKey: () => { }, method: 'POST',
signal: {}, defer: true, mapOptionsToKey: () => { }, method: 'POST', forceInitialFetch: true,
}))
.toMatchInlineSnapshot(`
Object {
Expand Down
5 changes: 5 additions & 0 deletions packages/fetchye/__tests__/queryHelpers.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ describe('isLoading', () => {
loading: false, data: undefined, numOfRenders: 1, options: { },
})).toEqual(true);
});
it('should return true if first render and forceInitialFetch option is true', () => {
expect(isLoading({
loading: false, data: undefined, numOfRenders: 1, options: { forceInitialFetch: true },
})).toEqual(true);
});
it('should return false if first render is true and defer is true', () => {
expect(isLoading({
loading: false, data: undefined, numOfRenders: 1, options: { defer: true },
Expand Down
78 changes: 58 additions & 20 deletions packages/fetchye/__tests__/useFetchye.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* permissions and limitations under the License.
*/

import React, { useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { render, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
Expand Down Expand Up @@ -348,31 +348,69 @@ describe('useFetchye', () => {
"initialData": true,
},
},
"error": null,
"error": undefined,
"isLoading": false,
"run": [Function],
}
`);
});
it('should ignore cache', async () => {
let fetchyeRes;
global.fetch = jest.fn(async () => ({ ...defaultPayload }));
render(
<AFetchyeProvider cache={cache}>
{React.createElement(() => {
const { isLoading } = useFetchye('http://example.com/one');
if (isLoading === true) {
return null;
}
return React.createElement(() => {
fetchyeRes = useFetchye('http://example.com/one', { forceInitialFetch: true });
return null;
});
})}
</AFetchyeProvider>
);
await waitFor(() => fetchyeRes.isLoading === false);

global.fetch = jest.fn()
.mockImplementationOnce(async () => ({
...defaultPayload,
text: async () => JSON.stringify({
fakeData: true,
fetchNo: 'first',
}),
}))
.mockImplementationOnce(async () => ({
...defaultPayload,
text: async () => JSON.stringify({
fakeData: true,
fetchNo: 'second',
}),
}));
let firstRes;
let secondRes;
// eslint-disable-next-line react/prop-types -- no need for test here
const TestComp = ({ forceFetch, firstResponse, setRes }) => {
const res = useFetchye('http://example.com/one', forceFetch ? { forceInitialFetch: forceFetch } : undefined);
if (forceFetch) {
secondRes = res;
} else {
firstRes = res;
}
useEffect(() => {
if (!firstResponse && res.data?.body.fetchNo === 'first' && !forceFetch) {
setRes(res);
}
}, [firstResponse, res, setRes, forceFetch]);
if (res.isLoading || !res.data) {
return null;
}
return <p>{res.data.body.fetchNo}</p>;
};
const Comp = () => {
const [firstResponse, setRes] = useState();
return (
<AFetchyeProvider cache={cache}>
<TestComp forceFetch={false} firstResponse={firstResponse} setRes={setRes} />
{firstResponse?.data && <TestComp forceFetch={true} />}
</AFetchyeProvider>
);
};
render(<Comp />);
await waitFor(() => {
expect(firstRes.data?.body.fetchNo).toBe('first');
});
await waitFor(() => {
expect(secondRes.data?.body).toStrictEqual({
fakeData: true,
fetchNo: 'second',
});
});
expect(secondRes.isLoading).toBeFalsy();
expect(secondRes.error).toBeUndefined();
expect(global.fetch.mock.calls).toHaveLength(2);
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/fetchye/src/defaultMapOptionsToKey.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const defaultMapOptionsToKey = (options) => {
defer,
mapOptionsToKey,
initialData,
forceInitialFetch,
...restOfOptions
} = options;
return restOfOptions;
Expand Down
5 changes: 5 additions & 0 deletions packages/fetchye/src/queryHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const isLoading = ({
// isLoading should be true
return true;
}
// when we force fetch isLoading is always going to be true on first render
if (options.forceInitialFetch) {
// isLoading should be true
return true;
}
}
// If not on first render and loading from cache is true
if (loading) {
Expand Down
57 changes: 43 additions & 14 deletions packages/fetchye/src/useFetchye.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,30 @@
*/

import { useEffect, useRef } from 'react';
import { setAction } from 'fetchye-core';
import { runAsync } from './runAsync';
import { computeKey } from './computeKey';
import {
isLoading,
} from './queryHelpers';
import { useFetchyeContext } from './useFetchyeContext';

const passInitialData = (value, initialValue, numOfRenders) => (numOfRenders === 1
? value || initialValue
: value);
const passInitialData = ({
value,
initialValue,
numOfRenders,
forceInitialFetch,
}) => {
if (numOfRenders === 1) {
if (initialValue) {
return initialValue;
}
if (forceInitialFetch === true) {
return undefined;
}
}
return value;
};

const useFetchye = (
key,
Expand Down Expand Up @@ -52,16 +66,25 @@ const useFetchye = (
const { loading, data, error } = selectorState.current;
// If first render and options.forceInitialFetch is true we want to fetch from server
// on first render.
if (
(!loading && !data && !error)
|| (numOfRenders.current === 1 && options.forceInitialFetch === true)
) {
// We check the numOfRenders as two here as that is when this useEffect will actually be run
// so to make this work it needs to be on render 2
// If data is not set here then we know cache is empty,
// so fetch will happen anyway and this would just cause a second fetch.
if (data && options.forceInitialFetch === true && numOfRenders.current === 2) {
// This is so it clears the cache before the forceFetch so we don't have isLoading true
// and data also defined from the cached value.
dispatch(setAction({ hash: computedKey.hash, value: undefined }));
runAsync({
dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options,
});
return;
}
if (!loading && !data && !error) {
runAsync({
dispatch, computedKey, fetcher: selectedFetcher, fetchClient, options,
});
}
});

return {
isLoading: isLoading({
loading: selectorState.current.loading,
Expand All @@ -70,14 +93,20 @@ const useFetchye = (
options,
}),
error: passInitialData(
selectorState.current.error,
options.initialData?.error,
numOfRenders.current
{
value: selectorState.current.error,
initialValue: options.initialData?.error,
numOfRenders: numOfRenders.current,
forceInitialFetch: options.forceInitialFetch,
}
),
data: passInitialData(
selectorState.current.data,
options.initialData?.data,
numOfRenders.current
{
value: selectorState.current.data,
initialValue: options.initialData?.data,
numOfRenders: numOfRenders.current,
forceInitialFetch: options.forceInitialFetch,
}
),
run() {
return runAsync({
Expand Down
Loading