How to run two function on useEffect at once on first render - javascript

useEffect(() => {
const getCategory = () => {
allcategory().then((res) => {
if (res.data.error) {
console.log("error");
setValues({ ...values,
message: res.data.error
});
} else {
setValues({ ...values,
categories: res.data.category,
formData: new FormData(),
})
}
})
}
const getPublisher = () => {
allpublisher().then((res) => {
if (res.data.error) {
console.log("error");
setValues({ ...values,
message: res.data.error
});
} else {
setValues({ ...values,
publishers: res.data.publisher,
formData: new FormData(),
});
}
})
}
getCategory()
getPublisher()
}, [])
When I try to add two function on useEffect it doesn't render any data but show data on first render if only one function is present on useEffect

Since you are using promises, you can implement promise chaining here like
getCategory().then(()=>getPublisher())
But instead of having multiple promise chains, you can implement async await functions
const getData = async() => {
await getCategory()
await getPublisher()
}
useEffect(() => {
getData()
},[])

I think you miss async function.
I think this should like this
const getCategory = async () =>
const getPublisher = async () =>
And with my opinion, you should refractor like this:
const getCategory = async () => {
// ...do sth
}
const getPublisher = async () => {
// ...do sth
}
useEffect(() => {
getCategory();
getPublisher();
},[])

Related

React custom hook returning array and utilize the same in App component

