Jest + Material-UI : Correctly mocking useMediaQuery - jestjs

I'm using Material-UI's useMediaQuery() function in one of my components to determine the size prop to use for a <Button> within the component.
I'm trying to test that it's working as expected in a jest test, however my current implementation isn't working:
describe("Unit: <Navbar> On xs screens", () => {
// Incorrectly returns `matches` as `false` ****************************
window.matchMedia = jest.fn().mockImplementation(
query => {
return {
matches: true,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn()
};
}
);
it("renders as snapshot", async () => {
const width = theme.breakpoints.values.sm - 1;
const height = Math.round((width * 9) / 16);
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: width
});
const { asFragment } = render(
<Container backgroundColor={"#ffffff"}>
<Navbar />
</Container>
);
expect(asFragment()).toMatchSnapshot();
const screenshot = await generateImage({
viewport: { width, height }
});
expect(screenshot).toMatchImageSnapshot();
});
});
describe("Unit: <Navbar> On md and up screens", () => {
// Correctly returns `matches` as `false` ****************************
window.matchMedia = jest.fn().mockImplementation(
query => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn()
};
}
);
it("renders as snapshot", async () => {
const width = theme.breakpoints.values.md;
const height = Math.round((width * 9) / 16);
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: width
});
const { asFragment } = render(
<Container backgroundColor={"#ffffff"}>
<Navbar />
</Container>
);
expect(asFragment()).toMatchSnapshot();
const screenshot = await generateImage({
viewport: { width, height }
});
expect(screenshot).toMatchImageSnapshot();
});
});
And the component I'm testing (removed irrelevant parts):
const Navbar = () => {
const theme = useTheme();
const matchXs = useMediaQuery(theme.breakpoints.down("xs"));
return (
<Button size={matchXs ? "medium" : "large"}>
Login
</Button>
);
};
export default Navbar;
It's returning matches as false for the first test, even though I've set it to return as true. I know this because it's generating a screenshot and I can see that the button size is set to large for the first test when it should be set to medium.
It works as expected in production.
How do I correctly get mock useMediaQuery() in a jest test?

The recommended way is using css-mediaquery which is now mentioned in the MUI docs:
import mediaQuery from 'css-mediaquery';
function createMatchMedia(width) {
return query => ({
matches: mediaQuery.match(query, { width }),
addListener: () => {},
removeListener: () => {},
});
}
describe('MyTests', () => {
beforeAll(() => {
window.matchMedia = createMatchMedia(window.innerWidth);
});
});

I figured it out...
useMediaQuery() needs to re-render the component to work, as the first render will return whatever you define in options.defaultMatches (false by default).
Also, the mock needs to be scoped to each test (it), not in the describe.
As I'm using react-testing-library, all I have to do is re-render the component again and change the scope of the mock and it works.
Here's the working example:
const initTest = width => {
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: width
});
window.matchMedia = jest.fn().mockImplementation(
query => {
return {
matches: width >= theme.breakpoints.values.sm ? true : false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn()
};
}
);
const height = Math.round((width * 9) / 16);
return { width, height };
};
describe("Unit: <Navbar> On xs screens", () => {
it("renders as snapshot", async () => {
const { width, height } = initTest(theme.breakpoints.values.sm - 1);
const { asFragment, rerender} = render(
<Container backgroundColor={"#ffffff"}>
<Navbar />
</Container>
);
rerender(
<Container backgroundColor={"#ffffff"}>
<Navbar />
</Container>
);
expect(asFragment()).toMatchSnapshot();
const screenshot = await generateImage({
viewport: { width, height }
});
expect(screenshot).toMatchImageSnapshot();
});
});
describe("Unit: <Navbar> On md and up screens", () => {
it("renders as snapshot", async () => {
const { width, height } = initTest(theme.breakpoints.values.md);
const { asFragment } = render(
<Container backgroundColor={"#ffffff"}>
<Navbar />
</Container>
);
rerender(
<Container backgroundColor={"#ffffff"}>
<Navbar />
</Container>
);
expect(asFragment()).toMatchSnapshot();
const screenshot = await generateImage({
viewport: { width, height }
});
expect(screenshot).toMatchImageSnapshot();
});
});

