why componentdidmount called two times - javascript

I have React Component in componentDidMount fetch data from the server. The issue is componentDidMount called twice also the API called twice. I have a view increment API like youtube video views increment twice in the database because of twice API calling.
class SingleVideoPlay extends React.Component {
constructor(props) {
super(props);
this.player = React.createRef();
}
state = {
autoPlay: true,
relatedVideos: [],
video: null,
user: null,
comments: [],
commentInput: {
value: '',
touch: false,
error: false
},
following: false,
tab: 'comments'
};
_Mounted = false;
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
componentWillUnmount() {
this._Mounted = false;
try {
clearInterval(this.state.videoInterval);
this.props.videoEditUrl('');
} catch (error) {}
}
captureVideoTime = async () => {
const { video } = this.state;
const result = await updateWatchTime({
id: video._id,
time: 1
});
if (result.status === 200) {
const updateVideo = {
...video,
secondsWatched: video.secondsWatched + 1
};
this.setState({ video: updateVideo });
}
};
videoEnded = () => {
clearInterval(this.state.videoInterval);
};
videoPause = () => {
clearInterval(this.state.videoInterval);
};
loadVideo = () => {
clearInterval(this.state.videoInterval);
};
playingVideo = () => {
const interval = setInterval(this.captureVideoTime, 1000);
this.setState({ videoInterval: interval });
};
getVideoDetails = async (videoId) => {
const video = await getVideo(videoId);
if (video.status === 200) {
let response = video.data;
if (this.props.userId)
if (response.user._id === this.props.userId._id)
this.props.videoEditUrl(`/video/edit/${response.media._id}`);
this.setState({
relatedVideos: response.videos.docs,
video: response.media,
user: response.user
});
this.checkIsFollowing();
this.updateVideoStat(response.media._id);
}
};
updateVideoStat = async (id) => videoView(id);
checkIsFollowing = async () => {
const { userId } = this.props;
const { video } = this.state;
if (userId && video) {
const response = await isFollow({
follower: userId._id,
following: video._id
});
if (response) {
this.setState({ following: response.following });
}
}
};
addOrRemoveFollowing = async () => {
this.checkIsFollowing();
const { following, video } = this.state;
const { userId } = this.props;
if (userId) {
if (following) {
const response = await removeFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: false });
} else {
const response = await addFollow({
follower: userId._id,
following: video._id
});
this.setState({ following: true });
}
}
};
submitCommentHandler = async (event) => {
const { userId } = this.props;
event.preventDefault();
if (userId) {
const result = await saveComment({
mediaId: this.state.video._id,
parentId: '0',
userID: userId._id,
userName: userId.username,
comment: this.state.commentInput.value
});
console.log(result);
if (result.status === 200) {
this.getVideoComments();
this.setState({ commentInput: { value: '', touch: false, error: false } });
}
}
};
render() {
const { autoPlay, relatedVideos, video, user, comments, commentInput, following, tab } = this.state;
const { userId } = this.props;
return (
<div className="container-fluid">
some coponents
</div>
);
}
}
const mapStateToProps = (state) => ({
userId: state.auth.user
});
export default connect(mapStateToProps, { videoEditUrl })(SingleVideoPlay);
I don't know why componentDidMount called two times alse it shows memmory lecage issue.
How to Fix it.

Multiple componentDidMount calls may be caused by using <React.StrictMode> around your component. After removing it double calls are gone.
This is intended behavior to help detect unexpected side effects. You can read more about it in the docs. It happens only in development environment, while in production componentDidMount is called only once even with <React.StrictMode>.
This was tested with React 18.1.0

I think the issue exists on the parent component that used SingleVideoPlay component. Probably that parent component caused SingleVideoPlay component rendered more than once.
Also, there is an issue on your code.
componentDidMount() {
this._Mounted = true;
if (this._Mounted) {
const videoId = this.props.match.params.id;
this.getVideoDetails(videoId);
}
}
Here, no need to check if this._Mounted, because it will always be true.

1.Install jQuery by
npm i jquery
import $ from 'jquery'
create your function or jwuery code after the export command or put at the end of the file

Related

