i have a next.js app deployed with heroku https://new-cardenal.herokuapp.com/reserve with a contact form that has a submit button that sends an email and it works fine when i run it locally via: npm install, npm run build, npm start but when i try it on the heroku url it sends a 500 error, here is my code
server.js
const express = require('express');
const cors = require('cors')
const next = require('next');
const path = require('path');
const url = require('url');
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
const dev = process.env.NODE_ENV !== 'production';
const port = process.env.PORT || 3000;
const mailer = require('./mailer')
const bodyParser = require('body-parser')
if (!dev && cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.error(`Node cluster worker ${worker.process.pid} exited: code ${code}, signal
${signal}`);
});
} else {
const nextApp = next({ dir: '.', dev });
const nextHandler = nextApp.getRequestHandler();
nextApp.prepare().then(() => {
const server = express();
server.use(cors());
server.use(bodyParser.json())
server.use(bodyParser.urlencoded({extended: true}));
server.post('/api/contact', (req, res) => {
const {senderMail,sendTo,rooms,date} = req.body
mailer({senderMail,sendTo,rooms,date}).then(() => {
console.log('success')
res.send('success')
}).catch((error) => {
console.log('failed', error)
res.send('badddd')
})})
if (!dev) {
server.use(function(req, res, next) {
var proto = req.headers["x-forwarded-proto"];
if (proto === "https") {
res.set({'Strict-Transport-Security': 'max-age=31557600'});
return next();
}
res.redirect("https://" + req.headers.host + req.url);
});
}
server.use('/static', express.static(path.join(__dirname, 'static'), {maxAge: dev ? '0'
: '365d'}));
server.get('*', (req, res) => {
const parsedUrl = url.parse(req.url, true);
nextHandler(req, res, parsedUrl);
});
server.listen(port, (err) => {if (err) throw err;});
});
}
contact.js
import nodemailer from "nodemailer"
const emailPass = "qbmD6Zs8Qv76b96"
const transporter = nodemailer.createTransport({
host: "smtp.gmail.com",
port: 465,
secure: true,
auth: {
user: "eitanschreiber97#gmail.com",
pass: emailPass
},
tls: {
rejectUnauthorized: false
}})
export default async (req, res) => {
const { senderMail, sendTo, rooms, date } = req.body
if (sendTo === "" || rooms === "" || date === "") {
res.status(403).send("")
return
}
const mailerRes = await mailer({ senderMail, sendTo, rooms, date })
res.send(mailerRes)
}
const mailer = ({ senderMail, sendTo, rooms, date }) => {
const message = {
senderMail,
to: `${sendTo}`,
subject: `El Cardenal Hotel`,
text: `Thank you for your reservation
you have reserved rooms: ${rooms} for ${date}
to cancel you can contact El Cardenal Hotel at (+593) 99 642 4583 or email at
elcardenalhotel#gmail.com`,
replyTo: senderMail
}
return new Promise((resolve, reject) => {
transporter.sendMail(message, (error, info) =>
error ? reject(error) : resolve(info)
)})}
reserve.js
import React, { Component } from 'react'
import dynamic from 'next/dynamic';
import Link from 'next/link'
import styled from 'styled-components'
import Header from '../components/header'
import OtherFooter from '../components/otherFooter'
import { sendContactMail } from "../components/networking/mail-api"
import Rooms from '../data/rooms'
import moment from 'moment';
import { GoArrowLeft } from 'react-icons/go';
import { GoArrowRight } from 'react-icons/go';
import { i18n, withTranslation } from '../i18n'
import { createForm, createFactory, createField } from 'micro-form'
import axios from 'axios';
const Wrapper=styled.div``
class DayNames extends Component {
render() {
return (<div className="row day-names">
<span className="day">Sun</span>
<span className="day">Mon</span>
<span className="day">Tue</span>
<span className="day">Wed</span>
<span className="day">Thu</span>
<span className="day">Fri</span>
<span className="day">Sat</span>
</div>);
}}
class Week extends Component {
render() {
let days = [];
let {
date,
} = this.props;
const {
month,
selected,
select,
} = this.props;
for (var i = 0; i < 7; i++) {
let day = {
name: date.format("dd").substring(0, 1),
number: date.date(),
isCurrentMonth: date.month() === month.month(),
isToday: date.isSame(new Date(), "day"),
date: date
};
days.push(<Day day={day}selected={selected}select={select}/>);
date = date.clone();
date.add(1, "day");
}
return <div className="row week" key={days[0]}>{days}</div>;
}}
class Day extends Component {
render() {
const {
day,
day: {
date,
isCurrentMonth,
isToday,
number
},
select,
selected
} = this.props;
return <span key={date.toString()}className={"day" + (isToday ? " today" :
"") + (isCurrentMonth ? "" : " different-month") + (date.isSame(selected) ? "
selected" : "")}onClick={()=>select(day)}>{number}</span>;
}}
const which = [201, 202, 301, 302, 303, 304];
class ReservePage extends Component {
constructor(props) {
super(props);
this.state={rooms:[false,false,false,false,false,false],
em:``,
firstMonth: moment(),secondMonth: moment(),firstSelected:
moment().startOf('day'),firstShow: false,secondSelected:
moment().startOf('day'),secondShow: false,thank_you:false}}
static async getInitialProps(ctx){
return {namespacesRequired: ['common', 'header']}
}
selectRoom = n => {
const y = n
this.setState(prev => {
const rooms = prev.rooms.map((r, ind) => {if (ind == y) {return !r
} else {return r}})
return { rooms }
})}
submitForm = async e => {
e.preventDefault();
const senderMail = `eitanschreiber97#gmail.com`
const res = await sendContactMail(senderMail, this.state.em,
which.filter((w, ind) => this.state.rooms[ind]),
[this.state.firstSelected.format("ll"),this.state.secondSelected.format("ll")])
if (res.status < 300) {
this.setState({rooms: [false,false,false,false,false,false],thank_you:
true,em: ``,firstSelected: moment().startOf('day'),firstShow: false,secondSelected:
moment().startOf('day'),secondShow: false})
}
}
firstPrevious = () => {
const {
firstMonth,
} = this.state;
this.setState({firstMonth:firstMonth.subtract(1,'month')});
}
secondPrevious = () => {
const {
secondMonth,
} = this.state;
this.setState({secondMonth:secondMonth.subtract(1,'month')});
}
firstNext = () => {
const {
firstMonth,
} = this.state;
this.setState({firstMonth:firstMonth.add(1,'month')});
}
secondNext = () => {
const {
secondMonth,
} = this.state;
this.setState({secondMonth:secondMonth.add(1,'month')});
}
firstSelect = day =>
this.setState({firstShow:true,firstSelected:day.date,firstMonth:day.date.clone()});
secondSelect = day =>
this.setState({secondShow:true,secondSelected:day.date,secondMonth:day.date.clone()});
renderFirstWeeks() {
let weeks = [];
let done = false;
let date = this.state.firstMonth.clone().startOf("month").add("w"
-1).day("Sunday");
let count = 0;
let monthIndex = date.month();
const {
firstSelected,
firstMonth,
} = this.state;
while (!done) {
weeks.push(<Week key={date}date={date.clone()}month=.
{firstMonth}select={(day)=>this.firstSelect(day)}selected={firstSelected}/>);
date.add(1, "w");
done = count++ > 2 && monthIndex !== date.month();
monthIndex = date.month();
}
return weeks;
};
renderSecondWeeks() {
let weeks = [];
let done = false;
let date = this.state.secondMonth.clone().startOf("month").add("w"
-1).day("Sunday");
let count = 0;
let monthIndex = date.month();
const {
secondSelected,
secondMonth,
} = this.state;
while (!done) {
weeks.push(<Week key={date}date={date.clone()}month=.
{secondMonth}select={(day)=>this.secondSelect(day)}selected={secondSelected}/>);
date.add(1, "w");
done = count++ > 2 && monthIndex !== date.month();
monthIndex = date.month();
}
return weeks;
};
renderFirstMonthLabel() {
const {
firstMonth,
} = this.state;
return firstMonth.format("MMMM YYYY");
}
renderSecondMonthLabel() {
const {
secondMonth,
} = this.state;
return secondMonth.format("MMMM YYYY");
}
render() {
const { rooms, date, em } = this.state
return (<Wrapper>
<Header/>
<main style={{width:`100%`, margin:0, position:`relative`, top:`7vh`, paddingTop:`90px`,
paddingBottom:`90px`, display:`flex`, flexDirection:`column`, alignItems:`center`,
zIndex:1, backgroundImage:`url(/roomsPage/background_1.png)`,
backgroundPosition:`center`, backgroundSize:`cover`, backgroundRepeat:`no-repeat`}}>
<section>
<h1>Select your room</h1>
<div className="top_list">
{Rooms.map((r,ind)=>{
return <div onClick={()=>this.selectRoom(ind)}>
<h1>{r.number}</h1>
<div style={{width:`240px`,height:`170px`,background:`center / cover no-repeat
url(/roomsPage/${r.number}.jpg)`,backgroundColor:this.state.rooms[ind] ? `grey` :
null,backgroundBlendMode:`multiply`}}></div>
</div>})}
</div>
<h1>Select the date</h1>
<div className="dates">
<div>
<p>Check in</p>
<section>
{this.state.firstShow ? <p>{this.state.firstSelected.format("ll")}</p> : null}
<section className="calendar">
<header className="header">
<div className="month-display row">.
{moment().format("MMMM YYYY") != this.renderFirstMonthLabel() ? <GoArrowLeft onClick=.
{this.firstPrevious}/> : null}<span className="month-label">.
{this.renderFirstMonthLabel()}</span><GoArrowRight onClick={this.firstNext}/></div>
<DayNames />
</header>
{this.renderFirstWeeks()}
</section>
</section>
</div>
<div>
<p>Check out</p>
<section>
{this.state.secondShow ? <p>
{this.state.secondSelected.format("ll")}</p> : null}
<section className="calendar">
<header className="header">
<div className="month-display row">.
{moment().format("MMMM YYYY") != this.renderSecondMonthLabel() ? <GoArrowLeft
onClick={this.SecondPrevious}/> : null}<span className="month-label">
{this.renderSecondMonthLabel()}</span><GoArrowRight onClick={this.secondNext}/>
</div>
<DayNames />
</header>
{this.renderSecondWeeks()}
</section>
</section>
</div>
</div>
<div className="bottom">
<div>
<p>Email</p>
<input type="text"id="em"name="em_name"value={em}onChange=
{e=>this.setState({em:e.target.value})}/>
</div>
<button type="submit" onClick={this.submitForm}>Reserve</button>
{this.state.thank_you && <p>thank you</p>}
</div>
</section>
</main>
<OtherFooter/>
</Wrapper>)}}
export default withTranslation('common')(ReservePage)
mail-api.js
import axios from "axios"
export const sendContactMail = async (senderMail, sendTo, rooms, date) => {
const data = {senderMail, sendTo, rooms, date}
try {
const res = await axios({
method: "post",
url: "/api/contact",
headers: {
"Content-Type": "application/json"
},
data
})
return res
} catch (error) {
return error
}
}
what do i need to fix
UPDATE: apparently i need to add ajax code
$.ajax({
url: 'https://new-cardenal.herokuapp.com/',
type: 'POST',
headers: {'Accept': 'application/json;'},
data: {
"subject": "the subject",
"message": "the body"
},
}).done(function (res) {
console.log(res); // it shows your email sent message.
});
i'm just not sure which file to put it code in or do i create a new file
never mind i figured it out i singed in with google accounts and kept the window open then ran: npm install, npm run build and npm start and it worked
Related
I am making a mern ecommerce website i just want to see how useEffect works so i console.log in some part of useEffect and loadFilteredResults i saw that --->
initial
entry
skip
entry1
screen shot
but i think it shoud be-->
initial
entry
entry1
skip
why console give this?? i am a begginer, i am a self learner , so please if you need any extra info please comment.
code snippet-->
const loadFilteredResults = (newFilters) => {
console.log("entry")
getFilteredProducts(skip, limit, newFilters).then((data) => {
console.log("entry1")
if (data.error) {
setError(data.error);
} else {
//console.log(data);
setFilteredResults(data.data);
//console.log("size-->");
//console.log(data.size);
setSize(data.size);
setSkip(0);
}
});
};
....
....
useEffect(() => {
init();
console.log("initial");
loadFilteredResults(skip, limit, myFilters.filters);
console.log("skip");
}, []);
//full code of shop.js
import React, { useEffect, useState } from "react";
import Layout from "./Layout";
import Card from "./Card";
import { getCategories, getFilteredProducts } from "./apiCore";
import Checkbox from "./Checkbox";
import RadioBox from "./RadioBox";
import { prices } from "./fixedPrices";
const Shop = () => {
const [myFilters, setMyFilters] = useState({
filters: { category: [], price: [] }
});
const [categories, setCategories] = useState([]);
const [error, setError] = useState(false);
const [limit, setLimit] = useState(3);//prpduct lesss so use 3 but sir used 6
const [skip, setSkip] = useState(0);
const [size, setSize] = useState(0);
const [filteredResults, setFilteredResults] = useState([]);
const init = () => {
getCategories().then((data) => {
if (data.error) {
//console.log("error");
setError(data.error);
} else {
//console.log("set");
//console.log(data);
setCategories(data);
//console.log(data);
}
});
};
const loadFilteredResults = (newFilters) => {
//console.log(newFilters);
console.log("entry")
getFilteredProducts(skip, limit, newFilters).then((data) => {
console.log("entry1")
if (data.error) {
setError(data.error);
} else {
//console.log(data);
setFilteredResults(data.data);
//console.log("size-->");
//console.log(data.size);
setSize(data.size);
setSkip(0);
}
});
};
const loadMore = () => {
console.log("skip"+skip);
console.log("limit"+limit);
let toSkip = skip + limit;
console.log("toSkip"+toSkip);
getFilteredProducts(toSkip, limit, myFilters.filters).then((data) => {
//console.log("filter");
//console.log( myFilters.filters)
if (data.error) {
setError(data.error);
} else {
//console.log(filteredResults);
//console.log(data.data);
setFilteredResults([...filteredResults, ...data.data]);
//console.log("after");
//console.log(...filteredResults);
//console.log(filteredResults);
//console.log(filteredResults);
//console.log([...filteredResults])
//console.log([...filteredResults, ...data.data])
setSize(data.size);
setSkip(toSkip);
}
});
};
const loadMoreButton = () => {
return (
size > 0 &&
size >= limit && (
<button onClick={loadMore} className="btn btn-warning mb-5">
load more
</button>
)
);
};
useEffect(() => {
init();
//console.log(skip);
console.log("initial");
loadFilteredResults(skip, limit, myFilters.filters);
console.log("skip");
}, []);
const handleFilters = (filters, filterBy) => {
//console.log("SHOP", filters, filterBy);
const newFilters = { ...myFilters };
//console.log(newFilters);
newFilters.filters[filterBy] = filters;
//console.log(typeof(filters));
if (filterBy === "price") {
let priceValues = handlePrice(filters);
newFilters.filters[filterBy] = priceValues;
//console.log(priceValues);
}
//console.log(myFilters.filters);
loadFilteredResults(myFilters.filters);
setMyFilters(newFilters);
};
const handlePrice = (value) => {
const data = prices;
let array = [];
//console.log(value);
for (let key in data) {
if (data[key]._id === parseInt(value)) {
array = data[key].array;
}
}
return array;
};
// const x = (filters)=>{
// console.log("filters:"+filters);
// handleFilters(filters, "category")
// }
return (
<Layout
title="Shop Page"
description="search and buy books of your choice"
className="container-fluid"
>
<div className="row">
<div className="col-4">
<h4>Filter by categories</h4>
<ul>
{/* below will be show in list show we wrap it in unorder list */}
<Checkbox
categories={categories}
handleFilters={(filters) =>
handleFilters(filters, "category")
}
/>
</ul>
<h4>Filter by price range</h4>
<div>
<RadioBox
prices={prices}
handleFilters={(filters) => handleFilters(filters, "price")}
/>
</div>
</div>
<div className="col-8">
<h2 className="mb-4">Products</h2>
<div className="row">
{filteredResults.map((product, i) => (
<Card key={i} product={product} />
))}
</div>
<hr />
{loadMoreButton()}
</div>
</div>
</Layout>
);
};
export default Shop;
getFilteredProducts must be a Promise. Please read Using promises
Callbacks added with then() will never be invoked before the
completion of the current run of the JavaScript event loop.
I am new to using Web Sockets, and am trying to use the Sockets.io library to finish a chat application I am building. When a message is sent, the recipient receives the message twice, which is obviously not how the app is supposed to work.
I built a Socket context that joins the user into a room that is represented by their unique MongoDB identifier so they can be reached regardless of whichever chat they may be currently viewing. I tried to build it so that each chat the user views enters them into a new room while simultaneously causing them to leave the room for the chat they were viewing previously.
This is my code for the socket-related portion of my server:
io.on('connection', socket => {
socket.on('setup', userData => {
socket.join(userData._id);
});
socket.on('join chat', chatId => {
socket.join(chatId);
});
socket.on('new message', message => {
let chat = message.chat;
chat.users.forEach(user => {
if (user._id === message.sender._id) return;
socket.to(user._id).emit('message received', message);
});
});
socket.on('leave chat', chatId => {
socket.leave(chatId);
});
Here is the relevant code for my socket context (if a new user signs in then it should close the old socket and create a new room representing the new user):
useEffect(() => {
if (!currentUser) return;
const newSocket = io(ENDPOINT);
newSocket.emit('setup', currentUser);
setSocket(newSocket);
return () => newSocket.close();
}, [currentUser]);
And, finally, here is the code within the chat instance component:
useEffect(() => {
if (!socket) return;
socket.emit('join chat', activeChat[0]._id);
return () => socket.emit('leave chat', activeChat[0]._id);
}, [activeChat, socket]);
useEffect(() => {
if (!socket) return;
socket.on('message received', message => {
if (!activeChat[0]._id || message.chat._id !== activeChat[0]._id) {
if (!notifications) return;
setNotifications(prevState => [message, ...prevState]);
return;
} else {
setMessages(prevState => {
return [...prevState, message];
});
}
});
}, [activeChat, fetchChats, notifications, socket, setNotifications]);
Just as a side note, I had the application working previously when I kept the socket instance inside of the chat instance (and did not try importing it from the socket hook), but it inhibited my ability for the user to be contacted while viewing another chat since removed the socket instance when the chat instance unmounted by calling return () => socket.close(). Here is that code:
let socket; // Global scope
useEffect(() => {
socket = io(ENDPOINT);
socket.emit('setup', currentUser);
socket.emit('stop typing', activeChat[0]._id, currentUser);
return () => socket.close();
}, [currentUser, activeChat]);
If there is anything I can clarify, please let me know! Thanks so much for the help :)
EDIT: So I fixed my problem and it had to do with how I was handling the event listeners on the client side, which I was never unmounting. For anyone in the future who faces the same problem, please see the code below that I used to handle user messaging, typing, and handling changes to which users are online. Namaste.
Server.js (relevant portion):
global.onlineUsers = new Map();
io.on('connection', socket => {
socket.on('setup', userId => {
socket.join(userId);
global.onlineUsers.set(userId, socket.id);
for (const [
onlineUserId,
_onlineSocketId,
] of global.onlineUsers.entries()) {
if (onlineUserId === userId) {
socket.emit('logged in user change', [...global.onlineUsers]);
return;
} else {
socket
.to(onlineUserId)
.emit('logged in user change', [...global.onlineUsers]);
}
}
});
socket.on('join room', chatId => {
socket.join(chatId);
});
socket.on('leave room', chatId => {
socket.leave(chatId);
});
socket.on('send-msg', message => {
message.chat.users.forEach(user => {
if (user._id === message.sender._id) return;
socket.to(user._id).emit('msg-received', message);
});
});
socket.on('typing', (room, user) => {
socket.to(room).emit('typing', user.userName);
});
socket.on('stop typing', (room, user) =>
socket.to(room).emit('stop typing', user.userName)
);
socket.on('log out', userId => {
socket.leave(userId);
global.onlineUsers.delete(userId);
for (const [
onlineUserId,
_onlineSocketId,
] of global.onlineUsers.entries()) {
socket
.to(onlineUserId)
.emit('logged in user change', [...global.onlineUsers]);
}
});
});
Socket Context (relevant portion):
useEffect(() => {
if (!currentUser) return;
const newSocket = io(ENDPOINT);
newSocket.emit('setup', currentUser._id);
newSocket.on('logged in user change', users => {
const userIdArr = users.map(([userId, socketId]) => userId);
setOnlineUsers(userIdArr);
});
setSocket(newSocket);
return () => {
newSocket.off('logged in user change');
newSocket.emit('log out', currentUser._id);
};
}, [currentUser]);
Chat Instance (entire component):
import { useCallback, useEffect, useState } from 'react';
import { io } from 'socket.io-client';
import Lottie from 'lottie-react';
import { useChatView } from '../../contexts/chat-view-context';
import Spinner from '../spinner/spinner.component';
import './message-view.styles.scss';
import { useAuthentication } from '../../contexts/authentication-context';
import animationData from '../../animations/typing.json';
import {
defaultToast,
sameSenderAndNotCurrentUser,
TOAST_TYPE,
userSent,
getTyperString,
} from '../../utils/utils';
import { useSocket } from '../../contexts/socket-context';
// Could definitely add timestamp data to the message as well, that would be pretty clean actually
let typingTimer;
const MessageView = () => {
// Somehow we are going to have to get all of the message in a conversation potentially and then mark whether or not they are your messages or someone else's to style accordingly;
const { currentUser } = useAuthentication();
const { activeChat, notifications, setNotifications, fetchChats } =
useChatView();
const { socket } = useSocket();
// const [socketConnected, setSocketConnected] = useState(false);
const [messages, setMessages] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isTyping, setIsTyping] = useState(false);
const [typing, setTyping] = useState(false);
const [typers, setTypers] = useState([]);
// console.log('typers from outside', typers);
// So I am thinking that I can definitely scroll into view whatever message is actually clicked within whatever chat, I don't see why that would not be possible?
// Pretty cool, when the component actually mounts, the ref for the element gets passed into the callback function, could actually do some pretyy coll things with this, like making an animation or shake the screen or bounce the message or anything when the message actually enters the screen...
const handleKeyDown = async e => {
if (!socket) return;
const newMessage = e.target.innerHTML;
if (e.key === 'Enter' && newMessage) {
e.preventDefault();
e.target.innerHTML = '';
try {
const response = await fetch(`http://localhost:4000/api/message`, {
method: 'post',
headers: {
Authorization: `Bearer ${currentUser.token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
chatId: activeChat[0]._id,
text: newMessage,
}),
});
const message = await response.json();
socket.emit('send-msg', message);
setMessages(prevState => [...prevState, message]);
setTyping(false);
return;
} catch (error) {
defaultToast(TOAST_TYPE.error, 'Error sending');
}
} else {
if (!typing) {
setTyping(true);
socket.emit('typing', activeChat[0]._id, currentUser);
}
const lastTypingTime = new Date().getTime();
const timerLength = 3000;
if (typingTimer) clearTimeout(typingTimer);
typingTimer = setTimeout(() => {
const timeNow = new Date().getTime();
const timeDiff = timeNow - lastTypingTime;
if (timeDiff >= timerLength) {
socket.emit('stop typing', activeChat[0]._id, currentUser);
setTyping(false);
}
}, timerLength);
}
};
const fetchMessages = useCallback(async () => {
if (!socket) return;
if (!activeChat) return;
setIsLoading(true);
const response = await fetch(
`http://localhost:4000/api/message/${activeChat[0]._id}`,
{
method: 'get',
headers: { Authorization: `Bearer ${currentUser.token}` },
}
);
const messages = await response.json();
setMessages(messages);
setIsLoading(false);
}, [activeChat, currentUser.token, socket]);
useEffect(() => {
fetchMessages();
}, [fetchMessages, activeChat]);
useEffect(() => {
if (!socket) return;
socket.emit('join room', activeChat[0]._id);
socket.emit('stop typing', activeChat[0]._id, currentUser);
return () => socket.emit('leave room', activeChat[0]._id);
}, [activeChat, socket, currentUser]);
useEffect(() => {
if (!socket) return;
socket.on('msg-received', message => {
if (!activeChat[0]._id || message.chat._id !== activeChat[0]._id) {
setNotifications(prevState => [message, ...prevState]);
return;
} else {
setIsTyping(false);
setMessages(prevState => [...prevState, message]);
}
});
return () => socket.off('msg-received');
}, [socket, activeChat, setNotifications]);
useEffect(() => {
if (!socket) return;
socket.on('typing', typer => {
setIsTyping(true);
setTypers(prevState => [...new Set([typer, ...prevState])]);
});
socket.on('stop typing', userName => {
const usersStillTyping = typers.filter(typer => typer !== userName);
if (usersStillTyping.length > 0 && typers.length !== 0) {
setIsTyping(true);
setTypers(usersStillTyping);
return;
}
setIsTyping(false);
setTypers([]);
});
return () => {
socket.off('typing');
socket.off('stop typing');
};
}, [socket, typers]);
const setRef = useCallback(
node => {
if (node && isTyping && isScrolledIntoView(node)) {
node.scrollIntoView({ smooth: true });
} else if (node && !isTyping) {
node.scrollIntoView({ smooth: true });
}
},
[isTyping]
);
function isScrolledIntoView(el) {
var rect = el.getBoundingClientRect();
var elemTop = rect.top;
var elemBottom = rect.bottom;
// Only completely visible elements return true:
var isVisible = elemTop >= 0 && elemBottom <= window.innerHeight;
// Partially visible elements return true:
//isVisible = elemTop < window.innerHeight && elemBottom >= 0;
return isVisible;
}
// What is the best way to make it so that the text bubble can expland if it needs to??
return (
<div className="message-view-container">
{isLoading ? (
<Spinner type="search" />
) : (
<>
<div className="message-view-active-chat-container">
{messages.length > 0 &&
messages.map((message, i) => {
const lastMessageBool = messages.length - 1 === i + 1;
const userSentBool = userSent(currentUser, message);
const sameSenderAndNotCurrentUserBool =
sameSenderAndNotCurrentUser(i, messages, currentUser);
return (
<div
key={i}
ref={lastMessageBool ? setRef : null}
className={`message-view-message-container ${
userSentBool ? 'user-sent' : ''
}`}
>
<div
className="message-view-message-image-container"
style={
sameSenderAndNotCurrentUserBool || userSentBool
? { visibility: 'hidden' }
: { marginTop: '2px' }
}
>
<img
height="100%"
src={message.sender.picture}
alt="profile"
/>
</div>
<div className="message-view-text-container">
<div className="message-view-text">{message.text}</div>
<div
style={
sameSenderAndNotCurrentUserBool || userSentBool
? { display: 'none' }
: {}
}
className="message-view-text-info"
>
<p>
#{!userSentBool ? message.sender.userName : 'You'}
</p>
</div>
</div>
</div>
);
})}
</div>
{isTyping && (
<div className="lottie-container">
{typers.length ? getTyperString(typers) : ''}
<Lottie
animationData={animationData}
loop={true}
autoplay={true}
style={{ height: '16px', display: 'block' }}
/>
</div>
)}
<div
className="send-message-editable"
data-text={`Message `}
contentEditable
onKeyDown={handleKeyDown}
/>
</>
)}
</div>
);
};
export default MessageView;
I'm trying to make an autocomplete search for Alpha Vantage API, but the array which should contain the matches (suggestion) always returns empty when I type in the input field and I don't know what could be the reason, I'm stuck for a while on this and would appreciate if someone could help me with this.
The related code here is mostly in useEffect and the inputHandler:
const Search = () => {
useEffect(() => {
const getSymbols = async () => {
const searchURL = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
const res = await axios.get(searchURL);
if(res) {
setSecurity(res.data.bestMatches);
if(security !== undefined && security.length > 0) {
let symbols = security.map(sec => sec['1. symbol'])
setAllSymbol(symbols);
}
}
}
getSymbols();
}, [])
console.log(allSymbol);
const inputHandler = (e) => {
setTextInput(e.target.value);
let matches = [];
if(textInput.length > 0) {
matches = allSymbol.filter(sym => {
const regex = new RegExp(`${textInput}`, "gi");
return sym.match(regex);
})
setSuggestion(matches);
}
console.log(suggestion);
setTextInput(e.target.value);
}
const showData = async (e) => {
e.preventDefault();
const searchURL = `https://www.alphavantage.co/query?function=SYMBOL_SEARCH&keywords=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
const monthlyURL = `https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=${textInput}&apikey=${process.env.REACT_APP_ALPHA_VANTAGE_API_KEY}`
try {
const res = await axios.get(searchURL);
const data = await axios.get(monthlyURL);
if(res) {
setTickers(res.data.bestMatches[0]);
setSymbol(res.data.bestMatches[0]['1. symbol']);
setSecurity(res.data.bestMatches);
if(data) {
const monthlyTimeSeries = Object.values(data.data['Monthly Time Series']);
const result = [monthlyTimeSeries[1]];
const resultValues = Object.keys(result[0]).map(key => {
return Math.floor(result[0][key]);
})
setPrices(resultValues);
}
}
} catch(err) {
console.log(err)
}
setDailyPrices([]);
setWeeklyPrices([]);
setIntraPrices([]);
}
return (
<StyledSearch>
<div className="wrapper">
<h1>Security Price Monitor App </h1>
<form onSubmit={showData} className="search-form">
<input type="text" value={textInput} onChange={inputHandler} placeholder='Enter Stock Symbol (GOOG, MSFT)'/>
<button type="submit">Search</button>
</form>
</div>
{prices.length > 0 && (
<>
<Table prices={prices} symbol={symbol}/>
<TimeFrames symbol={symbol} textInput={textInput} weeklyPrices={weeklyPrices} setWeeklyPrices={setWeeklyPrices} dailyPrices={dailyPrices} setDailyPrices={setDailyPrices} intraPrices={intraPrices} setIntraPrices={setIntraPrices} />
</>
)}
</StyledSearch>
)
}
I'm implementing an infinite scroll with Apollo and React. Everything works fine. When I navigate away from Feed and then back to Feed I'm getting this weird error:
TypeError: Cannot read property 'fetchMore' of undefined
Has anyone else had experience with this issue? I found this but there doesn't seem to yet be any solutions. One of the answers mentions a partial solution "checking the route before executing fetchMore" but I don't know what means.
I'm still in the middle of development so I haven't had a chance to clean this component up yet, but here it is:
import React, { useEffect, useRef } from 'react';
import { useQuery } from '#apollo/client';
import PostUpdateOrShow from '../posts/types/showOrUpdate/PostUpdateOrShow.js'
import Cookies from 'js-cookie';
import Queries from '../../graphql/queries';
import InfiniteScroll from './util/Infinite_Scroll.js';
const { FETCH_USER_FEED, FETCH_TAG_FEED } = Queries;
const Feed = ({
user, tag
}) => {
let fetchMoreDiv = useRef(null);
let cursorId = useRef(null);
useEffect(() => {
var scroll = document.addEventListener('scroll', function(event) {
fetchMoreDiv.current = document.querySelector('#fetchMore')
var el = fetchMoreDiv.current.getBoundingClientRect()
var elTop = el.top
var elBottom = el.bottom
var innerHeight = window.innerHeight
if (elTop >= 0 && elBottom <= innerHeight) {
fetchMore({
query: gqlQuery,
variables: {
query: query,
cursorId: cursorId.current
},
})
}
})
return () => {
document.removeEventListener('scroll', scroll)
}
})
var gqlQuery
var query
if (user) {
gqlQuery = FETCH_USER_FEED
query = user.blogName
} else if (tag) {
gqlQuery = FETCH_TAG_FEED
query = tag.title.slice(1)
} else {
gqlQuery = FETCH_USER_FEED
query = Cookies.get('currentUser')
}
let { loading, error, data, fetchMore } = useQuery(gqlQuery, {
variables: {
query: query,
cursorId: null
},
})
if (loading) return 'Loading...';
if (error) return `Error: ${error}`;
const { fetchUserFeed, fetchTagFeed } = data
cursorId.current = fetchUserFeed ? fetchUserFeed[fetchUserFeed.length - 1]._id :
fetchTagFeed[fetchTagFeed.length - 1]._id
if (tag) {
return(
<div>
<div>
{fetchTagFeed.map((post, i) => {
return (
<div
className='post'
key={post._id}
>
<PostUpdateOrShow
post={post}
/>
</div>
)
})}
</div>
<InfiniteScroll
fetchMoreDiv={fetchMoreDiv}
/>
<div
id='fetchMore'
>
</div>
</div>
)
} else {
return(
<div>
<div>
{fetchUserFeed.map((post, i) => {
return (
<div
className='post'
key={post._id}
>
<PostUpdateOrShow
post={post}
/>
</div>
)
})}
</div>
<InfiniteScroll
fetchMoreDiv={fetchMoreDiv}
/>
<div
id='fetchMore'
>
</div>
</div>
)
}
}
export default Feed;
Apollo client config:
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
fetchLikesRepostsAndComments: {
merge: (existing = [], incoming = []) => {
return incoming
}
},
fetchUserFeed: {
keyArgs: ['query'],
merge: (existing = [], incoming = []) => {
const elements = [...existing, ...incoming].reduce((array, current) => {
return array.map(i => i.__ref).includes(current.__ref) ? array : [...array, current];
}, []);
return elements
},
}
}
}
}
}),
errorLink
})
I believe the issue is that you are using fetchMore within useEffect hook.
Try to rethink your code to avoid this. Using fetchMore outside the hook would work flawlessly.
I updated my React application from 16.3+ to React 17 while upgrading to crate-react-app#4.0.2. Everything works as expected, but I see the following in the console:
Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See react-unsafe-component-lifecycles for details.
* Move code with side effects to componentDidMount, and set initial state in the constructor.
Please update the following components: SideEffect(NullComponent)
My App.jsx file:
import React, { useRef, useEffect, useCallback, createRef } from 'react';
import { useDispatch, useSelector, batch } from 'react-redux';
import './App.scss';
import { CountryBox, Error, MasterBox, MetaTags, ModalContainer, ScreenLoader } from '../../components';
import { dataActions, settingsActions, statisticsActions, statisticsUpdatesActions } from '../../store/actions/actions';
import { engineService } from '../../services';
import { coreUtils } from '../../utils';
const App = (props) => {
const dispatch = useDispatch();
// Refs.
const elRefs = useRef([]);
// State variables.
const settingsList = useSelector((state) => state.settings.settingsList);
const loadingList = useSelector((state) => state.settings.loadingList);
const sourcesList = useSelector((state) => state.data.sourcesList);
const countriesList = useSelector((state) => state.data.countriesList);
const { isActive, isRefreshMode, viewType, isDisplayError, activeModalName,
activeModalValue, isReplaceModalMode, isActionLoader } = settingsList;
const { loadingPrecentage, isScreenLoaderComplete } = loadingList;
// Functions to update the state.
const onSetStateCurrentTime = (data) => dispatch(statisticsActions.setStateCurrentTime(data));
const onSetStateSettingsList = (listName, listValues) => dispatch(settingsActions.setStateSettingsList(listName, listValues));
const onSetStateStatisticsField = (fieldName, fieldValue) => dispatch(statisticsActions.setStateStatisticsField(fieldName, fieldValue));
const onSetStateStatisticsList = (statisticsList) => dispatch(statisticsActions.setStateStatisticsList(statisticsList));
const onSetStateStatisticsUpdatesSettingsList = (statisticsUpdatesSettingsList) => dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
const onSetStateDataCollection = (collectionName, collectionValue) => dispatch(dataActions.setStateDataCollection(collectionName, collectionValue));
const onSetStateInitiateSettings = (data) => {
const { settingsList, loadingList } = data;
batch(() => {
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(settingsActions.setStateSettingsList('loadingList', loadingList));
});
};
const onSetStateInitiateSources = (data) => {
const { sourcesList, countriesList, countriesNameIdList, statisticsList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('sourcesList', sourcesList));
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
});
};
const onSetStateUpdateRound = (data) => {
const { countriesList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateActionUpdate = (data) => {
const { countriesList, settingsList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
});
};
const onSetStateActionRefresh = (data) => {
const { countriesList, settingsList, statisticsList, updateStatisticsUpdatesListResults } = data;
const { statisticsUpdatesList, statisticsUpdatesSettingsList } = updateStatisticsUpdatesListResults;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(settingsActions.setStateSettingsList('settingsList', settingsList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesSettingsList(statisticsUpdatesSettingsList));
}
});
};
const onSetStateUpdateCountryVisibility = (data) => {
const { countriesList, countriesNameIdList, statisticsList, statisticsUpdatesList } = data;
batch(() => {
dispatch(dataActions.setStateDataCollection('countriesList', countriesList));
dispatch(dataActions.setStateDataCollection('countriesNameIdList', countriesNameIdList));
dispatch(statisticsActions.setStateStatisticsList(statisticsList));
if (statisticsUpdatesList && statisticsUpdatesList.length > 0) {
dispatch(statisticsUpdatesActions.setStateStatisticsUpdatesList(statisticsUpdatesList));
}
});
};
// Run the engine.
useEffect(() => {
engineService.runEngine({
mode: props.mode,
onSetStateCurrentTime: onSetStateCurrentTime,
onSetStateSettingsList: onSetStateSettingsList,
onSetStateStatisticsField: onSetStateStatisticsField,
onSetStateStatisticsList: onSetStateStatisticsList,
onSetStateStatisticsUpdatesSettingsList: onSetStateStatisticsUpdatesSettingsList,
onSetStateInitiateSettings: onSetStateInitiateSettings,
onSetStateInitiateSources: onSetStateInitiateSources,
onSetStateUpdateRound: onSetStateUpdateRound,
onSetStateDataCollection: onSetStateDataCollection,
onSetStateActionUpdate: onSetStateActionUpdate,
onSetStateActionRefresh: onSetStateActionRefresh,
onSetStateUpdateCountryVisibility: onSetStateUpdateCountryVisibility
});
return () => {
engineService.clearSources();
};
}, []);
// Set loader for each master action.
useEffect(() => {
engineService.updateActionLoader(false);
}, [countriesList]);
// After exit from any modal - Scroll back to the element's vertical position.
const scrollToCountry = useCallback((data) => {
const { action, value } = data;
if (action === 'modal' && !value && activeModalValue && !isReplaceModalMode && activeModalName !== 'country') {
setTimeout(() => {
const offsetTop = elRefs.current.find(c => c.current?.dataset?.countryId === activeModalValue).current.offsetTop;
if (offsetTop > window.innerHeight) {
window.scrollTo(0, offsetTop);
}
}, 10);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on master modal click.
const handleActionClick = useCallback((e) => {
if (!isActionLoader) {
const data = {
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getAttributeName(e, 'name'),
id: coreUtils.getAttributeName(e, 'data-country-id')
};
scrollToCountry(data);
engineService.runMasterActionClick(data);
}
}, [elRefs, activeModalValue, isReplaceModalMode]);
// Update action on relevant modal change.
const handleModalActionChange = useCallback((e) => {
engineService.runModalActionUpdate({
modalName: coreUtils.getAttributeName(e, 'data-modal-name'),
action: coreUtils.getAttributeName(e, 'data-action'),
value: coreUtils.getValue(e)
});
}, []);
// Validate all OK to show the data and generate the countries.
const isInitiateComplete = !isDisplayError && countriesList && countriesList.length > 0 && loadingPrecentage === 100;
const renderCountries = useCallback(() => {
const countriesDOM = [];
const refsList = [];
for (let i = 0; i < countriesList.length; i++) {
const country = countriesList[i];
const ref = elRefs.current[i] || createRef();
refsList.push(ref);
countriesDOM.push(
(<CountryBox
key={country.id}
{...country} // React memo works only with separated properties.
isRefreshMode={isRefreshMode}
sourcesList={sourcesList}
onActionClick={handleActionClick}
ref={ref}
/>));
}
elRefs.current = refsList;
return countriesDOM;
}, [countriesList]);
return (
<div className="main">
{MetaTags}
{!isScreenLoaderComplete &&
<ScreenLoader
isActive={isActive}
loadingList={loadingList}
isDisplayError={isDisplayError}
/>
}
{isDisplayError &&
<Error
isDisplayError={isDisplayError}
/>
}
{activeModalName &&
<ModalContainer
onActionClick={handleActionClick}
onActionChange={handleModalActionChange}
/>
}
{isInitiateComplete &&
<div className="page">
<div className="main-container">
<div className={`container ${viewType} f32 f32-extra locations`}>
<MasterBox
onActionClick={handleActionClick}
/>
{renderCountries()}
</div>
</div>
</div>
}
</div>
);
};
export default App;
How can I fix this problem?
OK, I solved it.
The issue was with one of the components named MetaTags:
MetaTags.jsx
import React from 'react';
import { Helmet } from 'react-helmet';
import { timeUtils } from '../../../utils';
const MetaTags =
(<Helmet>
<title data-rh="true">World Covid 19 Data | Covid 19 World Data | {timeUtils.getTitleDate()}</title>
</Helmet>);
export default MetaTags;
The react-helmet package is outdated, and I needed to install 'react-helmet-async' instead, and change the code to:
initiate.jsx
app = (
<HelmetProvider>
<Suspense fallback={null}>
<Provider store={createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)))}>
<Helmet>
<title data-rh="true">Dynamic title {timeUtils.getTitleDate()}</title>
</Helmet>
<BrowserRouter>
{component}
</BrowserRouter>
</Provider>
</Suspense>
</HelmetProvider>
);
This solved my issue and the warning was gone.