A simple approach that worked for me:
component.tsx
import { Fade, Grid, useMediaQuery } from '#material-ui/core';
...
const isMobile = useMediaQuery('(max-width:600px)');
component.spec.tsx
import { useMediaQuery } from '#material-ui/core';
// not testing for mobile as default
jest.mock('#material-ui/core', () => ({
...jest.requireActual('#material-ui/core'),
useMediaQuery: jest.fn().mockReturnValue(false),
}));
describe('...', () => {
it(...)
// test case for mobile
it('should render something for mobile', () => {
((useMediaQuery as unknown) as jest.Mock).mockReturnValue(true);
....
})

Inspired by #iman-mahmoudinasab's accepted answer, here is a TypeScript implementation. Essentially, we would want to create valid MediaQueryList-object:
// yarn add -D css-mediaquery #types/css-mediaquery
import mediaQuery from 'css-mediaquery';
describe('Foo Bar', () => {
beforeAll(() => {
function createMatchMedia(width: number) {
return (query: string): MediaQueryList => ({
matches: mediaQuery.match(query, { width }) as boolean,
media: '',
addListener: () => {},
removeListener: () => {},
onchange: () => {},
addEventListener: () => {},
removeEventListener: () => {},
dispatchEvent: () => true,
});
}
// mock matchMedia for useMediaQuery to work properly
window.matchMedia = createMatchMedia(window.innerWidth);
});
});

Maybe we can do something like this:
const applyMock = (mobile) => {
window.matchMedia = jest.fn().mockImplementation((query) => {
return {
matches: mobile,
media: query,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});
};
and use it like this:
const mockMediaQueryForMobile = () => applyMock(true);
const mockMediaQueryForDesktop = () => applyMock(false);

Related

Cannot read property 'createEvent' of null

I'm getting the error Cannot read property 'createEvent' of null when I use setTimeout in the code to wait for a while before navigating. The timeout is needed for a reason. The test is passing when I remove the timeout but need to make it work with the timeout really.
Here is the code I have.
useEffect(() => {
(async () => {
if (hasInternetConnection) {
...
} else {
setTimeout(() => {
navigation.dispatch(state => {
const routes = state.routes.filter(route => route.name === splashScreen)
routes.push({ name: homeNavigation, params: { screen: offlineDashboardScreen } })
return CommonActions.reset({
...state,
routes,
index: routes.length - 1
})
})
}, 1000)
}
})()
}, [hasInternetConnection, conductor])
it('navigates OfflineDashboardScreen if there is no internet connection', async () => {
useNetInfo.mockReturnValueOnce({
type: 'test', // not 'unknown'
isInternetReachable: false,
details: {},
isConnected: false
})
const component = (
<NavigationContainer>
<AppProviders>
<ThemeProvider theme={mockTheme}>
<AppNavigation/>
</ThemeProvider>
</AppProviders>
</NavigationContainer>
)
const rendered = render(component)
expect(await rendered.findByText(strings.om1)).toBeTruthy()
})
I've added jest.useFakeTimers() to setup files.

webRTC connection from browser to phone not working

I'm building a project using react native and webRTC with nodejs + ejs.
establish connections:
1- browser to browser works well.
2- device to browser works well.
3- browser to device does not work and here is the problem and all codes are the same for socket io and peer connections.
// REACT NATIVE PART
const [localStream, setLocalStream] = useState<MediaStream|null>(null);
const [remoteStream, setRemoteStream] = useState<MediaStream|null>(null);
const socket = io(utils.RAW_BACKEND_URL)
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: ['stun:stun.l.google.com:19302']
}
]
})
useEffect(()=> {
peerConnection.onaddstream = (e) => {
setRemoteStream(e.stream);
}
socket.emit('join-room')
socket.on('calling-user', async (args)=> {
setOffer(JSON.stringify(args.offer))
await peerConnection.setRemoteDescription(new RTCSessionDescription(args.offer));
utils.showAlertMessage("Calling", "You have a call", [
{
text: "Call?",
onPress: async ()=> await answerCall()
},
{
text: "No",
}
])
})
socket.on('ic-candidate', async(args)=> {
await peerConnection.addIceCandidate(new RTCIceCandidate(args.candidate))
})
socket.on('answer-made', async (args)=> {
console.log("HERE");
await peerConnection.setRemoteDescription(new RTCSessionDescription(args.answer));
setShow(true)
})
}, [])
const getUserMedia = async (callAUser?:boolean) => {
let isFront = true, videoSourceId: any = null;
try {
const availableDevices = await mediaDevices.enumerateDevices();
let videoSourceId;
for (let i = 0; i < availableDevices.length; i++) {
const sourceInfo = availableDevices[i];
if (
sourceInfo.kind == 'videoinput' &&
sourceInfo.facing == (isFront ? 'front' : 'environment')
) {
videoSourceId = sourceInfo.deviceId;
}
}
const mediaStream = await mediaDevices.getUserMedia({
// audio: true,
video: {
mandatory: {
minWidth: 500,
minHeight: 300,
minFrameRate: 30,
},
facingMode: 'user',
optional: [{sourceId: videoSourceId}]
}
})
setLocalStream(mediaStream);
peerConnection.addStream(mediaStream);
if(callAUser){
await callUser();
}
}catch(err: any) {
utils.showAlertMessage("ERRORsss", err.message);
}
}
const callUser = async () => {
peerConnection.onicecandidate = e => {
if(e.candidate) {
socket.emit('candidate', {
candidate: e.candidate
})
}
}
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
socket.emit('call-user', {
offer: offer
})
}
const answerCall = async () => {
await getUserMedia();
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(new RTCSessionDescription(answer));
socket.emit('make-answer', {
answer
})
}
return (
<View style={styles.stream}>
<Button title="Call" onPress={()=>getUserMedia(true)} />
<Button title="Answer" onPress={()=>answerCall()} />
{localStream != null &&
<RTCView
objectFit={"cover"}
streamURL={localStream.toURL()}
style={{height: '50%', borderWidth: 1, borderColor: 'red'}}
/>
}
{remoteStream != null && show ?
<RTCView
objectFit={"cover"}
streamURL={remoteStream.toURL()}
style={{height: '50%', borderWidth: 1, borderColor: 'blue'}}
/>
: <Text>{offer}</Text>
}
</View>
);
//js file for serving ejs page
const client = io("https://b277-2001-16a2-cb25-a800-f1da-b62e-7f94-42ee.ngrok.io");
const peerConnection = new RTCPeerConnection({
iceServers: [
{
urls: ['stun:stun.l.google.com:19302']
}
]
})
client.on('answer-made', async (args)=> {
await peerConnection.setRemoteDescription(new RTCSessionDescription(args.answer));
sdpAnswer.innerHTML = JSON.stringify(args.answer);
})
client.on('answer-made', async (args)=> {
await peerConnection.setRemoteDescription(new RTCSessionDescription(args.answer));
sdpAnswer.innerHTML = JSON.stringify(args.answer);
})
onst callUser = async () => {
await getMedia();
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(new RTCSessionDescription(offer));
peerConnection.onicecandidate = (e) => {
if(e.candidate) {
client.emit('candidate', {
candidate: e.candidate
})
}
}
client.emit('call-user', {
offer
})
}
// socket io part
client.join("q2");
client.on('join-room', (args)=> {
client.broadcast.to("q2").emit("user-joined", {
id: client.id
})
})
client.on("call-user", (args) => {
client.broadcast.to("q2").emit("calling-user", args);
});
client.on("make-answer", (args) => {
client.broadcast.to("q2").emit("answer-made", args);
});
client.on("candidate", (args) => {
client.broadcast.to("q2").emit("ic-candidate", args);
});
I hope you can help me what mistake I made and did not figure it.
Thanks in advance

