I have a custom fetch hook. It is not doing a real fetch process.
It's an implantation example. I want to write a proper test for it.
This is the pseudo API.
import { data } from './data';
import { IProduct } from '../common/types';
// pseudo API
export const getProducts = (): Promise<IProduct[]> => {
return new Promise((resolve, reject) => {
if (!data) {
return setTimeout(() => reject(new Error('data not found')), 1000);
}
setTimeout(() => resolve(data), 1000);
});
};
Here is the useFetch.ts
import { useEffect, useState } from 'react';
import { getProducts } from '../api/api';
import { IProduct } from '../common/types';
export const useFetch = () => {
const [products, setProducts] = useState<IProduct[]>([]);
const [categories, setCategories] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<unknown>(null);
useEffect(() => {
const fetchProducts = async () => {
setLoading(true);
try {
const res = await getProducts();
const categories = Array.from(new Set(res.map((r) => r.category)));
setProducts(res);
setCategories(categories);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchProducts();
}, []);
return { products, categories, loading, error };
};
Here is the useFetch.test.ts
import { renderHook } from '#testing-library/react-hooks';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { data } from '../api/data';
import { useFetch } from './useFetch';
const server = setupServer(
rest.get('/api', (req, res, ctx) => {
return res(ctx.json(data));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('gets the data', async () => {
const { result, waitForNextUpdate } = renderHook(() => useFetch());
await waitForNextUpdate();
expect(result.current).toEqual(data);
});
How can I write a proper test for this case?
I'm getting the received, expected error.
expect(received).toEqual(expected) // deep equality
Related
Learning react, currently trying to create a video call web app, however I get this error:
TypeError: Cannot read properties of undefined (reading 'addEventListener')
on this line of code:
useEffect(() => {
peer.addEventListener("negationneeded",handleNegotiation);
return () =>{
peer.removeEventListener("negotionneeded",handleNegotiation);
};
},[]);
handleNegotiation:
const handleNegotiation = useCallback(() => {
const localOffer = peer.localDescription;
socket.emit("call-user",{userID: remoteUserID, offe: localOffer });
}, []);
here is also the whole file:
import React, {useEffect, useCallback, useState} from 'react';
import ReactPlayer from "react-player";
import { useSocket} from "../providers/Socket";
import { usePeer } from "../providers/Peer";
const SessionPage = () => {
const { socket } = useSocket();
const { peer, createOffer, createAnswer,setRemoteAns,sendStream,remoteStream } = usePeer();
const [myStream,setMyStream] = useState(null);
const [remoteUserID, setRemoteUserID] = useState();
const handleNewUserJoined = useCallback(
async(data) =>{
const {userID} = data
console.log("New user joined the session",userID);
const offer = await createOffer();
socket.emit('call-user',{ userID, offer });
setRemoteUserID(userID);
},
[createOffer,socket]
);
const handleIncomingCall = useCallback( async(data) => {
const {from, offer} = data;
console.log("Incoming Call from", from, offer);
const ans = await createAnswer(offer);
socket.emit("call-accepted",{userID: from, ans});
setRemoteUserID(from);
},
[createAnswer, socket] );
const handleCallAccepted = useCallback(async(data) => {
const {ans} = data;
console.log("Call Got Accepted",ans);
await setRemoteAns(ans);
}, [setRemoteAns]);
const getUserMediaStream = useCallback(async() => {
const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
setMyStream(stream);
}, []);
const handleNegotiation = useCallback(() => {
const localOffer = peer.localDescription;
socket.emit("call-user",{userID: remoteUserID, offe: localOffer });
}, []);
useEffect(() => {
socket.on("user-joined",handleNewUserJoined);
socket.on("incoming-call",handleIncomingCall);
socket.on("call-accepted",handleCallAccepted);
//return () =>{
// socket.off("user-joined",handleNewUserJoined);
//socket.off("incoming-call", handleIncomingCall);
//socket.off("call-accepted",handleCallAccepted);
//};
}, [handleCallAccepted,handleIncomingCall, handleNewUserJoined, socket]);
useEffect(() => {
peer.addEventListener("negationneeded",handleNegotiation);
return () =>{
peer.removeEventListener("negotionneeded",handleNegotiation);
};
},[]);
useEffect(() => {
getUserMediaStream();
},[]);
return(
<div className='session-page-container'>
<h1>Hi mom, Im on TV :D</h1>
<h4>You are now online with {remoteUserID}</h4>
<button onClick={(e) => sendStream(myStream)}>Share my video</button>
<ReactPlayer url={myStream} playing muted/>
<ReactPlayer url={remoteStream} playing/>
</div>
)
}
export default SessionPage;
--> Peer file
import React, { useMemo, useEffect, useState, useCallback } from "react";
const peerContext = React.createContext(null);
export const usePeer = () => React.createContext(null);
export const PeerProvider = (props) => {
const [remoteStream, setRemoteStream] = useState(null);
const peer = useMemo(() =>
new RTCPeerConnection({
iceServers: [
{
urls: [
"stun:stun.l.google.com:19302",
"stun:global.stun.twilio.com:3478",
],
},
],
}),
[]
);
const createOffer = async() => {
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);
return offer;
};
const createAnswer = async (offer) => {
await peer.setRemoteDescription(offer);
const answer = await peer.createAnswer();
await peer.setLocalDescription(answer);
return answer;
};
const setRemoteAns = async(ans) =>{
await peer.setRemoteDescription(ans);
};
const sendStream = async(stream) => {
const tracks = stream.getTracks();
for(const track of tracks){
peer.addTrack(track,stream);
}
};
const handleTrackEvent = useCallback((ev) =>{
const streams = ev.streams;
setRemoteStream(streams[0]);
}, [])
useEffect(() => {
peer.addEventListener("track",handleTrackEvent);
return () =>{
peer.removeEventListener("track",handleTrackEvent)
}
},[handleTrackEvent, peer])
return(
<peerContext.Provider value={{ peer, createOffer, createAnswer, setRemoteAns, sendStream,remoteStream}}>{props.children}</peerContext.Provider>
);
};
useEffect with calling addEventListener on peer works earlier than some value assigned there.
Just add check of value existed:
useEffect(() => {
if (!peer) return
peer.addEventListener("track",handleTrackEvent);
return () =>{
peer.removeEventListener("track",handleTrackEvent)
}
},[handleTrackEvent, peer])
Trying to test a status hook that uses a promise that is not getting updated by my test.
screens.OnStart() should trigger setStatus with the value the promise returns.
When I log status it never changes.
import { useEffect, useState } from 'react'
import screens from '#utils/screen'
const useStatus = () => {
const [status, setStatus] = useState()
useEffect(() => {
const listener = screens.OnStart(
"HAPPEN",
({ status }) =>
setStatus(status)
)
return () => {
screens.removeListener("HAPPEN", listener)
}
}, [])
return {
status,
}
}
export default useStatus
Test
import React from 'react'
import { act, renderHook } from '#testing-library/react-hooks'
import useStatus from '#hooks/useStatus'
const mockedOnStart = jest.fn().mockImplementation((event, callback) => callback)
jest.mock('#utils/screens', () => ({
...jest.requireActual('#utils/screens'),
default: {
OnStart: () => mockedOnStart(),
},
__esModule: true,
}))
describe('useStatus', () => {
test('Renders', async () => {
mockedOnStart.mockReturnValueOnce(2)
const { result } = renderHook(() => useStatus())
await act(async () => {
console.log('result = ', result.current.status)
})
})
})
We have a dynamic route system in our chat app in react frontend and nestjs backend. when we are not refreshing the page but going to a different route, and calling socket event, then the app goes to the previous routes where we visited before. But when we refresh the page and stay in the one route, it stays there.
the problem is, when we change the route, it remembers the previous routes where we visited and goes to that route when socket calling, when we do not refresh the page, this problem occurs.
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { userActiveStatusApi } from '../api/auth';
import { getAllMessageApi, makeReadApi, sendMessageApi } from '../api/chat';
import { selectUserProfile } from '../redux/features/authSlice';
import { selectActiveUser, setUpdateConversation, setUpdateUnreadCount } from '../redux/features/layoutSlice';
import ChattingHomeUi from '../ui/chattingHome/ChattingHomeUi';
import { newSocket } from '../utils/socket';
import { getDateWiseMessages } from '../utils/utils';
const ChattingHome = () => {
let { chatId } = useParams();
const [currentUserProfile, setCurrentUserProfile] = useState({})
const [isLoading, setIsLoading] = useState(true);
const [messagesText, setMessagesText] = useState('')
const [allMessage, setAllMessage] = useState([])
const userProfile = useSelector(selectUserProfile)
const onlineUsers = useSelector(selectActiveUser)
const dispatch = useDispatch();
const userId = userProfile.id;
// check user online status function
function isOnline(id) {
return onlineUsers.indexOf(id) !== -1;
}
// get current user profile function
const getCurrentUserProfile = useCallback(async () => {
async function successHandler(response) {
const res = await response.json();
setCurrentUserProfile(res.user)
setIsLoading(false);
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error.message);
setIsLoading(false);
}
return await userActiveStatusApi(chatId, { successHandler, handleBadReq })
}, [chatId])
// get current user all message list function
const getAllMessage = useCallback(async () => {
async function successHandler(response) {
const res = await response.json();
let sortedData = getDateWiseMessages(res)
setIsLoading(false)
setAllMessage(sortedData)
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error)
setIsLoading(false);
}
return await getAllMessageApi(chatId, { userId: userId }, { successHandler, handleBadReq })
}, [userId, chatId])
// add or update message on conversations List
const updateConversationList = (res) => {
const newMessage = {
users_id: parseInt(res.receiverId),
users_profileImage: currentUserProfile.profileImage,
users_fullname: currentUserProfile.fullname,
message_Status_lastMessage: res?.content,
message_Status_lastMessageTime: res?.createdAt,
message_Status_unreadMessages: 0,
}
dispatch(setUpdateConversation(newMessage))
}
// Send message function
async function handleSubmitMessage() {
if (!messagesText.length) return;
const messageData = {
message: messagesText,
senderId: userId,
type: "text"
}
async function successHandler(response) {
const res = await response.json();
getDeliveryStatus()
updateConversationList(res.result)
setMessagesText('')
getAllMessage();
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error.message);
setIsLoading(false);
}
return await sendMessageApi(chatId, messageData, { successHandler, handleBadReq })
}
const getDeliveryStatus = () => {
newSocket.on('delevered/' + userId, (res) => {
console.log(res)
})
}
// make message as read message
const makeReadMessage = useCallback(async () => {
newSocket.once('message-seen-status' + chatId, (msg) => {
console.log("red", msg);
})
const payload = {
senderId: chatId,
}
async function successHandler(response) {
dispatch(setUpdateUnreadCount(chatId))
}
async function handleBadReq(response) {
let error = await response.json();
console.log(error);
}
return await makeReadApi(userProfile.id, payload, { successHandler, handleBadReq })
}, [dispatch, chatId, userProfile.id])
useEffect(() => {
getCurrentUserProfile()
makeReadMessage()
}, [getCurrentUserProfile, makeReadMessage])
useEffect(() => {
console.log(chatId)
newSocket.on('newMessage/user/' + userId, (msg) => {
if (parseInt(msg.senderId) === parseInt(chatId)) {
makeReadMessage();
}
getAllMessage();
})
}, [chatId, makeReadMessage, getAllMessage, userId])
useEffect(() => {
getAllMessage()
}, [getAllMessage])
return (
<ChattingHomeUi
handleSubmitMessage={handleSubmitMessage}
messagesText={messagesText}
setMessagesText={setMessagesText}
allMessage={allMessage}
userProfile={userProfile}
isOnline={isOnline}
isLoading={isLoading}
currentUserProfile={currentUserProfile} />
);
};
export default ChattingHome;
I have used match.params.id but recent update is not helping.
import { getPost } from '../../service/api';
const DetailView =({ match }) => {
const classes = useStyle();
const url = 'https://images.';
const [post, setPost] = useState({});
useEffect(() => {
const fetchData = async () => {
let data = await getPost(match.params.id);
console.log(data);
setPost(data);
}
fetchData();
}, []);
This is another calling by id code Inside another post-controller.js
export const getPost = async (request, response) => {
try {
const post = await Post.findById(request.params.id);
response.status(200).json(post);
} catch (error) {
response.status(500).json(error)
}
}
Try to use the useParams hook from react-router-dom:
import { useParams } from 'react-router-dom';
const DetailView =() => {
const { id } = useParams();
useEffect(() => {
const fetchData = async () => {
let data = await getPost(id);
...
}
fetchData();
}, []);
i am a newbie to react but i'm learning and need your help here.
I use Auth0 for Authentication and i have implemented their react sample in parts:
https://auth0.com/docs/quickstart/spa/react/01-login
This are parts of my code:
App.js:
<Auth0Provider
domain={AUTH_CONFIG.domain}
client_id={AUTH_CONFIG.clientId}
redirect_uri={AUTH_CONFIG.callbackUrl}
onRedirectCallback={onRedirectCallback}
>
<Router history={history}>
<RequireAuthentication>
<MyTheme>
<MyLayout />
</MyTheme>
</RequireAuthentication>
</Router>
</Auth0Provider>
Auth0Provider:
import React, { useState, useEffect, useContext } from "react";
import createAuth0Client from "#auth0/auth0-spa-js";
import jwtDecode from "jwt-decode";
import axios from "axios";
import AUTH_CONFIG from "./auth0Config";
import { useDispatch } from "react-redux";
import * as authActions from "app/auth/store/actions";
const DEFAULT_REDIRECT_CALLBACK = () =>
window.history.replaceState({}, document.title, window.location.pathname);
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallback = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popupOpen, setPopupOpen] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
const initAuth0 = async () => {
console.log("initAuth0 start");
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
const isAuthenticated = await auth0FromHook.isAuthenticated();
console.log("Authenticated from init: " + isAuthenticated);
setIsAuthenticated(isAuthenticated);
setLoading(false);
console.log("initAuth0 end");
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopup = async (params = {}) => {
setPopupOpen(true);
try {
await auth0Client.loginWithPopup(params);
} catch (error) {
console.error(error);
} finally {
setPopupOpen(false);
}
const user = await getUserData();
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = await getUserData();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
dispatch(authActions.setUserDataAuth0(user));
};
const getAccessToken = async () => {
const accessToken = await auth0Client.getTokenSilently({
audience: AUTH_CONFIG.identity_audience,
scope: "read:allUsers read:UserPermission"
});
return accessToken;
};
const getIdToken = async () => {
if (!auth0Client) {
console.warn("Auth0 Service didn't initialize, check your configuration");
return;
}
const claims = await auth0Client.getIdTokenClaims();
return claims.__raw;
};
const getTokenData = async () => {
const token = await getIdToken();
const decoded = jwtDecode(token);
if (!decoded) {
return null;
}
return decoded;
};
const getUserData = async () => {
console.log("getuserdata");
const tokenData = await getTokenData();
const accessToken = await getAccessToken();
return new Promise((resolve, reject) => {
const { sub: userId } = tokenData;
const UserService =
"https://localhost:44312/api/v1/usermanagement/user/" + userId;
axios
.get(UserService, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": "GET,HEAD,OPTIONS,POST,PUT",
"Access-Control-Allow-Headers":
"Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers",
"Content-Type": "application/json",
Authorization: "Bearer " + accessToken
}
})
.then(response => {
resolve(response.data);
})
.catch(error => {
// handle error
console.warn("Cannot retrieve user data", error);
reject(error);
});
});
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popupOpen,
loginWithPopup,
handleRedirectCallback,
getIdTokenClaims: (...p) => auth0Client.getIdTokenClaims(...p),
loginWithRedirect: (...p) => auth0Client.loginWithRedirect(...p),
getTokenSilently: (...p) => auth0Client.getTokenSilently(...p),
getTokenWithPopup: (...p) => auth0Client.getTokenWithPopup(...p),
logout: (...p) => auth0Client.logout(...p)
}}
>
{children}
</Auth0Context.Provider>
);
};
RequireAuthentication:
import React, { useEffect } from "react";
import { useAuth0 } from "app/auth/AuthProvider";
import { SplashScreen } from "#my";
import history from "#history";
export const RequireAuthentication = ({ children }) => {
const { isAuthenticated, loading } = useAuth0();
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, []);
const checkAuth = () => {
console.log("checkAuth isAuthenticated: " + isAuthenticated);
console.log("checkAuth loading: " + loading);
if (!isAuthenticated && !loading) {
history.push("/login");
}
};
return isAuthenticated ? (
<React.Fragment>{children}</React.Fragment>
) : (
<SplashScreen />
);
};
callback.js:
import React, { useEffect } from "react";
import { SplashScreen } from "#my";
import { useAuth0 } from "app/auth/AuthProvider";
function Callback(props) {
const { isAuthenticated, handleRedirectCallback, loading } = useAuth0();
useEffect(() => {
const fn = async () => {
if (!loading) {
console.log("handleRedirectCallback: " + loading);
await handleRedirectCallback();
}
};
fn();
}, [isAuthenticated, loading, handleRedirectCallback]);
return <SplashScreen />;
}
export default Callback;
The problem is that the RequireAuthentication Component is rendered before the Auth0Provider is completely initialized and therefore i get never the isAuthenticated on "true".
The RequireAuthentication Component is a child of the Auth0Provider. Is it possible to wait for the Auth0Provider is fully initialized before rendering the RequireAuthentication Component???
What is the right way here?? Am I completely wrong?
Thanks
Chris
Depend on loading and isAuthenticated items in useEffect so that component will re render once they change.
useEffect(() => {
console.log("checkAuth");
if (!loading) checkAuth();
// eslint-disable-next-line
}, [loading, isAuthenticated]);