My issue is that when I start a call for the first time I receive remote stream and the loadMetaData() works fine, but when I close the call and try to call the same peerId I receive the remote stream but the loadMetadata() doesn't emit any events.
MainContainer.tsx
import React, { useState, useEffect } from "react";
// #ts-ignore
import { Peer } from "peerjs";
import decodeJWT from "jwt-decode";
import callStates from "../helpers/callStates";
import { CALL_EVENTS } from "../helpers/callEvents";
import VideoBox from "./InCallView";
import OutgoingCallView from "./OutgoingCallView";
import IncomingCallView from "./IncomingCallView";
import StandbyView from "./StandbyView";
import { Box } from "#mui/material";
export interface Contacts {
firstName: string;
lastName: string;
peerId: string;
}
function MainContainer() {
const [callState, setCallState] = useState(callStates.standby);
const [contactsList, setContactsList] = useState<Contacts[]>([]);
const [callerState, setCallerState] = useState<"receiver" | "caller">();
const resetCallState = () => {
setCallState(callStates.standby);
};
useEffect(() => {
peer.on("connection", (conn: any) => {
conn.on("data", async (data: { event: string }) => {
console.log("EVENT TYPE:: ", data.event);
if (data.event === CALL_EVENTS.CALL_REQUEST) {
setCallerState("receiver");
if (callState === callStates.standby) {
setCallState(callStates.incomingCall);
}
}
if (data.event === CALL_EVENTS.ANSWERED_CALL) {
setCallerState("caller");
setCallState(callStates.inCall);
}
if (data.event === CALL_EVENTS.REJECT_CALL) {
resetCallState();
}
if (data.event === CALL_EVENTS.END_CALL) {
resetCallState();
}
});
});
// eslint-disable-next-line
}, [peer]);
const handleCall = async () => {
console.log("%c Call sent! ", "background: #222; color: #bada55; font-size: 25px");
const conn = await peer.connect(alaaId);
console.log({ conn });
setCallState(callStates.outgoingCall);
conn.on("open", () => {
console.log("%c connection opened! ", "background: #222; color: #bada55");
conn.send({ event: CALL_EVENTS.CALL_REQUEST });
});
};
const answerCall = async () => {
await setCallState(callStates.inCall);
const conn = peer.connect(alaaId);
conn.on("open", () => {
conn.send({ event: CALL_EVENTS.ANSWERED_CALL });
});
};
const rejectCall = () => {
const conn = peer.connect(alaaId);
conn.on("open", () => {
conn.send({ event: CALL_EVENTS.REJECT_CALL });
resetCallState();
});
};
const endCall = () => {
const conn = peer.connect(alaaId);
conn.on("open", () => {
resetCallState();
conn.send({ event: CALL_EVENTS.END_CALL });
});
};
const hangupCall = () => {
const conn = peer.connect(alaaId);
console.log({ conn });
conn.on("open", () => {
conn.send({ event: CALL_EVENTS.HANGUP_CALL });
resetCallState();
});
};
return (
<>
<Box sx={{ display: callState === callStates.standby ? "none" : "block" }}>
{callState === callStates.inCall && <VideoBox callState={callerState} peer={peer} peerId={alaaId} endCall={endCall} />}
{callState !== callStates.standby && callState !== callStates.inCall && (
<React.Fragment>
{callState === callStates.outgoingCall && <OutgoingCallView hangupCall={hangupCall} />}
{callState === callStates.incomingCall && <IncomingCallView answerCall={answerCall} rejectCall={rejectCall} />}
</React.Fragment>
)}
</Box>
{callState === callStates.standby && <StandbyView handleCall={handleCall} contactsList={contactsList} />}
</>
);
}
export default MainContainer;
InCallView.tsx
import { useDebugValue, useEffect, useState } from "react";
function useUserMedia(constraints: any) {
const [stream, setStream] = useState<MediaStream | null>();
const [error, setError] = useState();
useDebugValue({ error, stream });
useEffect(() => {
console.log("inside main useeffect");
let canceled = false;
navigator.mediaDevices.getUserMedia(constraints).then(
(stream) => {
if (!canceled) {
console.log("%c inside canceled condition", "font-size:60px");
setStream(stream);
}
},
(error) => {
if (!canceled) {
setError(error);
}
}
);
return () => {
canceled = true;
setStream(null);
};
}, [constraints]);
useEffect(
() => () => {
console.log("inside cancel useeffect");
if (stream)
stream.getTracks().forEach((track) => {
track.stop();
});
},
[stream]
);
return { error, stream };
}
export default useUserMedia;
useMediaStream hook
import { useState, useEffect } from "react";
export function useUserMedia() {
const [mediaStream, setMediaStream] = useState<MediaStream>();
useEffect(() => {
async function enableVideoStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
frameRate: { exact: 15, ideal: 15 },
echoCancellation: true,
noiseSuppression: true,
width: { min: 300, max: 640 },
height: { min: 300, max: 480 },
},
});
setMediaStream(stream);
} catch (err) {
console.log("Video error: ", err);
}
}
if (!mediaStream) {
enableVideoStream();
} else {
return function cleanup() {
mediaStream.getTracks().forEach((track) => {
track.stop();
});
};
}
// eslint-disable-next-line
}, [mediaStream]);
return mediaStream;
}
I trie many solutions but non of them works with me
Related
i am new, and am getting acquainted with i reactjs and nodejs, i am writing a website in which i want to write a function to get the current login information of the user through redux, and then after then assign the obtained user value to react-select, then from react-select, we select that user to perform the assignment of new data to the database from reactjs. However, I have not been able to get the logged in user information through redux. This is my code, anyone have any ideas? Thanks very much
here is my FE(Reactjs code):
here is DoctorManageSchedule.js:
import React, { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import './ManageSchedule.scss';
import Select from 'react-select';
import * as actions from "../../../store/actions";
import { CRUD_ACTIONS, LANGUAGES, dateFormat } from '../../../utils';
import DatePicker from '../../../components/Input/DatePicker';
import moment from 'moment';
import { toast } from "react-toastify";
import _ from 'lodash';
import { saveBulkScheduleDoctor, getScheduleDoctorById } from '../../../services/userService';
import DetailDoctor from './DetailDoctor';
class DoctorManageSchedule extends Component {
constructor(props) {
super(props);
this.state = {
arrDoctor: [],
selectedDoctor: {},
currentDate: '',
rangeTime: [],
minDate: moment().calendar(),
}
}
async componentDidMount() {
this.props.fetchDoctor(this.props.match.params.id);
this.props.fetchAllScheduleTimes();
}
async componentDidUpdate(prevProps, prevState, snapshot) {
if (prevProps.doctor !== this.props.doctor) {
let dataSelect = this.buildDataInputSelect(this.props.doctor)
this.setState({
arrDoctor: dataSelect
})
}
if (prevProps.allScheduleTime !== this.props.allScheduleTime) {
let data = this.props.allScheduleTime;
if (data && data.length > 0) {
data = data.map(item => ({ ...item, isSelected: false }))
}
this.setState({
rangeTime: data
})
}
}
buildDataInputSelect = (inputData) => {
let result = [];
let { language } = this.props;
if (inputData && inputData.length > 0) {
inputData.map((item, index) => {
let object = {};
let labelEn = `${item.lastName} ${item.firstName}`;
let labelVi = `${item.firstName} ${item.lastName}`;
object.label = language === LANGUAGES.VI ? labelVi : labelEn;
object.value = item.id;
result.push(object)
})
}
return result;
}
handleChangeSelect = async (selectedOption) => {
this.setState({ selectedDoctor: selectedOption });
}
handleOnChangeDatePicker = (date) => {
this.setState({
currentDate: date[0]
})
}
handleClickBtnTime = (time) => {
let { rangeTime } = this.state;
if (rangeTime && rangeTime.length > 0) {
rangeTime = rangeTime.map(item => {
if (item.id === time.id) item.isSelected = !item.isSelected;
return item;
})
this.setState({
rangeTime: rangeTime
})
}
}
handleSaveSchedule = async () => {
let { rangeTime, selectedDoctor, currentDate } = this.state;
let result = [];
if (!currentDate) {
toast.error("Invalid date!");
}
if (selectedDoctor && _.isEmpty(selectedDoctor)) {
toast.error("Invalid selected doctor! ");
console.log('check doctor: ', this.state)
return;
}
let formatedDate = new Date(currentDate).getTime();
if (rangeTime && rangeTime.length > 0) {
let selectedTime = rangeTime.filter(item => item.isSelected === true);
if (selectedTime && selectedTime.length > 0) {
selectedTime.map((schedule, index) => {
let object = {};
object.doctorId = selectedDoctor.value;
object.date = formatedDate;
object.timeType = schedule.keyMap;
result.push(object);
})
} else {
toast.error("Invalid selected time! ");
return;
}
}
let res = await saveBulkScheduleDoctor({
arrSchedule: result,
doctorId: selectedDoctor.value,
formatedDate: formatedDate
})
if (res && res.errCode === 0) {
toast.success("Save Infor succeed!");
} else {
toast.error("error saveBulkScheduleDoctor ");
console.log('error saveBulkScheduleDoctor >>> res: ', res)
}
console.log('bao phuc check result: ', result);
console.log('check res: saveBulkScheduleDoctor : ', res);
}
render() {
let { rangeTime, arrDoctor } = this.state;
console.log("check doctor:", arrDoctor)
let { language } = this.props;
let today = new Date(new Date().setDate(new Date().getDate()));
return (
<div className="manage-schedule-container">
<div className="m-s-title">
<FormattedMessage id="manage-schedule.title"></FormattedMessage>
</div>
<div className="container">
<div className="row">
<div className="col-6 form-group">
<label>
<FormattedMessage id="manage-schedule.choose-doctor" /> </label>
<Select
value={this.state.selectedDoctor}
onChange={this.handleChangeSelect}
options={this.state.listDoctors}
/>
</div>
<div className="col-6 form-group">
<label>
<FormattedMessage id="manage-schedule.choose-date" /> </label>
<DatePicker
value={this.state.currentDate}
className="form-control"
onChange={this.handleOnChangeDatePicker}
minDate={today}
/>
</div>
<div className="col-12 pick-hour-container">
{rangeTime && rangeTime.length > 0 &&
rangeTime.map((item, index) => {
return (
<button className={item.isSelected === true ?
"btn btn-schedule active" : "btn btn-schedule"}
key={index} onClick={() => this.handleClickBtnTime(item)}>
{language === LANGUAGES.VI ? item.valueVi : item.valueEn}
</button>
)
})}
</div>
<div className="col-12">
<button className="btn btn-primary btn-save-schedule"
onClick={() => this.handleSaveSchedule()}>
<FormattedMessage id="manage-schedule.save" />
</button>
</div>
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
language: state.app.language,
isLoggedIn: state.user.isLoggedIn,
doctor: state.admin.doctor,
allScheduleTime: state.admin.allScheduleTime,
};
};
const mapDispatchToProps = dispatch => {
return {
fetchDoctor: () => dispatch(actions.fetchDoctorStart()),
fetchAllScheduleTimes: () => dispatch(actions.fetchAllScheduleTimes())
};
};
export default connect(mapStateToProps, mapDispatchToProps)(DoctorManageSchedule);
and here is my actionTypes.js:
const actionTypes = Object.freeze({
FETCH_DOCTOR_SUCCESS: 'FETCH_DOCTOR_SUCCESS',
FETCH_DOCTOR_FAILED: 'FETCH_DOCTOR_FAILED',
})
export default actionTypes;
and here is my adminActions.js:
export const fetchDoctorStart = id => () => {
return async (dispatch, getState) => {
try {
let res = await getScheduleDoctorById(id);
if (res && res.errCode === 0) {
dispatch({
type: actionTypes.FETCH_DOCTOR_SUCCESS,
dataDoctor: res.data
})
} else {
toast.error("Failed to fetch doctor");
dispatch(fetchDoctorFailed());
}
} catch (e) {
toast.error("Failed to fetch doctor");
dispatch(fetchDoctorFailed());
console.log("check fetch doctor failed: ", e);
}
}
};
export const fetchDoctorSuccess = (data) => ({
type: actionTypes.FETCH_DOCTOR_SUCCESS,
doctor: data
})
export const fetchDoctorFailed = () => ({
type: actionTypes.FETCH_DOCTOR_FAILED,
})
here is my adminReducer.js:
case actionTypes.FETCH_DOCTOR_SUCCESS:
state.doctor = action.dataDoctor;
return {
...state,
}
case actionTypes.FETCH_DOCTOR_FAILED:
state.doctor = [];
return {
...state,
}
here is my userService.js:
const getScheduleDoctorById = (inputId) => {
return axios.get(`/api/get-schedule-doctor-by-id?id=${inputId}`)
}
here is my BE(Nodejs code):
here is web.js:
router.get('/api/get-schedule-doctor-by-id', doctorController.getScheduleById);
here is doctorController.js:
let getScheduleById= async (req, res) => {
try {
let infor = await doctorService.getScheduleById(req.query.id);
return res.status(200).json(infor);
} catch (e) {
console.log(e);
return res.status(200).json({
errCode: -1,
errMessage: 'Error from the server'
})
}
}
here is doctorService.js:
let getScheduleById = (inputId) => {
return new Promise(async (resolve, reject) => {
try {
if (!inputId) {
resolve({
errCode: 1,
errMessage: 'Missing required parameter!'
})
} else {
let data = await db.User.findOne({
where: {
id: inputId
},
attributes: {
exclude: ['password']
},
include: [
{ model: db.Allcode, as: 'positionData', attributes: ['valueEn', 'valueVi'] },
{
model: db.Doctor_Infor,
attributes: {
exclude: ['id', 'doctorId']
}
},
],
raw: false,
nest: true
})
if (data && data.image) {
data.image = new Buffer(data.image, 'base64').toString('binary');
}
if (!data) data = {};
resolve({
errCode: 0,
data: data
})
}
} catch (e) {
reject(e);
}
})
}
when i run the app i only get the available time slots from the database, but no info about who is logged in, i checked the network tab, but it seems the api gets the user info via redux do not run. Or does anyone have a way to do it other than using redux, that the user (doctor) can set his own schedule and save it to the database? However, I don't know why, please comment, thanks a lot
this is my code which I have to combined two history components in one. I am trying to combined both using if else loop and it runs as well. but in both icon it will show the all outdated processes.
useEffect(() => {
getVersions('');
}, []);
useEffect(() => {
if (selected_process && selected_process.id) {
if (selected_process.root_version) {
getVersions(selected_process.root_version.id.toString());
} else {
getVersions(selected_process.id.toString());
}
}
}, [props.selected_process]);
const getVersions = (id: string) => {
if (selected_process) {
const url = `${props.location.search}?version=${id}`;
props.searchOutdatedProcessesAction(url, (res: any) => {
res.data.objects.length > 0 && setFetchedProcesses(res.data.objects);
});
}
if (outdated_process) {
const url = `?outdated=true&limit=0&order_by=-date_created`;
props.searchOutdatedProcessesAction(url, (res: any) => {
res.data.objects.length > 0 && setFetchedProcesses(res.data.objects);
});
}
};
const getOutDatedVersions = () => {
return fetchedProcesses.filter((i: Process) => i.id != selected_process?.id);
};
const renderList = () => {
if (fetchedProcesses) {
return (
<div className={styles.tableWrapper}>
<Stack className={styles.table}>
<DetailsList
items={getOutDatedVersions(selected_process=true, outdated_process=true)}
columns={columnsList}
setKey="none"
layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={true}
selectionMode={SelectionMode.none}
/>
</Stack>
</div>
);
}
return <></>;
};
*************************************************************************************************************************************************************************************************************************************************************************************************************+
I have merged separated useEffect function for both processes using if else loop. Just added in getVersion function with outdated processes and added return in getoutdatedversions function.
useEffect(() => {
if (selected_process && selected_process.id) {
if (selected_process.root_version) {
getVersions(selected_process.root_version.id.toString());
} else {
getVersions(selected_process.id.toString());
}
} else {
getVersions('');
}
}, [props.selected_process]);
const getVersions = (id: string) => {
if (selected_process) {
const url = `${props.location.search}?version=${id}`;
props.searchOutdatedProcessesAction(url, (res: any) => {
res.data.objects.length > 0 && setFetchedProcesses(res.data.objects);
});
} else {
const url = `?outdated=true&limit=0&order_by=-date_created`;
props.searchOutdatedProcessesAction(url, (res: any) => {
res.data.objects.length > 0 && setFetchedProcesses(res.data.objects);
});
}
};
const getOutDatedVersions = () => {
if (selected_process) {
return fetchedProcesses.filter((i: Process) => i.id != selected_process?.id);
} else {
return fetchedProcesses.filter(() => outdated_process);
}
};
I wrote a component that uses Player from #vimeo/player but now I am unsure how to write tests that are not focused on implementation details. I am using testing-library.
Specifically, I am looking for a way to test state changes that stem from these events (loaded, bufferstart, bufferend, play, pause):
useEffect(() => {
async function setupPlayer() {
if (playerRef.current) {
playerRef.current.on('loaded', () => setIsLoading(false));
playerRef.current.on('bufferstart', () => setIsBuffering(true));
playerRef.current.on('bufferend', () => setIsBuffering(false));
playerRef.current.on('play', () => setIsPlaying(true));
playerRef.current.on('pause', () => setIsPlaying(false));
}
}
if (playerRef.current) {
playerRef.current = new Player(
playerRef.current,
{ url: `https://player.vimeo.com/video/${video}`,
loop: true,
title: false,
portrait: false,
controls: false,
byline: false,
muted: true,
responsive: true },
);
setupPlayer();
}
return () => playerRef.current && playerRef.current.destroy();
}, [playerRef]);
My goal is to test for example that a loading/buffering has been rendered until loaded/bufferend events are fired.
/* eslint-disable max-len */
import React, { useEffect, useRef, useState } from 'react';
import Button from 'button';
import { IconScan, IconPause, IconPlay } from 'iconography';
import Loader from 'loader';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import Player from '#vimeo/player';
const VIDEO_PLAYER_INDEX = 2;
const VideoPlayer = ({ wrapperEl,
video,
style,
wrapperClassNames,
renderedInOverlay,
onSetOverlay,
isMobile }) => {
const playerRef = useRef();
const [isLoading, setIsLoading] = useState(true);
const [isPlaying, setIsPlaying] = useState(false);
const [isHovering, setIsHovering] = useState(true);
const [isBuffering, setIsBuffering] = useState(false);
const [width, setWidth] = useState(0);
const [height, setHeight] = useState(0);
useEffect(() => {
async function setupPlayer() {
if (playerRef.current) {
playerRef.current.on('loaded', (e) => {
setIsLoading(false);
console.log(e);
});
playerRef.current.on('bufferstart', () => setIsBuffering(true));
playerRef.current.on('bufferend', () => setIsBuffering(false));
playerRef.current.on('play', () => setIsPlaying(true));
playerRef.current.on('pause', () => setIsPlaying(false));
}
}
if (playerRef.current) {
playerRef.current = new Player(
playerRef.current,
{ url: `https://player.vimeo.com/video/${video}`,
loop: true,
title: false,
portrait: false,
controls: false,
byline: false,
muted: true,
responsive: true },
);
setupPlayer();
}
return () => playerRef.current && playerRef.current.destroy();
}, [playerRef]);
useEffect(() => {
if (isMobile && isPlaying && !isBuffering) {
setIsHovering(false);
}
}, [isPlaying, isMobile, isBuffering]);
useEffect(() => {
if (playerRef.current) {
Promise.all([playerRef.current.getVideoWidth(), playerRef.current.getVideoHeight()]).then((dimensions) => {
setWidth(dimensions[0]);
setHeight(dimensions[1]);
});
}
}, [isMobile, playerRef]);
const handlePlayClick = () => {
if (isPlaying) {
playerRef.current.pause();
} else if (!isPlaying && !isBuffering) {
playerRef.current.play();
}
};
const renderIframe = () => {
if (!video) return null;
const showControls = isHovering || !isPlaying || isBuffering;
const showPlay = isPlaying && !isBuffering;
const aspectRatio = (width / height) * 100;
const wrapperBtnStyle = renderedInOverlay ? { style: { maxWidth: `${aspectRatio}vh` } } : {};
return (
<div className={ classNames('video-player', { 'video-player--is-loading': isLoading }) }>
<Button
className="video-player__wrapper-btn"
variant="naked"
onClick={ () => handlePlayClick() }
onMouseEnter={ () => setIsHovering(true) }
onMouseLeave={ () => setIsHovering(false) }
{ ...wrapperBtnStyle }>
<div
ref={ playerRef }
className="video-player__player"
data-testid="video-player"
/>
{ showControls &&
<div className="video-player__controls-container">
<div className="video-player__controls-btn" >
{showPlay ?
<IconPause size="lg" title="Pause" className="video-player__control-icon" />
:
<IconPlay size="lg" title="Play" className="video-player__control-icon" />
}
</div>
{isBuffering && (
<div className="video-player__buffer-container">
<div className="video-player__buffer-indicator" />
</div>)}
</div>
}
</Button>
{ !renderedInOverlay &&
<div className="video-player__fullscreen-btn-container">
<Button
className="video-player__fullscreen-btn"
variant="naked"
onClick={ (e) => onSetOverlay(e, false, VIDEO_PLAYER_INDEX) }
onTouchStart={ (e) => onSetOverlay(e, false, VIDEO_PLAYER_INDEX) }>
<IconScan size="lg" title="Fullscreen" />
</Button>
</div>
}
<Loader className="video-player__loader" height={ 30 } />
</div>
);
};
return (
wrapperEl ?
React.createElement(
wrapperEl,
{
className: classNames(wrapperClassNames),
style,
},
renderIframe(),
)
: renderIframe()
);
};
VideoPlayer.defaultProps = {
video: '',
wrapperEl: '',
wrapperClassNames: '',
style: {},
renderedInOverlay: false,
isMobile: false,
};
VideoPlayer.propTypes = {
video: PropTypes.string.isRequired,
wrapperEl: PropTypes.string,
wrapperClassNames: PropTypes.string,
style: PropTypes.shape({}),
renderedInOverlay: PropTypes.bool,
isMobile: PropTypes.bool,
};
export {
VideoPlayer,
VIDEO_PLAYER_INDEX,
};
Is someone able to give me a pointer how I can achieve that? Thanks!
These days I create advanced chat app. But I cant solve this problem now. How Can I do 'user typing' in socket.io and react.
This is my page when I write message:
import {Input, Button } from 'antd'
import { useConversation } from "../../conversation";
import { SendOutlined } from '#ant-design/icons'
import {Auth} from '../../context'
import {useState, useEffect} from 'react'
import {useSocket} from '../../socket'
export default function Bottom(){
const [text, setText] = useState('')
const [ok, setok] = useState(false)
const {sendMessage, currentConversation} = useConversation()
const {user} = Auth()
const socket = useSocket()
useEffect( ()=>{
if(text.length > 0 && !ok ) {
socket.emit('send-typing', {currentConversation, user })
setok(true)
}
if(text.length === 0 && ok){
socket.emit('stop-typing', {currentConversation, user})
setok(false)
}
},[text] )
function handleSubmit() {
const {recipients, ...other} = currentConversation;
sendMessage(
currentConversation.recipients.map(r => r._id),
{recipients: recipients.push(user), ...other},
text
)
setok(false)
socket.emit('stop-emit', {currentConversation, user})
setText('')
}
return (<div className="open_bottom">
<Input.Group compact >
<Input.TextArea placeholder="Write message" value={text} style={{ resize: 'none' }} onChange={(e)=>setText(e.target.value)}
onPressEnter={handleSubmit}
/>
<Button type="primary" onClick={handleSubmit} > <SendOutlined/> </Button>
</Input.Group>
</div>)
}
This is my backend. Its work successfully:
io.on('connection', socket => {
const id = socket.handshake.query.id
socket.join(id)
console.log("user ", id, " connected" );
socket.on('send-message', async ( {recipients, sender , current, text}, callback )=>{
recipients.forEach(recipient => {
socket.broadcast.to(recipient).emit('receive-message', {
current, sender, text
})
})
let err=false;
try {
const nw = new Messages({ about: current._id, sender: sender._id, text, who_deleted:[], readers:[] });
await nw.save();
} catch (e) {
err=true;
}
callback({err: err})
});
socket.on('send-typing', ({currentConversation, user})=>{
console.log("typing ", currentConversation._id);
currentConversation.recipients.forEach( r=>{
socket.broadcast.to( r ).emit('get-typing', { current: currentConversation, writer: user })
})
})
socket.on('stop-typing', ({currentConversation, user})=>{
console.log("stop-typing ", currentConversation._id);
currentConversation.recipients.forEach( r=>{
socket.broadcast.to( r ).emit('dur-typing', { current: currentConversation, writer: user })
})
})
socket.on('disconnect', ()=>{
console.log("user ", id, " disconnect");
})
})
Problem here: socket.on('dur-typing', ...) and socket.on('get-typing', ...) cannot work. Really words I didnt understand why cannot work or cannot get data. I didnt understand where I must use socket.off. Please help fix this problem
import React, {useContext, useEffect, useState, useMemo, useCallback} from 'react'
import {useSocket} from './socket'
import {Auth} from './context'
const ConversationsContext = React.createContext()
export function useConversation(){
return useContext(ConversationsContext)
}
export default function ConversationProvider( {children} ){
const socket = useSocket()
const [conversations, setConversations] = useState([])
const [currentConversation, setCurrentConversation] = useState(null)
const [typing, setTyping] = useState([])
const [msg, setMsg] = useState(false)
const [typ, setTyp] = useState(false)
const [dur, setDur] = useState(false)
const {user} = Auth()
const addMessageToConversation = useCallback(({ current, sender, text}) => {
setMsg(true)
setConversations(prevConversations => {
let madeChange = false
const newMessage = { sender, text }
const newConversations = prevConversations.map(conversation => {
if( conversation._id === current._id ){
madeChange = true
return {
...conversation,
messages: [...conversation.messages, newMessage]
}
}
return conversation
})
if (madeChange) {
return newConversations
} else {
return [
...prevConversations,
{ ...current, messages: [newMessage] }
]
}
})
}, [setConversations])
useEffect(() => {
if (socket == null) return
setMsg(false)
setTyp(false)
setDur(false)
socket.on('receive-message', addMessageToConversation)
socket.on('get-typing', (current, writer)=>{
console.log("get-typing", current, writer);
setTyp(true)
setTyping( prev=>{
let ok = false;
const nw = prev.map( i=>{
if( i.current._id === current._id ){
ok = true;
return {current, writers: [...i.writers, writer]}
}
else return i;
} )
if(ok) return nw;
else return [...prev, {current, writers: [writer]}]
})
})
socket.on('dur-typing', (current, writer)=>{
setDur(true)
console.log("dur-typing", current, writer);
setTyping( prev=>{
const nw = prev.map( i=>{
if( i.current._id === current._id ){
const wr = i.writers.filter( t =>{
return t._id !== writer._id
} ) ;
return {current, writers: wr }
}
else return i;
} )
return nw;
})
})
return () =>{
if(msg) socket.off('receive-message')
// if(typ) socket.off('get-typing')
// if(dur) socket.off('dur-typing')
}
}, [socket, addMessageToConversation, typing])
console.log(typing);
// useEffect(()=>{
// if(currentConversation === null || socket==null ) return
// socket.emit('read-message', {_id: currentConversation._id, user: user._id}, ({err})=>{
// if(err) alert( "Cannot update!" );
// })
// }, [socket, currentConversation ])
function sendMessage(recipients, current, text) {
socket.emit('send-message', { recipients, sender: user, current, text }, ({err})=>{
if(err) alert(" Try again! ")
});
addMessageToConversation({ current, sender:user , text })
}
function rnd_arr(arr) {
var currentIndex = arr.length, randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[arr[currentIndex], arr[randomIndex]] = [arr[randomIndex], arr[currentIndex]];
}
return arr;
}
const formatted = useMemo ( ()=> conversations.map( i=>{
const { recipients, groupName, ...other } = i;
const all = recipients.filter( t=>{
return t._id !== user._id
})
const logos = rnd_arr(all)
let l = [], name = ""
l.push( logos[0].logo )
name = logos[0].username
if( logos.length > 1 ){
l.push(logos[1].logo)
if( groupName.length > 0 ) name = groupName
else name += ',...'
}
return {recipients: all, name, logo: l, isGroup: all.length > 1,groupName, isAdmin: i.admins.includes(user._id), ...other }
} ), [conversations])
const t = useMemo( ()=>{
if( currentConversation === null ) return null;
else return formatted[formatted.findIndex( t => {
return ( t._id === currentConversation._id );
} )]
}, [formatted,currentConversation] )
const value = {
setConversations,
conversations: formatted,
setCurrentConversation,
currentConversation: t,
sendMessage,
typing
}
return (
<ConversationsContext.Provider value={value}>
{ children }
</ConversationsContext.Provider>
)
}
I have a reusable component for Sign in with Apple Button
After user success, i navigate hem to Home screen
But i notes when i log navigation it's log undefined,
and when i log this.props i just got the two actions i made in redux!
So how can i access to navigation in this component and why it's not accessed by default!
Log
props => {"isLogin": [Function isLogin], "storeToken": [Function storeToken]}
navigation => undefined
Code
import appleAuth, {
AppleAuthCredentialState,
AppleAuthError,
AppleAuthRealUserStatus,
AppleAuthRequestOperation,
AppleAuthRequestScope,
AppleButton,
} from '#invertase/react-native-apple-authentication';
import React from 'react';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {connect} from 'react-redux';
import API from '../../api/API';
import {isLoginFunc} from '../../redux/actions/isLoginAction';
import {saveToken} from '../../redux/actions/saveTokenAction';
class AppleAuth extends React.Component {
constructor(props) {
super(props);
this.authCredentialListener = null;
this.user = null;
this.state = {
credentialStateForUser: -1,
loading: false,
};
}
componentDidMount() {
const {navigation} = this.props;
console.log('did-navigation', navigation);
console.log('did- this.props', this.props);
/**
* subscribe to credential updates.This returns a function which can be used to remove the event listener
* when the component unmounts.
*/
this.authCredentialListener = appleAuth.onCredentialRevoked(async () => {
// console.warn('Credential Revoked');
this.fetchAndUpdateCredentialState().catch(error =>
this.setState({credentialStateForUser: `Error: ${error.code}`}),
);
});
this.fetchAndUpdateCredentialState()
.then(res => this.setState({credentialStateForUser: res}))
.catch(error =>
this.setState({credentialStateForUser: `Error: ${error.code}`}),
);
}
componentWillUnmount() {
/**
* cleans up event listener
*/
this.authCredentialListener();
}
signIn = async () => {
// start a login request
try {
const appleAuthRequestResponse = await appleAuth.performRequest({
requestedOperation: AppleAuthRequestOperation.LOGIN,
requestedScopes: [
AppleAuthRequestScope.EMAIL,
AppleAuthRequestScope.FULL_NAME,
],
});
this.setState({loading: true});
const {
user: newUser,
email,
nonce,
fullName: {familyName, givenName},
identityToken,
realUserStatus /* etc */,
} = appleAuthRequestResponse;
let username = `${givenName} ${familyName}`;
this.user = newUser;
this.fetchAndUpdateCredentialState()
.then(res => {
this.setState({credentialStateForUser: res});
console.log('res:::', res);
})
.catch(error => {
console.log(`Error: ${error.code}`);
this.setState({credentialStateForUser: `Error: ${error.code}`});
});
if (identityToken) {
console.log('email', email);
console.log('username', username);
console.log('nonce', nonce);
this.sendData(email, username, nonce);
// e.g. sign in with Firebase Auth using `nonce` & `identityToken`
} else {
// no token - failed sign-in?
}
if (realUserStatus === AppleAuthRealUserStatus.LIKELY_REAL) {
console.log("I'm a real person!");
}
// console.warn(`Apple Authentication Completed, ${this.user}, ${email}`);
} catch (error) {
if (error.code === AppleAuthError.CANCELED) {
alert('User canceled Apple Sign in');
// console.warn('User canceled Apple Sign in.');
} else {
console.error(error);
}
}
};
fetchAndUpdateCredentialState = async () => {
if (this.user === null) {
this.setState({credentialStateForUser: 'N/A'});
} else {
const credentialState = await appleAuth.getCredentialStateForUser(
this.user,
);
if (credentialState === AppleAuthCredentialState.AUTHORIZED) {
this.setState({credentialStateForUser: 'AUTHORIZED'});
} else {
this.setState({credentialStateForUser: credentialState});
}
}
};
// Send data "name,image,email" to API
sendData = async (Email, Name, Id) => {
try {
let response = await API.post('/apple', {
email: Email,
name: Name,
id: Id,
});
let {
data: {
data: {
response: {token},
},
},
} = response;
console.log('token:?>:', token);
console.log('props', this.props);
console.log('navigation', this.props.navigation);
this.setState({loading: false});
this.props.storeToken(token);
this.props.isLogin(true);
// this.props.navigation.push('BottomTabNavigator');
} catch (err) {
console.log(err);
alert('Unexpected Error, try again later.');
this.setState({loading: false});
}
};
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator />
) : (
<AppleButton
style={styles.appleButton}
cornerRadius={5}
buttonStyle={AppleButton.Style.WHITE}
buttonType={AppleButton.Type.SIGN_IN}
onPress={() => this.signIn()}
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
appleButton: {
width: 200,
height: 50,
// margin: 10,
},
container: {
flex: 1,
justifyContent: 'center',
},
});
const mapDispatchToProps = dispatch => {
// to excute the actions we want to invok
return {
isLogin: isLogin => {
dispatch(isLoginFunc(isLogin));
},
storeToken: token => {
dispatch(saveToken(token));
},
};
};
export default connect(
null,
mapDispatchToProps,
)(AppleAuth);
-
singin.js
<AppleAuth /> in the render method
if you render your component as component, not as a navigation screen, it will not receive navigation prop. It was like this in all versions of react-navigation
Access the navigation prop from any component