Why do I have to refresh the page when I delete a post? MERN stack

I am a beginner in the MERN stack and I am interested in why I have to refresh the page after deleting the document (post)?
This is my Action.js
export const deletePost = id => async (dispatch, getState) => {
try {
dispatch({ type: DELETE_POST_BEGIN });
const {
userLogin: { userInfo },
} = getState();
const config = {
headers: {
Authorization: `Bearer ${userInfo.token}`,
},
};
const { data } = await axios.delete(`/api/v1/post/${id}`, config);
dispatch({ type: DELETE_POST_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: DELETE_POST_FAIL,
payload: { msg: error.response.data.msg },
});
}
};
This is my Reducer.js
export const deletePostReducer = (state = {}, action) => {
switch (action.type) {
case DELETE_POST_BEGIN:
return { loading: true };
case DELETE_POST_SUCCESS:
return { loading: false };
case DELETE_POST_FAIL:
return { loading: false, error: action.payload.msg };
default:
return state;
}
};
And this is my Home page where i list all posts:
import { useEffect } from 'react';
import { Col, Container, Row } from 'react-bootstrap';
import { useDispatch, useSelector } from 'react-redux';
import { getPosts } from '../actions/postActions';
import Loader from '../components/Loader';
import Message from '../components/Message';
import Post from '../components/Post';
const HomePage = () => {
const dispatch = useDispatch();
const allPosts = useSelector(state => state.getPosts);
const { loading, error, posts } = allPosts;
const deletePost = useSelector(state => state.deletePost);
const { loading: loadingDelete } = deletePost;
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
return (
<Container>
{loading || loadingDelete ? (
<Loader />
) : error ? (
<Message variant='danger'>{error}</Message>
) : (
<>
<Row>
{posts.map(post => (
<Col lg={4} key={post._id} className='mb-3'>
<Post post={post} />
</Col>
))}
</Row>
</>
)}
</Container>
);
};
export default HomePage;
And this is my single Post component:
const Post = ({ post }) => {
const dispatch = useDispatch();
const allPosts = useSelector(state => state.getPosts);
const { loading, error, posts } = allPosts;
const userLogin = useSelector(state => state.userLogin);
const { userInfo } = userLogin;
const handleDelete = id => {
dispatch(deletePost(id));
};
return (
<>
<div>{post.author.username}</div>
<Card>
<Card.Img variant='top' />
<Card.Body>
<Card.Title>{post.title}</Card.Title>
<Card.Text>{post.content}</Card.Text>
<Button variant='primary'>Read more</Button>
{userInfo?.user._id == post.author._id && (
<Button variant='danger' onClick={() => handleDelete(post._id)}>
Delete
</Button>
)}
</Card.Body>
</Card>
</>
);
};
And my controller:
const deletePost = async (req, res) => {
const postId = req.params.id;
const post = await Post.findOne({ _id: postId });
if (!post.author.equals(req.user.userId)) {
throw new BadRequestError('You have no permission to do that');
}
await Post.deleteOne(post);
res.status(StatusCodes.NO_CONTENT).json({
post,
});
};
I wish someone could help me solve this problem, it is certainly something simple but I am a beginner and I am trying to understand.
I believe the issue is that you are not fetching the posts after delete is successful.
Try this inside the HomePage component:
...
const [isDeleting, setIsDeleting] = useState(false);
const { loading: loadingDelete, error: deleteError } = deletePost;
useEffect(() => {
dispatch(getPosts());
}, [dispatch]);
useEffect(() => {
if (!deleteError && isDeleting && !loadingDelete) {
dispatch(getPosts());
}
setIsDeleting(loadingDelete);
}, [dispatch, deleteError, isDeleting, loadingDelete]);
...
Another method is to use "filtering", but you have to update your reducer as such:
export const deletePostReducer = (state = {}, action) => {
switch (action.type) {
case DELETE_POST_BEGIN:
return { loading: true };
case DELETE_POST_SUCCESS:
return { loading: false, data: action.payload}; // <-- this was changed
case DELETE_POST_FAIL:
return { loading: false, error: action.payload.msg };
default:
return state;
}
};
Now in your HomePage component, you will do something like this when rendering:
...
const { loading: loadingDelete, data: deletedPost } = deletePost;
...
useEffect(() => {
dispatch(getPosts());
if (deletedPost) {
console.log(deletedPost);
}
}, [dispatch, deletedPost]);
return (
...
<Row>
{posts.filter(post => post._id !== deletedPost?._id).map(post => (
<Col lg={4} key={post._id} className='mb-3'>
<Post post={post} />
</Col>
))}
</Row>
)