Trying to figure out how to use socket.io the correct way in a useEffect that is using an axios get request to fetch messages

So far i'm stuck on my useEffect that fetches all the current messages and renders the state accordingly. as of right now it doesn't render the new state until page is refreshed.
const Home = ({ user, logout }) => {
const history = useHistory();
const socket = useContext(SocketContext);
const [conversations, setConversations] = useState([]);
const [activeConversation, setActiveConversation] = useState(null);
const classes = useStyles();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const addSearchedUsers = (users) => {
const currentUsers = {};
// make table of current users so we can lookup faster
conversations.forEach((convo) => {
currentUsers[convo.otherUser.id] = true;
});
const newState = [...conversations];
users.forEach((user) => {
// only create a fake convo if we don't already have a convo with this user
if (!currentUsers[user.id]) {
let fakeConvo = { otherUser: user, messages: [] };
newState.push(fakeConvo);
}
});
setConversations(newState);
};
const clearSearchedUsers = () => {
setConversations((prev) => prev.filter((convo) => convo.id));
};
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
const sendMessage = (data, body) => {
socket.emit("new-message", {
message: data.message,
recipientId: body.recipientId,
sender: data.sender,
});
};
const postMessage = async (body) => {
try {
const data = await saveMessage(body);
if (!body.conversationId) {
addNewConvo(body.recipientId, data.message);
} else {
addMessageToConversation(data);
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
const addNewConvo = useCallback(
(recipientId, message) => {
conversations.forEach((convo) => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message);
convo.latestMessageText = message.text;
convo.id = message.conversationId;
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
const addMessageToConversation = useCallback(
(data) => {
// if sender isn't null, that means the message needs to be put in a brand new convo
const { message, sender = null } = data;
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
setConversations((prev) => [newConvo, ...prev]);
}
conversations.forEach((convo) => {
console.log('hi', message.conversationId)
if (convo.id === message.conversationId) {
const convoCopy = { ...convo }
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
console.log('convo', convoCopy)
} else {
return convo
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
const setActiveChat = useCallback((username) => {
setActiveConversation(username);
}, []);
const addOnlineUser = useCallback((id) => {
setConversations((prev) =>
prev.map((convo) => {
if (convo.otherUser.id === id) {
const convoCopy = { ...convo };
convoCopy.otherUser = { ...convoCopy.otherUser, online: true };
return convoCopy;
} else {
return convo;
}
}),
);
}, []);
const removeOfflineUser = useCallback((id) => {
setConversations((prev) =>
prev.map((convo) => {
if (convo.otherUser.id === id) {
const convoCopy = { ...convo };
convoCopy.otherUser = { ...convoCopy.otherUser, online: false };
return convoCopy;
} else {
return convo;
}
}),
);
}, []);
// Lifecycle
useEffect(() => {
// Socket init
socket.on("add-online-user", addOnlineUser);
socket.on("remove-offline-user", removeOfflineUser);
socket.on("new-message", addMessageToConversation);
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
socket.off("add-online-user", addOnlineUser);
socket.off("remove-offline-user", removeOfflineUser);
socket.off("new-message", addMessageToConversation);
};
}, [addMessageToConversation, addOnlineUser, removeOfflineUser, socket]);
useEffect(() => {
// when fetching, prevent redirect
if (user?.isFetching) return;
if (user && user.id) {
setIsLoggedIn(true);
} else {
// If we were previously logged in, redirect to login instead of register
if (isLoggedIn) history.push("/login");
else history.push("/register");
}
}, [user, history, isLoggedIn]);
useEffect(() => {
const fetchConversations = async () => {
try {
const { data } = await axios.get("/api/conversations");
setConversations(data);
} catch (error) {
console.error(error);
}
};
if (!user.isFetching) {
fetchConversations();
}
}, [user]);
const handleLogout = async () => {
if (user && user.id) {
await logout(user.id);
}
};
return (
<>
<Button onClick={handleLogout}>Logout</Button>
<Grid container component="main" className={classes.root}>
<CssBaseline />
<SidebarContainer
conversations={conversations}
user={user}
clearSearchedUsers={clearSearchedUsers}
addSearchedUsers={addSearchedUsers}
setActiveChat={setActiveChat}
/>
<ActiveChat
activeConversation={activeConversation}
conversations={conversations}
user={user}
postMessage={postMessage}
/>
</Grid>
</>
);
};
this is the main part im working on, the project had starter code when i began and was told not to touch the backend so i know its something wrong with the front end code. i feel like im missing something important for the socket.io
import { io } from 'socket.io-client';
import React from 'react';
export const socket = io(window.location.origin);
socket.on('connect', () => {
console.log('connected to server');
});
export const SocketContext = React.createContext();
this is how i have the socket.io setup, if anyone could point me in the right direction that would be cool. I have been reading up on socket.io as much as I can but am still pretty lost on it.
Based on the assumption the backend is working properly...
const addNewConvo = useCallback(
(recipientId, message) => {
conversations.forEach((convo) => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message);
convo.latestMessageText = message.text;
convo.id = message.conversationId;
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
setConversations(conversations);
This is an incorrect way to set a state using the state's variable, and such it wont do anything. Likely why your code wont change until refresh.
Suggested fix:
const addNewConvo = useCallback(
(recipientId, message) => {
setConversations(previousState => previousState.map(convo => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message)
convo.latestMessageText = message.text;
convo.id = message.conversationId;
return convo
}
return convo
}))
},
[setConversations, conversations],
);
note: even above could be done more efficiently since I made a deep copy of messages

Problems saving frontend client value with MERN stack to backend server

I am having a problem with saving a value from the client to backend server, what it's suppose to do is on button click essentially save the previous value and increase it by one
VoteReducer
import {
FETCH_SERVERID,
FETCH_VOTE,
UPDATE_VOTE,
INCREASE_VOTE
} from './constants';
const initialState = {
serverID: '',
voteCount: '',
};
const voteReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_SERVERID:
return {
...state,
serverID: action.payload
};
case FETCH_VOTE:
return {
...state,
fetchVote: action.payload
};
case UPDATE_VOTE:
return {
...state,
voteCount: {
...state.voteCount, ...action.payload,
}
}
case INCREASE_VOTE:
return state + 1
default:
return state;
};
};
export default voteReducer;
Vote Actions
import { push } from 'connected-react-router';
import axios from 'axios';
import { success } from 'react-notification-system-redux';
import {
FETCH_SERVERID,
FETCH_VOTE,
UPDATE_VOTE,
INCREASE_VOTE
} from './constants';
import handleError from "../../utils/error";
export const fetchServerID = value => {
return {
type: FETCH_SERVERID,
payload: value
};
};
export const fetchVote = value => {
return {
type: FETCH_VOTE,
payload: value
};
};
export const increaseVote = () => {
return {
type: INCREASE_VOTE,
};
};
export const voteCount = vote => {
return async (dispatch, getState) => {
try {
const voteValues = {
serverID: "",
voteCount: + 1
}
const voteCount = localStorage.getItem('vote_button')
const newVote = {
votedServer: voteValues.serverID,
voteCount: voteValues.voteCount,
};
const successOptions = {
title: "Your vote has been succesfully recorded",
position: 'tr',
autodismiss: 1
};
const increasingVote = {
title: "We are increasing your vote count test ",
position: 'tr',
autodismis: 1
};
dispatch(success(increaseVote));
const response = await axios.post('/api/vote/add', newVote);
if(response.data.success === true) {
dispatch(success(successOptions));
} else {
const retryOptions = {
title: "Please properly vote on a server",
position: 'tr',
autodismiss: 1
};
dispatch(warning(retryOptions));
}
} catch (error) {
handleError(error, dispatch);
}
}
}
Server side code
const express = require('express');
const router = express.Router();
// Models/helpers
const Vote = require('../../models/vote');
router.post('/add', async (req, res) => {
const serverID = req.body.serverID;
const voteCount = req.body.voteCount;
const vote = new Vote({
serverID,
voteCount,
});
const voteSave = await vote.save();
await vote.save(async (err, data) => {
if (err) {
return res.status(400).json({
error: 'Your request could not be completed at this time'
});
}
res.status(200).json({
success: true,
message: 'We are now counting your vote!',
vote: voteSave
});
});
});
module.exports = router;
Vote Index
class Vote extends React.PureComponent {
render() {
const {
user,
vote,
voteCount,
} = this.props;
return (
<div className='vote-page'>
<button
variant='primary'
text='Vote'
className='vote_button'
onClick={() => { voteCount(vote) }}>
>Vote</button>
</div>
);
}
}
const mapStateToProps = state => {
return {
user: state.account.user,
voteCount: state.voteCount,
};
};
export default connect(mapStateToProps, actions)(Vote);
I literally started learning full stack development this week, uhm just trying to grasp advanced concepts but essentially what I want it to do is keep track of votes then increase it by one when a button is pressed/vote on from the client then passed to the server saved but increased by one every-time a user presses a vote on a specific page but I haven't got to the point of getting that to work nor have I gotten to the point of implementing a way for each type of listing to be different from one another when voted on
Hopefully someone answers soon! Thanks

VueJS/VueX Maximum call stack size exceeded when processing Websocket (Best Practice advice)

Just trying to find the right approach, "good practice" on how to manage a websocket connection with Vue and Vuex. This was my first original approach for the websocket module but as you can see I'm really close to deal with async data inside the SET_REMOTE_DATA mutation, which I recogn is an antipattern.
Here's the first module approach, as you can see the mutation is dealing with two state properties and at the same time with the resolve data coming from the WebSocket promise coming from an action:
export const namespaced = true
export const state = {
connected: false,
error: null,
connectionId: '',
statusCode: '',
incomingChats: [],
remoteMessage: [],
messageType: '',
ws: null,
}
export const actions = {
processWebsocket({ commit }) {
return new Promise((resolve) => {
const v = this
this.ws = new WebSocket('wss://xyz')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
resolve(event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
})
},
}
export const mutations = {
SET_REMOTE_DATA(state, remoteData) {
const wsData = JSON.parse(remoteData.data)
const chatSession = wsData.chat
const wsDataType = wsData.type
if (wsData.connectionId && wsData.connectionId !== state.connectionId) {
state.connectionId = wsData.connectionId
} else {
if (wsDataType==="incoming-chats-updated") {
state.incomingChats = wsData.incomingChats
} else {
console.log(JSON.stringify(chatSession))
}
}
},
SET_CONNECTION(state, message) {
if (message == 'open') {
state.connected = true
} else state.connected = false
},
SET_ERROR(state, error) {
state.error = error
},
}
export const getters = {
getIncomingChats: (state) => {
return state.incomingChats
},
}
And here's my second approach, a more modular one where all the stress now lives on the processWebsocket action. Instead of having one mutation setting the "connectionId" and the "incomingChats" state now I'm trying to create two mutations, one for each property. The problem with this approach is that I'm not sure where to resolve the websocket promise and I'm getting an "Error in Login Maximum call stack size exceeded" JS error in the console.
export const namespaced = true
export const state = {
connected: false,
error: null,
connectionId: '',
statusCode: '',
incomingChats: [],
remoteMessage: [],
messageType: '',
ws: null,
}
export const actions = {
processWebsocket({ commit }) {
return new Promise((resolve) => {
const v = this
this.ws = new WebSocket('wss://xyz')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
const wsData = JSON.parse(event.data)
const chatSession = wsData.chat
const wsDataType = wsData.type
const incomingChats = wsData.incomingChats
const connectionId = wsData.connectionId
if (wsData.connectionId && wsData.connectionId !== state.connectionId) {
commit('SET_CONNECTION_ID', connectionId)
} else{
if (wsDataType==="incoming-chats-updated") {
commit('SET_INCOMING_CHATS', incomingChats)
} else {
console.log(JSON.stringify(chatSession))
}
}
resolve(event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
})
},
}
export const mutations = {
SET_CONNECTION_ID(remoteConnectionId) {
state.connectionId = remoteConnectionId
},
SET_INCOMING_CHATS(state, incomingChats) {
state.incomingChats = incomingChats
},
SET_CONNECTION(state, message) {
if (message == 'open') {
state.connected = true
} else state.connected = false
},
SET_ERROR(state, error) {
state.error = error
},
}
export const getters = {
getIncomingChats: (state) => {
return state.incomingChats
},
}
Any advice will be really appreciate.

How to deal with nested useEffect?

Recently started using Hooks and, as cook as they are, they are giving me a bit of a headache.
I have a custom useFetch() hook that deals with fetching data from the API.
I also have a component where I need to use useFetch a few times and the results must be passed from one to another.
E.g.:
const ComponentName = () => {
const { responseUserInfo } = useFetch('/userinfo')
const { responseOrders } = useFetch(`/orders?id=${responseUserInfo.id}`)
const { isOrderRefundable } = useFetch(`/refundable?id={responseOrders.latest.id}`)
return <div>{isOrderRefundable}</div>
}
So, how do I actually "cascade" the hooks without creating 3 intermediate wrappers? Do I have to use HoC?
Your hook could return a callback, that when called does the API call:
const [getUserInfo, { userInfo }] = useFetch('/userinfo');
const [getOrders, { orders }] = useFetch(`/orders`)
const [getOrderRefundable, { isOrderRefundable }] = useFetch(`/refundable`);
useEffect(getUserInfo, []);
useEffect(() => { if(userInfo) getOrders({ id: userInfo.id }); }, [userInfo]);
useEffect(() => { if(orders) getOrderRefundable({ id: /*..*/ }); }, [orders]);
But if you always depend on the whole data being fetched, I'd just use one effect to load them all:
function useAsync(fn, deps) {
const [state, setState] = useState({ loading: true });
useEffect(() => {
setState({ loading: true });
fn().then(result => { setState({ result }); });
}, deps);
return state;
}
// in the component
const { loading, result: { userInfo, orders, isRefundable } } = useAsync(async function() {
const userInfo = await fetch(/*...*/);
const orders = await fetch(/*...*/);
const isRefundable = await fetch(/*...*/);
return { userInfo, orders, isRefundable };
}, []);

window.location.reload is refreshing page again and again

I am working on project where I am invalidating browser cache but when I call this function window.location.reload() it refresh page again and again . I want to reload page at once . Could someone please help me how to stop page from refreshing again and again . Thanks
Note: I am using React.JS
Code
import React from "react";
import packageJson from "../package.json";
global.appVersion = packageJson.version;
// version from response - first param, local version second param
const semverGreaterThan = (versionA, versionB) => {
const versionsA = versionA.split(/\./g);
const versionsB = versionB.split(/\./g);
while (versionsA.length || versionsB.length) {
const a = Number(versionsA.shift());
const b = Number(versionsB.shift());
// eslint-disable-next-line no-continue
if (a === b) continue;
// eslint-disable-next-line no-restricted-globals
return a > b || isNaN(b);
}
return false;
};
class CacheBuster extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
isLatestVersion: false,
refreshCacheAndReload: (caches) => {
if (caches) {
caches.keys().then(async function (names) {
await Promise.all(names.map((name) => caches.delete(name)));
});
}
window.location.reload();
},
};
}
componentDidMount() {
fetch(`/meta.json?${new Date().getTime()}`, { cache: "no-cache" })
.then((response) => response.json())
.then((meta) => {
const latestVersion = meta.version;
const currentVersion = global.appVersion;
const shouldForceRefresh = semverGreaterThan(
latestVersion,
currentVersion
);
if (shouldForceRefresh) {
console.log(
`We have a new version - ${latestVersion}. Should force refresh`
);
this.setState({ loading: false, isLatestVersion: false });
} else {
console.log(
`You already have the latest version - ${latestVersion}. No cache refresh needed.`
);
this.setState({ loading: false, isLatestVersion: true });
}
});
}
render() {
const { loading, isLatestVersion, refreshCacheAndReload } = this.state;
return this.props.children({
loading,
isLatestVersion,
refreshCacheAndReload,
});
}
}
export default CacheBuster;
if (caches) {...} else (window.location.reload(true));
This code is struck between constructor and the render.
this.state.refreshCacheAndReload should be set to null in constructor, where in you choose to reload it in componentdidmount whenever the version mismatches.

Categories

Resources