I have my App component defined as below;
function App() {
const [state1, setState1] = useState({});
const [state2, setState2] = useState({});
const [isApiCallDone, setIsApiCallDone] = useState(false);
const fetchFn = useMyCustomFetch();
useEffect(() => {
(async function() {
try {
let [state11] = await fetchFn('api/api1', {}, 'GET');
let [state22] = await fetchFn('api/api2', {}, 'GET');
setState1(state11); // Is there a better way to set this ?
setState2(state22);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
useEffect(() => {
if (Object.keys(state1).length > 0 && Object.keys(state2).length > 0) {
// Set some other state variables on App
}
}, [state1, state2])
return (
<>
<MyContextProvider>
{isApiCallDone && (
<MyComponent />
)
}
</MyContextProvider>
</>
}
Also my useMyCustomFetch hook looks like below
export default function useMyCustomFetch() {
const fetchData = async (url, reqData, reqType) => {
try {
var statusObj = {
statMsg: null
};
const response = await fetch(url, reqOptions);
if (!response.ok) {
throw response;
}
statusObj.status = "success";
const json = await response.json();
return [json, statusObj];
} catch (error) {
statusObj.status = "error";
return [null, statusObj];
}
}
return fetchData;
}
My questions are;
For the lines
let [state11] = await fetchFn('api/api1', {}, 'GET');
setState1(state11);
I first assign it to a new variable state11 and then assign the same by calling setState1.
Is there a better way to set the state1 directly?
Is the usage of async function inside the useEffect fine ?
For Question 1:
You can directly setState like below without using state11.
useEffect(() => {
(async function () {
try {
setState1(
(await fetchFn("https://reqres.in/api/users/1", {}, "GET"))[0]
); // Is there a better way to set this ?
setState2(
(await fetchFn("https://reqres.in/api/users/2", {}, "GET"))[0]
);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
For Question 2:
I don't see any problem using async & IIFE in the useEffect. In fact I like the way its done. Looks good to me.
Please find screenshot of the state being set properly in the console (I have used a dummy api url):
If you don't want to use async functions, you can use the Promise.prototype.then() method to combine your calls like this :
useEffect(() => {
fetchFn('api/api1', {}, 'GET').then(state => {
setState1(state[0]);
return fetchFn('api/api2', {}, 'GET')
}).then(state => {
setState2(state[0]);
setIsApiCallDone(true);
}).catch(console.log);
}, []);
An other way to set this with an async function but more factorised is this way :
useEffect(() => {
(async function() {
try {
await fetchFn('api/api1', {}, 'GET')
.then(tab => tab[0])
.then(setState1);
await fetchFn('api/api2', {}, 'GET');
.then(tab => tab[0])
.then(setState2);
setIsApiCallDone(true);
} catch (e) {
console.error(e);
}
})();
}, []);
Finally, the usage of the async function in an useEffect is not a problem.

How to wait for all promises to resolve

I have two api call.
I want to do some calculation based on the results of both api.
I am using Promise.all() for waiting for both promises to resolve.
const getHashTagList = async () => {
loader.start();
try {
await getAllHashTags().then((response: any) => {
setHashtagList([...response?.data]);
});
} catch (err) {
} finally {
loader.stop();
}
};
const getUserFollowingHT = async () => {
loader.start();
try {
await getUserDetails().then((response: any) => {
setUserFollowingHT([...response?.data?.followingHashtags]);
});
} catch (err) {
} finally {
loader.stop();
}
};
For calling these 2 promises I am using below syntax:
useEffect(() => {
//getHashTagList();
// getUserFollowingHT();
Promise.all([getHashTagList, getUserFollowingHT]).then(
(combineResp) => {
console.log(combineResp);
}
);
}, []);
But in the output I am getting function declaration syntax.
It is not able to get call those promises.
Try this
useEffect(() => {
(async () => {
const values = await Promise.all([getHashTagList, getUserFollowingHT]);
console.log(values);
})();
}, []);

How to mock multiple promise .then in react hooks and check for state set in one .then

The question could be seen as similar to this one but is not working really the same way as that one is checking for a function to be called while im looking for a state to change.
The code i have is this one (headers and body are not really important in this case):
const useGetToken = () => {
const [token, setToken] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const fetchToken = useCallback(() => {
setLoading(true);
fetch('http://localhost.something', {
headers,
body,
})
.then((response) => {
response.json();
})
.then((response) => {
setToken(response.access_token);
})
.catch((e) => {
setError(e);
})
.finally(() => {
setLoading(false);
});
}, []);
return { fetchToken, token, error, loading };
};
what I am trying to find is a way to test that the output I have is the correct one in case of success and in case of error.
Seems like I can mock until the first .then but then i dont know how to mock the second one.
import { renderHook, act } from '#testing-library/react-hooks';
describe('useGetToken', () => {
it('should fetch and return a token', () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken' }),
}),
);
const { result } = renderHook(() => useGetToken());
// also how to check for the fetchToken function to equal to itself i dont know how to do
// or maybe i can check if it is just a function
expect(result.current).toEqual({ token: 'aToken', loading: false, error: false });
});
});
managed a way to fix it changing with async await and the correct act.
also changed the double .then in the file to this
.then((response) => {
const parsedResponse = response.json();
setToken(parsedResponse.access_token);
})
cause i didnt need two
import { renderHook, act } from '#testing-library/react-hooks';
import useGetToken from '../useGetVfsToken';
describe('useGetToken', () => {
it('should fetch when fetchToken is called', async () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => ({ access_token: 'aToken123' }),
}),
);
const { result } = renderHook(() => useGetToken());
expect(window.fetch).not.toHaveBeenCalled();
await act(async () => {
result.current.fetchToken();
});
expect(window.fetch).toHaveBeenCalledTimes(1);
expect(result.current.token).toEqual('aToken123');
expect(result.current.loading).toEqual(false);
expect(result.current.error).toBeUndefined();
});
it('should have an error', async () => {
const error = 'an error text';
global.fetch = jest.fn().mockImplementation(() => Promise.reject(error));
const { result } = renderHook(() => useGetToken());
await act(async () => {
result.current.fetchToken();
});
expect(result.current.error).toEqual(error);
expect(result.current.loading).toEqual(false);
});
});

Testing a fetch.catch in custom hook

I've got this custom hook:
import React from 'react';
import { useMessageError } from 'components/Message/UseMessage';
export interface Country {
code: string;
name: string;
}
export default function useCountry(): Array<Country> {
const [countries, setCountries] = React.useState<Country[]>([]);
const { showErrorMessage } = useMessageError();
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => showErrorMessage());
}, []);
return countries;
}
I want to test catching an error if there will be invalid response. With that, error message should appear thanks to showErrorMessage(). And I've got this test:
const showErrorMessage = jest.fn();
jest.mock('components/Message/UseMessage', () => ({
useMessageError: () => ({
showErrorMessage: showErrorMessage,
}),
}));
import useCountry from 'components/Country/useCountry';
import { renderHook } from '#testing-library/react-hooks';
import { enableFetchMocks } from 'jest-fetch-mock';
enableFetchMocks();
describe('The useCountry hook', () => {
it('should show error message', async () => {
jest.spyOn(global, 'fetch').mockImplementation(() =>
Promise.resolve({
json: () => Promise.reject(),
} as Response),
);
const { result, waitForNextUpdate } = renderHook(() => useCountry());
await waitForNextUpdate();
expect(fetch).toHaveBeenCalled();
expect(showErrorMessage).toHaveBeenCalled();
expect(result.current).toEqual([]);
});
});
But with that, I'm getting an error:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error
What I'm doing wrong in here? I assume it is somehow related with await waitForNextUpdate();, but I really don't know for sure and how to manage with it.
waitForNextUpdate() waits for next update but your hook does not trigger it since it only calls showErrorMessage(). Take a look at this sandbox
As a straightforward solution something that triggers an update can be added:
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => {
showErrorMessage();
// trigger update in any suitable way, for example:
setCountries([]);
});
}, []);
But it may be better to refactor it in some way. For example, you could use a separate hook and state for errors:
export default function useCountry(): Array<Country> {
const [countries, setCountries] = React.useState<Country[]>([]);
const [error, setError] = React.useState(null);
const { showErrorMessage } = useMessageError();
React.useEffect(() => {
fetch('/api/countries', {
method: 'GET',
})
.then(data => data.json())
.then(function(data) {
// ..
})
.catch(() => setError(true));
}, []);
React.useEffect(() => {
if (error) {
showErrorMessage()
}
}, [error]);
return countries;
}