Group Video call using React-Native and Node js

I want to implement conference video call in React-native and node js using without any paid library
I was Implement single Person Video call using react-native-webrtc and node js Socket using Peer-to-Peer
My React-Native files below
My Privider Files
import React, { useState } from "react";
import { Alert } from "react-native";
import {
mediaDevices,
MediaStream,
MediaStreamConstraints,
} from "react-native-webrtc";
import socketio from "socket.io-client";
import ReactNativeForegroundService from "#supersami/rn-foreground-service";
import { MainContext as MainContextType, User } from "../interfaces";
import {
SERVER_URL,
PEER_SERVER_HOST,
PEER_SERVER_PORT,
PEER_SERVER_PATH,
} from "../server";
// #ts-ignore
import Peer from "react-native-peerjs";
import { navigate } from "../helpers/RootNavigation";
import InCallManager from "react-native-incall-manager";
const initialValues: MainContextType = {
username: "",
peerId: "",
users: [],
localStream: null,
remoteStream: null,
remoteUser: null,
initialize: () => {},
setUsername: () => {},
call: () => {},
switchCamera: () => {},
toggleMute: () => {},
isMuted: false,
isShareScreen: false,
toggleSpeacker: () => {},
isSpeaker: true,
swipeWindow: false,
closeCall: () => {},
reset: () => {},
localStreamSet: () => {},
activeCall: null,
};
export const MainContext = React.createContext(initialValues);
interface Props {}
const MainContextProvider: React.FC<Props> = ({ children }) => {
const [username, setUsername] = useState(initialValues.username);
const [peerId, setPeerId] = useState(initialValues.peerId);
const [users, setUsers] = useState<User[]>(initialValues.users);
const [localStream, setLocalStream] = useState<MediaStream | null>(
initialValues.localStream
);
const [remoteStream, setRemoteStream] = useState<MediaStream | null>(
initialValues.remoteStream
);
const [remoteUser, setRemoteUser] = useState<User | null>(null);
const [socket, setSocket] = useState<SocketIOClient.Socket | null>(null);
const [peerServer, setPeerServer] = useState<any>(null);
const [isMuted, setIsMuted] = useState(initialValues.isMuted);
const [swipeWindow, setSwipeWindow] = useState(initialValues.swipeWindow);
const [isShareScreen, setIsShareScreen] = useState(
initialValues.isShareScreen
);
// const localStreamSet=(newstream)=>{
// setLocalStream(null)
// }
const [isSpeaker, setIsSpeaker] = useState(initialValues.isSpeaker);
const [activeCall, setActiveCall] = useState<any>(null);
const initialize = async () => {
const isFrontCamera = true;
const devices = await mediaDevices.enumerateDevices();
const facing = isFrontCamera ? "front" : "environment";
const videoSourceId = devices.find(
(device: any) => device.kind === "videoinput" && device.facing === facing
);
const facingMode = isFrontCamera ? "user" : "environment";
const constraints: MediaStreamConstraints = {
audio: true,
video: {
mandatory: {
minWidth: 1280,
minHeight: 720,
minFrameRate: 30,
},
facingMode,
optional: videoSourceId ? [{ sourceId: videoSourceId }] : [],
},
};
const newStream = await mediaDevices.getUserMedia(constraints);
setLocalStream(newStream as MediaStream);
const io = socketio.connect(SERVER_URL, {
reconnection: true,
autoConnect: true,
});
io.on("connect", () => {
setSocket(io);
io.emit("register", username);
});
io.on("users-change", (users: User[]) => {
setUsers(users);
});
io.on("accepted-call", (user: User) => {
InCallManager.start("video");
setRemoteUser(user);
});
io.on("rejected-call", (user: User) => {
InCallManager.stop();
InCallManager.start();
setRemoteUser(null);
setActiveCall(null);
Alert.alert("Your call request rejected by " + user?.username);
navigate("Users");
});
io.on("not-available", (username: string) => {
setRemoteUser(null);
setActiveCall(null);
Alert.alert(username + " is not available right now");
navigate("Users");
});
const peerServer = new Peer(undefined, {
host: PEER_SERVER_HOST,
path: PEER_SERVER_PATH,
secure: true,
port: PEER_SERVER_PORT,
config: {
iceServers: [
{
urls: [
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
],
},
],
},
});
peerServer.on("error", (err: Error) =>
);
peerServer.on("open", (peerId: string) => {
setPeerServer(peerServer);
setPeerId(peerId);
io.emit("set-peer-id", peerId);
});
io.on("call", (user: User) => {
peerServer.on("call", (call: any) => {
InCallManager.startRingtone("_BUNDLE_");
setRemoteUser(user);
Alert.alert(
"New Call",
"You have a new call from " + user?.username,
[
{
text: "Reject",
onPress: () => {
InCallManager.stopRingtone();
InCallManager.stop();
io.emit("reject-call", user?.username);
setRemoteUser(null);
setActiveCall(null);
},
style: "cancel",
},
{
text: "Accept",
onPress: () => {
InCallManager.stopRingtone();
InCallManager.start();
InCallManager.setSpeakerphoneOn(true);
io.emit("accept-call", user?.username);
call.answer(newStream);
setActiveCall(call);
navigate("Call");
},
},
],
{ cancelable: false }
);
call.on("stream", (stream: MediaStream) => {
setRemoteStream(stream);
});
call.on("close", () => {
closeCall();
});
call.on("error", () => {});
});
});
};
const call = (user: User) => {
if (!peerServer || !socket) {
Alert.alert("Peer server or socket connection not found");
return;
}
if (!user.peerId) {
Alert.alert("User not connected to peer server");
return;
}
socket.emit("call", user.username);
setRemoteUser(user);
try {
const call = peerServer.call(user.peerId, localStream);
call.on(
"stream",
(stream: MediaStream) => {
setActiveCall(call);
setRemoteStream(stream);
},
(err: Error) => {
console.error("Failed to get call stream", err);
}
);
} catch (error) {
}
};
const switchCamera = () => {
if (localStream) {
// #ts-ignore
localStream.getVideoTracks().forEach((track) => track._switchCamera());
}
};
const toggleMute = () => {
if (localStream)
localStream.getAudioTracks().forEach((track) => {
track.enabled = !track.enabled;
setIsMuted(!track.enabled);
});
};
const toggleswipeWindow = () => {
var templocalstream = localStream;
var tempremotestream = remoteStream;
if (swipeWindow == false) {
setLocalStream(tempremotestream);
setRemoteStream(templocalstream);
setSwipeWindow(!swipeWindow);
} else {
setLocalStream(tempremotestream);
setRemoteStream(templocalstream);
setSwipeWindow(!swipeWindow);
}
};
const toggleScreenShare = async () => {
if (localStream)
if (isShareScreen == false) {
mediaDevices
.getDisplayMedia({ video: true, audio: true })
.then(handleSuccess, handleError);
InCallManager.setKeepScreenOn(true);
setIsShareScreen(true);
} else {
// localStream.getVideoTracks().forEach((track) => {track.stop()}
ReactNativeForegroundService.stop();
InCallManager.setKeepScreenOn(false);
setIsShareScreen(false);
}
};
const toggleSpeacker = () => {
if (localStream) InCallManager.start();
InCallManager.setSpeakerphoneOn(!isSpeaker);
setIsSpeaker(!isSpeaker);
};
const closeCall = () => {
activeCall?.close();
setActiveCall(null);
setRemoteUser(null);
navigate("Users");
Alert.alert("Call is ended");
};
const reset = async () => {
peerServer?.destroy();
socket?.disconnect();
setActiveCall(null);
setRemoteUser(null);
setLocalStream(null);
setRemoteStream(null);
setUsername("");
setPeerId("");
};
const handleError = async (error) => {
};
const handleSuccess = async (stream) => {
localStream.getVideoTracks().forEach((track) => {
localStream.removeTrack(track);
});
localStream.addTrack(stream.getTracks()[0]);
if (swipeWindow == true) {
setRemoteStream(stream);
} else {
setLocalStream(stream);
}
stream.getVideoTracks()[0].addEventListener("ended", () => {
});
};
return (
<MainContext.Provider
value={{
username,
setUsername,
peerId,
setPeerId,
users,
setUsers,
localStream,
setLocalStream,
remoteStream,
setRemoteStream,
initialize,
call,
switchCamera,
toggleMute,
isMuted,
isSpeaker,
toggleSpeacker,
closeCall,
reset,
remoteUser,
activeCall,
toggleScreenShare,
isShareScreen,
toggleswipeWindow,
swipeWindow,
// localStreamSet
}}
>
{children}
</MainContext.Provider>
);
};
export default MainContextProvider;
Callscreen.js File
import React, {useContext} from 'react';
import {
ActivityIndicator,
Dimensions,
StyleSheet,
Text,
View,
TouchableWithoutFeedback,
TouchableHighlight
} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
import {RTCView} from 'react-native-webrtc';
import IconButton from '../components/IconButton';
import icons from '../constants/icons';
import {CallScreenNavigationProp} from '../interfaces/navigation';
import {MainContext} from '../store/MainProvider';
import InCallManager from 'react-native-incall-manager';
const {width, height} = Dimensions.get('window');
import RecordScreen from 'react-native-record-screen';
import Video from 'react-native-video';
interface Props {
navigation: CallScreenNavigationProp;
}
const Call = ({}: Props) => {
const {
localStream,
remoteStream,
activeCall,
remoteUser,
isMuted,
isSpeaker,
toggleSpeacker,
toggleScreenShare,
isShareScreen,
closeCall,
toggleMute,
switchCamera,
swipeWindow,
toggleswipeWindow
} = useContext(MainContext);
React.useEffect(() => {
if (InCallManager.recordPermission !== 'granted') {
InCallManager.requestRecordPermission()
.then((requestedRecordPermissionResult) => {
})
.catch((err) => {
});
}
});
const [recordscreen,setRecordScreen]=React.useState(false)
const btnStyle = React.useMemo(() => {
return recordscreen ? styles.btnActive : styles.btnDefault;
}, [recordscreen]);
const [uri, setUri] = React.useState('');
const _handleOnRecording = async () => {
if (recordscreen) {
setRecordScreen(false);
const res = await RecordScreen.stopRecording().catch((error) =>
console.warn(error)
);
if (res) {
setUri(res.result.outputURL);
}
} else {
setUri('');
setRecordScreen(true);
await RecordScreen.startRecording().catch((error) => {
console.warn(error);
setRecordScreen(false);
setUri('');
});
}
};
return (
<SafeAreaView style={styles.container}>
{uri ? (
<View style={styles.preview}>
<Video
source={{
uri,
}}
style={styles.video}
/>
</View>
) : null}
{remoteStream && (
<RTCView
key={2}
mirror={true}
style={styles.remoteStream}
streamURL={remoteStream.toURL()}
objectFit="cover"
/>
)}
{localStream && (
<View style={styles.myStreamWrapper}>
<TouchableWithoutFeedback onPress={toggleswipeWindow}>
<RTCView
style={styles.myStream}
objectFit="cover"
streamURL={localStream.toURL()}
zOrder={1}
/>
</TouchableWithoutFeedback>
</View>
)}
{!activeCall && (
<View style={styles.spinnerWrapper}>
<ActivityIndicator color="#341EFF" size={120} />
<Text style={styles.callingText}>Calling {remoteUser?.username}</Text>
</View>
)}
<View style={styles.iconsWrapper}>
<IconButton
icon={icons.CHANGE_CAMERA}
onPress={switchCamera}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
{isMuted ? (
<IconButton
icon={icons.UNMUTE}
onPress={toggleMute}
iconColor={'#fff'}
backgroundColor="red"
/>
) : (
<IconButton
icon={icons.MUTE}
onPress={toggleMute}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
{isSpeaker ? (
<IconButton
icon={icons.SPEAKERON}
onPress={toggleSpeacker}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
) : (
<IconButton
icon={icons.SPEAKEROFF}
onPress={toggleSpeacker}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
{recordscreen ? (
<IconButton
icon={icons.RECORDING}
onPress={_handleOnRecording}
iconColor={'#fff'}
backgroundColor="red"
/>
) : (
<IconButton
icon={icons.RECORDING}
onPress={_handleOnRecording}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
{isShareScreen ? (
<IconButton
icon={icons.SCREENSHARE}
onPress={toggleScreenShare}
iconColor={'#fff'}
backgroundColor="red"
/>
) : (
<IconButton
icon={icons.SCREENSHARE}
onPress={toggleScreenShare}
iconColor={'#341EFF'}
backgroundColor="#fff"
/>
)}
<IconButton
icon={icons.END_CALL}
onPress={closeCall}
iconColor={'#fff'}
backgroundColor="red"
/>
</View>
</SafeAreaView>
);
};
export default Call;
const styles = StyleSheet.create({
preview: {
position: 'absolute',
right: 12,
bottom: 116,
width: Dimensions.get('window').width / 2,
height: Dimensions.get('window').height / 3,
zIndex: 1,
padding: 8,
backgroundColor: '#aaa',
},
video: {
flex: 1,
},
container: {
backgroundColor: '#0f0f0f',
flex: 1,
position: 'relative',
},
btnDefault: {
width: 48,
height: 48,
backgroundColor: '#fff',
borderRadius: 24,
borderWidth: 4,
borderStyle: 'solid',
borderColor: '#212121',
},
btnActive: {
width: 36,
height: 36,
backgroundColor: 'red',
borderRadius: 8,
},
myStream: {
height: width * 0.6,
width: width * 0.4,
},
myStreamWrapper: {
position: 'absolute',
bottom: 20,
right: 20,
height: width * 0.6 + 8,
width: width * 0.4 + 8,
backgroundColor: '#333',
borderRadius: 12,
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center',
},
remoteStreamWrapper: {},
remoteStream: {
width: '100%',
height: '100%',
},
spinnerWrapper: {
top: height * 0.3,
width: '100%',
justifyContent: 'center',
alignItems: 'center',
},
callingText: {
fontSize: 26,
color: '#fff',
},
iconsWrapper: {
position: 'absolute',
bottom: 20,
left: 20,
},
});
Socket Server.js
//socketio
const socketio = require("socket.io");
class SocketService {
io;
constructor() {
this.io = null;
}
listen = (server) => {
this.io = socketio(server);
this.io.users = {};
this.io.on("connection", (socket) => {
socket.on("register", (username) => this.onRegister(socket, username));
socket.on("set-peer-id", (peerId) => this.onSetPeerId(socket, peerId));
socket.on("call", (username) => this.onCall(socket, username));
socket.on("reject-call", (username) =>
this.onRejectCall(socket, username)
);
socket.on("accept-call", (username) =>
this.onAcceptCall(socket, username)
);
console.log(`${Date(Date.now()).toLocaleString()}: new user connected`);
socket.on("disconnect", () => this.onDisconnect(socket));
});
};
onAcceptCall = (socket, username) => {
if (this.io.users[username])
this.io
.to(this.io.users[username].socketId)
.emit("accepted-call", this.io.users[socket.username]);
};
onRejectCall = (socket, username) => {
if (this.io.users[username]) {
this.io
.to(this.io.users[username].socketId)
.emit("rejected-call", this.io.users[socket.username]);
}
};
onCall = (socket, username) => {
if (this.io.users[username]) {
this.io
.to(this.io.users[username].socketId)
.emit("call", this.io.users[socket.username]);
} else {
socket.emit("not-available", username);
}
};
onRegister = (socket, username) => {
console.log("Registered", username);
socket.username = username;
this.io.users[username] = {
username,
peerId: "",
socketId: socket.id,
};
this.onUsersChange(socket);
};
getUsers = () => {
const users = [];
Object.keys(this.io.users).forEach((key) => {
users.push(this.io.users[key]);
});
return users;
};
onUsersChange = (socket) => {
this.io.emit("users-change", this.getUsers());
};
onSetPeerId = (socket, peerId) => {
console.log("Set Peer Id user:", socket.username, " peerId: ", peerId);
this.io.users[socket.username] = {
peerId,
socketId: socket.id,
username: socket.username,
};
this.onUsersChange();
};
onDisconnect = (socket) => {
delete this.io.users[socket.username];
console.log(
`${Date(Date.now()).toLocaleString()} ID:${
socket.username
} user disconnected`
);
this.onUsersChange();
};
emit = (event, userId, data) => {
if (this.io.users[userId]) {
this.io.to(this.io.users[userId]).emit(event, data);
}
};
}
module.exports = new SocketService();
Peer Server.js (2nd API server in Node js)
require('dotenv').config()
const express = require("express");
const { ExpressPeerServer } = require("peer");
const app = express();
app.get("/", (req, res, next) => res.send("Hello world!"));
const http = require("http");
const server = http.createServer(app);
const peerServer = ExpressPeerServer(server, {
debug: true,
path: "/",
});
app.use("/peerjs", peerServer);
server.listen(process.env.PORT || 9000);

source.uri is null coming from aws on react native

I'm sourcing the image on aws and when I run the code through i get warning error that source.uri is null and the image wont display. Im getting the image from the server cause i can see the image in the debugger but it wont display in the code.
This is my action file src/actions/item.js
import { HOST } from '../constants';
import { normalizeItems, normalizeItem } from '../utils';
export const SET_ITEMS = 'SET_ITEMS';
export function setItems(items) {
return {
type: SET_ITEMS,
items
}
}
export function getItems() {
return (dispatch, getState) => {
return fetch(`${HOST}/api/v1/items`)
.then(response => response.json())
.then(json => {
console.log("getItems", json);
if (json.is_success) {
dispatch(setItems(normalizeItems(json.items)));
} else {
alert(json.error);
}
})
.catch(e => alert(e));
}
}
This the image of the debugger shows the i have the image there
This is my reducers file src/reducers/item.js
import { SET_ITEMS,SET_ITEM } from '../actions/item';
const initialState = {
items: [],
};
export default function(state = initialState, action) {
if (action.type === SET_ITEMS) {
return {
...state,
items: action.items
}
}
This file where i render my code src/components/MainScreen/ExploreTab.js
class ExploreTab extends Component {
componentWillMount() {
this.props.getItems();
}
onPress(item) {
this.props.navigate({ routeName: "Item", params: { item: item } });
}
render() {
const { items, filter } = this.props;
return (
<FlatList
style={styles.container}
data = { items }
renderItem={({item}) =>
<TouchableOpacity onPress={() => this.onPress(item)} style={styles.item}>
<Image style={styles.image} source = {{uri: item.image}} />
<Text style = {styles.title}>{`$${item.price} ${item.instant ? '🎉 ' : ''}${item.title}`}</Text>
<Text>{`${item.itemCategory} - ${item.itemCondition} `}</Text>
</TouchableOpacity>
}
keyExtractor={(item, index) => item.id}
/>
);
}
}
const mapStateToProps = state => ({
items: state.item.items
});
const mapDispatchToProps = dispatch => ({
navigate: (route) => dispatch(navigate(route)),
getItems: () => dispatch(getItems()),
});
if I change this line
<Image style={styles.image} source = {{uri: item.image}} />
to this I will see the image
<Image style={styles.image} source = {{uri: 'https://s3.us-east-2.amazonaws.com/borroup/photos/images/1/medium/Screen_Shot_2018-03-22_at_10.48.28_PM.png'}} />
In the src/utils/index.js
I had setup as
import { HOST } from '../constants';
export function normalizeItems(items) {
return items.map(item => {
return{
id: item.id || '',
title: item.item_name ||'',
image: `${HOST}${item.image}` ||'',
itemCategory: item.item_category ||'',
itemCondition: item.item_condition ||'',
price: item.price ||'',
instant: item.instant ||'',
}
})
}
so i had to change
image: `${HOST}${item.image}` ||'',
to this and worked for me
image: `${item.image}` ||'',

Resources