how to check function is called or not in react js?

I am trying to test my service which have one function saveWithoutSubmit
export const saveWithoutSubmit = async (
values,
orderId,
taskId,
fseMsisdn,
updateTaskListAfterFilter
) => {
var obj = {
remarks: values.remarks,
requestedBy: localStorage.getItem("msisdn")
};
try {
const response = await sendPostRequest(`${API_TASK_URL}closeSr`, {
...obj,
saveWithoutSubmit: true
});
if (response && response.data && response.data.status.code !== "200") {
error(response.data.result.message);
} else {
console.log(response);
success(response.data.status.message);
updateTaskListAfterFilter();
}
} catch (e) {
if (e.response && e.response.data) {
console.log(e.response.data.message);
error(e.response.data.status.message);
}
}
};
I want to check success or error method is called or not ? or updateTaskListAfterFilter is called or not?
I tried like this
https://codesandbox.io/s/ecstatic-currying-5q1b8
describe("remark service test", () => {
const fakeAxios = {
get: jest.fn(() => Promise.resolve({ data: { greeting: "hello there" } }))
};
it("save without sumit", () => {
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, updateTaskListAfterFilter);
expect(updateTaskListAfterFilter).toBeCalled();
});
});
can you please suggest how i will test async methods or post request (using mook data)??
so that my test cases will be passed.
I want to check if I got success from promise my success method will be called else error
any update ?..!!
update
https://codesandbox.io/s/ecstatic-currying-5q1b8
it("save without sumit", async () => {
const sendPostRequest = jest.fn(() =>
Promise.resolve({ data: { greeting: "hello there" } })
);
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, updateTaskListAfterFilter);
expect(updateTaskListAfterFilter).toBeCalled();
});
it("save without sumit", async () => {
const sendPostRequest = jest.fn(() =>
Promise.resolve({ data: { greeting: "hello there" } })
);
const mockUpdateTaskListAfterFilter = jest.fn();
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, updateTaskListAfterFilter);
expect(updateTaskListAfterFilter).toBeCalled();
await wait(() => {
expect(mockUpdateTaskListAfterFilter).toBeCalled();
});
});
You should change it("save without sumit", () => { to it("save without sumit", async () => {.
Then you usually use jest.fn() to create a mock function that you will give to another function or component.
Finally, await the mock function to be called:
await wait(() => {
expect(mockUpdateTaskListAfterFilter).toBeCalled();
});
Alternatively, you can await some other event that you know will occur before your mock is called, like some other mock getting called or something appearing on the page, and then check that your mock was called.

Categories

Resources