this.setState is not a function - Firebase get document error - javascript

I'm newbie at React.
My project simply allows more than one person to listen to music simultaneously. I managed to make an api connection over Spotify, but I keep the information of the room on the firebase. Therefore, I need to get the room's founder, that is, roomAdminMail information, from firebase. When I try to retrieve room information like I did on Homepage, I get an error like this.
import React, {Component} from "react";
import {
Grid,
Typography,
Card,
IconButton,
LinearProgress,
} from "#material-ui/core";
import Spotify from 'spotify-web-api-js';
import PlayArrowIcon from "#material-ui/icons/PlayArrow";
import PauseIcon from "#material-ui/icons/Pause";
import SkipNextIcon from "#material-ui/icons/SkipNext";
import SkipPreviousIcon from '#material-ui/icons/SkipPrevious';
import firebase from 'firebase';
import {GridItem} from '#chakra-ui/react';
const spotifyWebApi = new Spotify();
//const Player = props =>
class Player extends Component {
constructor(){
super();
const params = this.getHashParams();
this.state = {
logeedIn : params.access_token ? true : false,
currentStatus: false,
roomAdminMail: "",
roomName: "",
roomInfo: "",
nowPlaying: {
artist_name : 'Not Checked',
song_name: 'Not Checked',
image: ''
}
}
if(params.access_token){
spotifyWebApi.setAccessToken(params.access_token)
}
}
getHashParams() {
var hashParams = {};
var e, r = /([^&;=]+)=?([^&;]*)/g,
q = window.location.hash.substring(1);
while ( e = r.exec(q)) {
hashParams[e[1]] = decodeURIComponent(e[2]);
}
return hashParams;
}
getNowPlaying(){
spotifyWebApi.getMyCurrentPlayingTrack()
.then((response) => {
this.setState({
nowPlaying: {
artist_name: response.item.artists[0].name,
song_name: response.item.name,
image: response.item.album.images[0].url
}
})
})
}
getRoomCollection(){
const db = firebase.firestore();
db.collection("rooms").onSnapshot(function(querySnapshot) {
this.setState(querySnapshot.docs.map((doc) => ({
roomAdminMail: doc.data().roomAdminMail,
roomName: doc.data().roomName,
roomInfo: doc.data().roomInfo
})));
})
}
componentDidMount(){
this.getNowPlaying();
this.getRoomCollection();
}
render() {
return (
<GridItem
colStart={[1, null, null, 2, null, null]}
colSpan={[3, null, null, 1, null, null]}
p={6}
>
<a href='http://localhost:8888'>
<button>Login With Spotify</button>
</a>
<Typography component="h5" variant="h5">
Room Admin: {this.state.roomAdminMail}
</Typography>
<Card item align="center">
<Grid container alignItems="center">
<Grid item align="center" xs={12} className="now-playing__img">
<img src={this.state.nowPlaying.image} />
</Grid>
<Grid item align="center" xs={8}>
<Typography item align="center" component="h5" variant="h5">
{this.state.nowPlaying.song_name}
</Typography>
<Typography item align="center" color="textSecondary" variant="subtitle1">
{this.state.nowPlaying.artist_name}
</Typography>
<div>
<IconButton
onClick={() => { spotifyWebApi.skipToPrevious();
{this.getNowPlaying()}
}}
>
<SkipPreviousIcon />
</IconButton>
<IconButton
onClick={() => { spotifyWebApi.play();
{this.getNowPlaying()}
}}
>
<PlayArrowIcon />
</IconButton>
<IconButton
onClick={() => { spotifyWebApi.pause();
{this.getNowPlaying()}
}}
>
<PauseIcon />
</IconButton>
<IconButton onClick={() => spotifyWebApi.skipToNext()}>
{this.getNowPlaying()}
<SkipNextIcon />
</IconButton>
</div>
</Grid>
</Grid>
</Card>
</GridItem>
);
}
}
export default Player;

You need to bind this context to the function or use an arrow function.
Binding this:
constructor(){
this.getRoomCollection = this.getRoomCollection.bind(this);
this.getNowPlaying = this.getNowPlaying.bind(this);
}
Using arrow function:
Just make the function an arrow function
getRoomCollection = () => {
const db = firebase.firestore();
db.collection("rooms").onSnapshot((querySnapshot) => {
this.setState(querySnapshot.docs.map((doc) => ({
roomAdminMail: doc.data().roomAdminMail,
roomName: doc.data().roomName,
roomInfo: doc.data().roomInfo
})));
})
}

Related

Infer type from parent prop to children using React.cloneElement

I'm using formik to create a reusable component. This is how I've created a container to render a formik form.
I need to infer types from props; since, i've cloned a children, also passed on the props from the container, I need to infer types in the FormFields
import React from 'react';
import { Formik } from 'formik';
import { Container, Grid } from '#mui/material';
import MainCard from 'ui-component/cards/MainCard';
interface ContainerProps<T> {
title: string;
initialValues: T;
validationSchema: Object;
handleSubmit: (values: T, setSubmitting: (isSubmitting: boolean) => void) => void;
children: React.ReactElement;
others?: any;
}
const FormikContainer = <T,>({ title, initialValues, validationSchema, handleSubmit, children, ...others }: ContainerProps<T>) => (
<MainCard title={title}>
<Formik
enableReinitialize
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
handleSubmit(values, setSubmitting);
}}
>
{(props) => (
<form onSubmit={props.handleSubmit}>
<Grid container gap={3} maxWidth="500px">
{React.cloneElement(children, { ...props })}
</Grid>
</form>
)}
</Formik>
</MainCard>
);
export default FormikContainer;
I'm not sure on how to infer types in the FormFields for all the props associated with it. How can I define types for otherProps and since otherProps are props through formik;I need to infer formikProps types dynamically as well.
FormFields.tsx
/* eslint-disable consistent-return */
/* eslint jsx-a11y/label-has-associated-control: 0 */
import React, { useState } from 'react';
import {
Grid,
TextField,
FormHelperText,
FormControl,
FormLabel,
RadioGroup,
FormControlLabel,
Radio,
MenuItem,
Button,
IconButton,
Typography,
InputLabel
} from '#mui/material';
import { FieldArray, getIn } from 'formik';
import { v4 as uuid } from 'uuid';
import AddIcon from '#mui/icons-material/Add';
import DeleteOutlineIcon from '#mui/icons-material/DeleteOutline';
import { ImgWrapper } from './formik.styles';
import { Label } from 'components/form/Form';
type PropTypes = {
formFields: any;
btnLabel: string;
handleAdd?: any;
handleRemove?: any;
otherProps?: any;
};
const ErrorMessage = ({ errors, touched, name }) => {
// console.log(errors);
return <FormHelperText error>{getIn(touched, name) && getIn(errors, name) && getIn(errors, name)}</FormHelperText>;
};
const FormFields = ({ formFields, btnLabel, ...otherProps }): any => {
const { values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, setFieldValue, handleAdd, handleRemove } =
otherProps;
const [img, setImg] = useState<string>('');
return (
<>
{formFields.map((field, index) => {
switch (field.type) {
case 'fieldArray':
return (
<Grid key={index} item xs={12}>
<Typography
variant={field.mainHeading.variant || 'h4'}
component="h1"
textAlign={field.mainHeading.align}
sx={{ p: 2 }}
>
{field.mainHeading.title}
</Typography>
<FieldArray name={field.name}>
{({ push, remove }) => {
return (
<Grid container gap={3} maxWidth="100%">
{values[field.name].map((eachField, fieldIndex) => {
const IconButtonList = !field.iconButtonDisable && (
<Grid key={`container-${fieldIndex}`} container>
<Grid item xs={10}>
<Typography variant="h4">{`${field.subHeading} ${
fieldIndex + 1
}`}</Typography>
</Grid>
<Grid
item
xs={2}
sx={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}
>
<IconButton onClick={() => handleAdd(push)}>
<AddIcon />
</IconButton>
<IconButton
onClick={() => {
handleRemove(eachField.id, values, remove);
}}
disabled={values[field.name].length === 1}
>
<DeleteOutlineIcon />
</IconButton>
</Grid>
</Grid>
);
const QuestionList = field.choice.map((eachChoice, choiceIndex) => {
return (
<Grid key={`question-${choiceIndex}`} item xs={12}>
<InputLabel>{eachChoice.label}</InputLabel>
<TextField
fullWidth
placeholder={eachChoice.placeholder}
name={`${field.name}[${fieldIndex}].${eachChoice.name}`}
label={eachChoice.innerLabel}
type={eachChoice.type}
size="medium"
onBlur={handleBlur}
onChange={handleChange}
/>
<ErrorMessage
{...{
errors,
touched,
name: `${field.name}[${fieldIndex}].${eachChoice.name}`
}}
/>
</Grid>
);
});
return [IconButtonList, QuestionList];
})}
</Grid>
);
}}
</FieldArray>
</Grid>
);
}
})}
<Button type="submit" variant="contained" onSubmit={handleSubmit} disabled={isSubmitting}>
{btnLabel || 'Test Button'}
</Button>
</>
);
};
export default FormFields;
It's how I thought of creating a reusable component using formik. Am I doing it right ?
Form.tsx
import FormikContainer from 'components/formik/FormikContainer';
import FormFields from 'components/formik/FormFields';
import * as Yup from 'yup';
import { v4 as uuid } from 'uuid';
import AddPhotoAlternateOutlinedIcon from '#mui/icons-material/AddPhotoAlternateOutlined';
import { formFields } from 'views/userManagement/appUsers/constants/variables';
const initialValues = {
content: [{ id: uuid(), question: '', answer: '' }]
};
const fields = [
{
name: 'content',
type: 'fieldArray',
mainHeading: { align: 'center', title: 'Main heading' },
subHeading: 'Section',
iconButtonDisable: false,
choice: [
{ name: 'question', type: 'text', label: 'Question', placeholder: 'Enter question' },
{ name: 'answer', type: 'text', label: 'Answer', placeholder: 'Enter answer' }
]
}
];
const Form = () => {
const handleSubmit = (values, setSubmitting: (isSubmitting: boolean) => void) => {
console.log(values);
setSubmitting(false);
};
const handleAdd = (push) => {
push({ id: uuid(), question: '', answer: '' });
};
const handleRemove = (id, values, remove) => {
// const target = values.content.findIndex((value) => value.id === id);
// console.log(target);
// remove(target);
console.log(values);
values.content = values.content.filter((value) => value.id !== id);
};
return (
<FormikContainer<typeof initialValues>
title="Formik Reusable components"
initialValues={initialValues}
validationSchema={Yup.object().shape({
content: Yup.array().of(
Yup.object().shape({
question: Yup.string().required('Question is a required field'),
answer: Yup.string().required('Answer is a required field')
})
)
})}
handleSubmit={handleSubmit}
>
<FormFields formFields={fields} btnLabel="Test Button" handleAdd={handleAdd} handleRemove={handleRemove} />
</FormikContainer>
);
};
export default Form;

ReactJS and Twilio - TypeError: this.props.history not defined

Learning React for the first time, and opted to use Twilio's chat app demo to get a jump start.
I can render the welcome screen without issue, but not routed to the chat screen/room when I login. Including link to demo, code snippets, notes and more below.
Anyone out there see what's going on here and can advise? I've fixed a few issues so far that came up due to updates since the demo was posted (change Switch to Routes, etc.), but haven't been able to get past this TypeError. Any and all help is welcome and TIA!
Link to Twilio Demo: Twilio Programmable Chat App Demo
The error is raised in the login() function, at the line: this.props.history.push('chat', { email, room }); and the error reads Uncaught TypeError: this.props.history is undefined .
As an aside, I have attempted to import the withRouter method from react-router-dom but the method is not exported from react-router-dom and all information I am finding online about this method points to an older version of react-router-dom than what I am working with, so this is not a workable solution. I've also tried to apply .bind(this) on the onClick that calls login(), but this did not work either.
WelcomeScreen.js
import React from "react";
import {
Grid,
TextField,
Card,
AppBar,
Toolbar,
Typography,
Button,
} from "#material-ui/core";
class WelcomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
email: "",
room: "",
};
}
login = () => {
const { room, email } = this.state;
if (room && email) {
this.props.history.push("chat", { room, email });
}
}
handleChange = (event) => {
this.setState({ [event.target.name]: event.target.value });
};
render() {
const { email, room } = this.state;
return (
<>
<AppBar style={styles.header} elevation={10}>
<Toolbar>
<Typography variant="h6">
Chat App with Twilio Programmable Chat and React
</Typography>
</Toolbar>
</AppBar>
<Grid
style={styles.grid}
container
direction="column"
justify="center"
alignItems="center">
<Card style={styles.card} elevation={10}>
<Grid item style={styles.gridItem}>
<TextField
name="email"
required
style={styles.textField}
label="Email address"
placeholder="Enter email address"
variant="outlined"
type="email"
value={email}
onChange={this.handleChange}/>
</Grid>
<Grid item style={styles.gridItem}>
<TextField
name="room"
required
style={styles.textField}
label="Room"
placeholder="Enter room name"
variant="outlined"
value={room}
onChange={this.handleChange}/>
</Grid>
<Grid item style={styles.gridItem}>
<Button
color="primary"
variant="contained"
style={styles.button}
onClick={this.login}>
Login
</Button>
</Grid>
</Card>
</Grid>
</>
);
}
}
const styles = {
header: {},
grid: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0 },
card: { padding: 40 },
textField: { width: 300 },
gridItem: { paddingTop: 12, paddingBottom: 12 },
button: { width: 300 },
};
export default WelcomeScreen;
ChatScreen.js
import React from "react";
import {
AppBar,
Backdrop,
CircularProgress,
Container,
CssBaseline,
Grid,
IconButton,
List,
TextField,
Toolbar,
Typography,
} from "#material-ui/core";
import { Send } from "#material-ui/icons";
import axios from "axios";
import ChatItem from "./ChatItem";
const Chat = require("twilio-chat");
class ChatScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
text: "",
messages: [],
loading: false,
channel: null,
};
this.scrollDiv = React.createRef();
}
componentDidMount = async () => {
const { location } = this.props;
const { state } = location || {};
const { email, room } = state || {};
let token = "";
if (!email || !room) {
this.props.history.replace("/");
}
this.setState({ loading: true });
try {
token = await this.getToken(email);
} catch {
throw new Error("Unable to get token, please reload this page");
}
const client = await Chat.Client.create(token);
client.on("tokenAboutToExpire", async () => {
const token = await this.getToken(email);
client.updateToken(token);
});
client.on("tokenExpired", async () => {
const token = await this.getToken(email);
client.updateToken(token);
});
client.on("channelJoined", async (channel) => {
// getting list of all messages since this is an existing channel
const messages = await channel.getMessages();
this.setState({ messages: messages.items || [] });
this.scrollToBottom();
});
try {
const channel = await client.getChannelByUniqueName(room);
this.joinChannel(channel);
} catch(err) {
try {
const channel = await client.createChannel({
uniqueName: room,
friendlyName: room,
});
this.joinChannel(channel);
} catch {
throw new Error("Unable to create channel, please reload this page");
}
}
}
getToken = async (email) => {
const response = await axios.get(`http://localhost:5000/token/${email}`);
const { data } = response;
return data.token;
}
joinChannel = async (channel) => {
if (channel.channelState.status !== "joined") {
await channel.join();
}
this.setState({
channel:channel,
loading: false
});
channel.on("messageAdded", this.handleMessageAdded);
this.scrollToBottom();
};
handleMessageAdded = (message) => {
const { messages } = this.state;
this.setState({
messages: [...messages, message],
},
this.scrollToBottom
);
};
scrollToBottom = () => {
const scrollHeight = this.scrollDiv.current.scrollHeight;
const height = this.scrollDiv.current.clientHeight;
const maxScrollTop = scrollHeight - height;
this.scrollDiv.current.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
};
sendMessage = () => {
const { text, channel } = this.state;
if (text) {
this.setState({ loading: true });
channel.sendMessage(String(text).trim());
this.setState({ text: "", loading: false });
}
};
render() {
const { loading, text, messages, channel } = this.state;
const { location } = this.props;
const { state } = location || {};
const { email, room } = state || {};
return (
<Container component="main" maxWidth="md">
<Backdrop open={loading} style={{ zIndex: 99999 }}>
<CircularProgress style={{ color: "white" }} />
</Backdrop>
<AppBar elevation={10}>
<Toolbar>
<Typography variant="h6">
{`Room: ${room}, User: ${email}`}
</Typography>
</Toolbar>
</AppBar>
<CssBaseline />
<Grid container direction="column" style={styles.mainGrid}>
<Grid item style={styles.gridItemChatList} ref={this.scrollDiv}>
<List dense={true}>
{messages &&
messages.map((message) =>
<ChatItem
key={message.index}
message={message}
email={email}/>
)}
</List>
</Grid>
<Grid item style={styles.gridItemMessage}>
<Grid
container
direction="row"
justify="center"
alignItems="center">
<Grid item style={styles.textFieldContainer}>
<TextField
required
style={styles.textField}
placeholder="Enter message"
variant="outlined"
multiline
rows={2}
value={text}
disabled={!channel}
onChange={(event) =>
this.setState({ text: event.target.value })
}/>
</Grid>
<Grid item>
<IconButton
style={styles.sendButton}
onClick={this.sendMessage}
disabled={!channel}>
<Send style={styles.sendIcon} />
</IconButton>
</Grid>
</Grid>
</Grid>
</Grid>
</Container>
);
}
}
const styles = {
textField: { width: "100%", borderWidth: 0, borderColor: "transparent" },
textFieldContainer: { flex: 1, marginRight: 12 },
gridItem: { paddingTop: 12, paddingBottom: 12 },
gridItemChatList: { overflow: "auto", height: "70vh" },
gridItemMessage: { marginTop: 12, marginBottom: 12 },
sendButton: { backgroundColor: "#3f51b5" },
sendIcon: { color: "white" },
mainGrid: { paddingTop: 100, borderWidth: 1 },
};
export default ChatScreen;
Router.js
import React from "react";
import { BrowserRouter, Routes, Route, } from "react-router-dom";
import WelcomeScreen from "./WelcomeScreen";
import ChatScreen from "./ChatScreen";
function Router() {
return (
<BrowserRouter>
<Routes>
<Route path="/chat" element={<ChatScreen/>} />
<Route path="/" element={<WelcomeScreen/>} />
</Routes>
</BrowserRouter>
);
}
export default Router;
If ChatScreen and WindowScreen are functional Components, then you can directly use useNavigate instead of useHistory.
But can't find anything specific for class component.
It is given in migration from v5 section of the documentation.
https://reactrouter.com/docs/en/v6/upgrading/v5
You can search for useNavigate.
Solution (workaround)
You can create a Function Wrapper Component around your class
components and in that component, you can pass props like
navigate from useNavigate
I have taken an idea from HOC components in react.
Here is an example of what I tried and have worked.
https://codesandbox.io/s/snowy-moon-30br5?file=/src/Comp.js
This can work till we don't find a legitimate method of doing the same.
you can click on Click button in route /1 and it successfully navigates to route /2
Note: See the console for the passed props in the Sandox's console
Twilio developer evangelist here.
In your Router.js file you have:
<Route path="/chat" element={<ChatScreen/>} />
<Route path="/" element={<WelcomeScreen/>} />
The correct attribute is component not element. So your Router.js should look like this:
<Route path="/chat" component={<ChatScreen/>} />
<Route path="/" component={<WelcomeScreen/>} />

How can i manipulate data from api call in React?

I make an api call to openWeatherapi but the issue is the data is "undefined" until it has resolved. So when i make the api call in ComponentDidMount and set the data to state, state is undefined for a bit until the data comes in. The issue is that in the meantime if i try to do anything with the data i get cannot "Dosomething" of propriety of undefined and the whole thing crashes. How do i get around this?
I have a weatherwidget and i want to pass the weather data from the api call. The city name includes the continent, so if i want to do .split() on it, i get an error because the value is undefined at first.
Here's the dashboard code
import React, { PureComponent } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import brand from 'dan-api/dummy/brand';
import { Helmet } from 'react-helmet';
import { withStyles } from '#material-ui/core/styles';
import Hidden from '#material-ui/core/Hidden';
import Grid from '#material-ui/core/Grid';
import moment from 'moment';
import Divider from '#material-ui/core/Divider';
import {
SliderWidget,
CounterIconsWidget,
PerformanceChartWidget,
DateWidget,
TaskWidget,
WeatherWidget,
ContactWidget,
TimelineWidget,
FilesWidget,
} from 'dan-components';
import ScrollableTabs from '../UiElements/demos/Tabs/ScrollTabs';
import styles from './dashboard-jss';
class PersonalDashboard extends PureComponent {
constructor(props) {
super(props);
this.state = {
scheduleToday: [],
scheduleTomorrow: [],
weather: {},
};
}
fetchTodaySchedule = () => {
axios
.get(`/api/total/schedule/?day=${moment().format('llll')}`)
.then(response => {
this.setState({
scheduleToday: response.data[0].result,
});
})
.catch(error => {
console.log(error);
});
};
fetchTomorrowSchedule = () => {
axios
.get(
`api/total/schedule/?day=${moment()
.add(1, 'days')
.format('llll')}`,
)
.then(response => {
this.setState({
scheduleTomorrow: response.data[0].result,
});
})
.catch(error => {
console.log(error);
});
};
fetchWeather = async () => {
axios
.get(`api/total/weather`)
.then(response => {
this.setState({
weather: response.data,
});
})
.catch(error => {
console.log(error);
});
};
async componentWillMount() {
await this.fetchWeather();
this.fetchTodaySchedule();
this.fetchTomorrowSchedule();
}
render() {
const title = brand.name + ' - Personal Dashboard';
const description = brand.desc;
const { classes } = this.props;
return (
<div>
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
</Helmet>
{/* 1st Section */}
<Grid container spacing={3} className={classes.root}>
<Grid item md={6} xs={12}>
<CounterIconsWidget />
</Grid>
<Grid item md={6} sm={12} xs={12}>
<div className={classes.sliderWrap}>
<SliderWidget />
</div>
</Grid>
</Grid>
<Divider className={classes.divider} />
{/* 2nd Section */}
<Grid container spacing={2} className={classes.root}>
<Grid item xs={12}>
<PerformanceChartWidget />
</Grid>
</Grid>
{/* 3rd Section */}
<Grid container spacing={3} className={classes.root}>
<Grid item md={6} xs={12}>
<Divider className={classes.divider} />
{/* <ScrollableTabs /> */}
<ContactWidget />
<Divider className={classes.divider} />
<TaskWidget />
</Grid>
<Grid item md={6} xs={12}>
<Hidden mdDown>
<Divider className={classes.divider} />
</Hidden>
<WeatherWidget weatherData={this.state.weather} />
<Divider className={classes.divider} />
<DateWidget />
<Divider className={classes.divider} />
<TimelineWidget />
</Grid>
</Grid>
<Divider className={classes.divider} />
<FilesWidget />
</div>
);
}
}
PersonalDashboard.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(PersonalDashboard);
and this is the weatherwidget component
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import 'dan-styles/vendors/react-weather/GenericWeather.css';
import classNames from 'classnames';
import styles from './widget-jss';
function WeatherWidget(props) {
const { status, classes, temp, city, weatherData } = props;
const cls = classNames('weather-icon', status);
const bg = classNames(classes.weathercard, status === 'sun' ? classes.sun : classes.cloud);
return (
<div className={bg}>
<div className="wheater-wrap">
<div className={cls} />
<h1>{temp}ยบ</h1>
<p>{weatherData.timezone}</p>
</div>
</div>
);
}
WeatherWidget.propTypes = {
classes: PropTypes.object.isRequired,
city: PropTypes.string,
temp: PropTypes.number,
status: PropTypes.string,
};
WeatherWidget.defaultProps = {
city: 'Bucharest',
temp: 28,
status: 'sun', // cloud and sun
};
export default withStyles(styles)(WeatherWidget);
async componentDidMount() {
try {
const [tickets, alerts, calls, weather, schedToday, schedTomorrow] = await Promise.all([
axios.get('api/total/tickets'),
axios.get('api/total/alerts'),
axios.get('api/total/avaya/logs'),
axios.get('api/total/weather'),
axios.get(`/api/total/schedule/?day=${today}`),
axios.get(`/api/total/schedule/?day=${tomorrow}`),
]);
this.setState({
tickets: tickets.data.tickets,
alerts: alerts.data,
calls: calls.data,
weather: weather.data,
scheduleToday: schedToday.data[0].result,
scheduleTomorrow: schedTomorrow.data[0].result
});
} catch (err) {
console.error(err);
}
}
I ended up doing it this way and used async componentDidMount.
So far it seems to work fine.
async componentDidMount() {
const ticketsPromise = await axios.get('api/total/tickets');
const alertsPromise = await axios.get('api/total/alerts');
const callsPromise = await axios.get('api/total/avaya/logs');
const weatherPromise = await axios.get('api/total/weather');
const scheduleTodayPromise = await axios.get(`/api/total/schedule/?day=${today}`);
const scheduleTomorrowPromise = await axios.get(`/api/total/schedule/?day=${tomorrow}`);
const resolves = await Promise.all([
ticketsPromise.data,
alertsPromise.data,
callsPromise.data,
weatherPromise.data,
scheduleTodayPromise.data[0].result,
scheduleTomorrowPromise.data[0].result,
]);
const [ticketsData, alertsData, callsData, weatherData, schedTodayData, schedTomorrowData] =
resolves || [];
this.setState({ tickets: ticketsData.tickets });
this.setState({ alerts: alertsData });
this.setState({ calls: callsData });
this.setState({ weather: weatherData });
this.setState({ scheduleToday: schedTodayData });
this.setState({ scheduleTomorrow: schedTomorrowData });
}

Clicking like icons increases the like count of other components

Upon the click of a single like, it is increasing the number of likes for both separate components. What is causing both like numbers to increase, and how can I code it to where only one like number increases upon clicking a like?
I have also include the console in the picture below where I have console logged the logic in my reducer. You can find the code for the reducer further below the picture.
Reducer code
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "../actions/types";
const initialState = {
goals: [],
goal: null,
loading: true,
error: {}
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case GET_GOALS:
return {
...state,
goals: payload,
loading: false
};
case GOAL_ERROR:
return {
...state,
error: payload,
loading: false
};
case UPDATE_LIKES:
return {
...state,
goals: state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) ? { ...goal, likes: payload.likes } : goal
),
loading: false
};
default:
return state;
}
}
Action code
import axios from "axios";
import { GET_GOALS, GOAL_ERROR, UPDATE_LIKES } from "./types";
// Get goals
export const getGoals = () => async dispatch => {
try {
const res = await axios.get("/api/goal/goalfeed");
dispatch({
type: GET_GOALS,
payload: res.data
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Add like
export const addLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/like/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
// Remove like
export const removeLike = goalId => async dispatch => {
try {
const res = await axios.put(`/api/goal/unlike/${goalId}`);
dispatch({
type: UPDATE_LIKES,
payload: { goalId, likes: res.data }
});
} catch (error) {
dispatch({
type: GOAL_ERROR,
payload: { msg: error.response }
});
}
};
Goals component code
import React, { useEffect } from "react";
import Moment from "react-moment";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { addLike, removeLike } from "../../actions/goal";
import { getGoals } from "../../actions/goal";
import Spinner from "../layout/Spinner";
import Navbar from "../dashboard/Navbar";
import ThumbUpAltIcon from "#material-ui/icons/ThumbUpAlt";
import ThumbDownAltIcon from "#material-ui/icons/ThumbDownAlt";
import ChatIcon from "#material-ui/icons/Chat";
import DeleteIcon from "#material-ui/icons/Delete";
import DoneIcon from "#material-ui/icons/Done";
import {
Typography,
Container,
CssBaseline,
makeStyles,
Grid,
Avatar,
Paper,
Button
} from "#material-ui/core";
const useStyles = makeStyles(theme => ({
paper: {
height: "auto",
marginBottom: theme.spacing(3)
},
actionButtons: {
marginTop: "3vh"
},
profileHeader: {
textAlign: "center",
marginBottom: 20
},
avatar: {
width: theme.spacing(7),
height: theme.spacing(7)
}
}));
const Goals = ({
getGoals,
auth,
addLike,
removeLike,
goal: { goals, user, loading }
}) => {
useEffect(() => {
getGoals();
}, [getGoals]);
const classes = useStyles();
return loading ? (
<>
<Navbar />
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<Spinner />
</div>
</Container>
</>
) : (
<>
<CssBaseline />
<Navbar />
<main>
<Container>
<Typography variant="h2" className={classes.profileHeader}>
Goals
</Typography>
{/* parent grid */}
<Grid container spacing={4}>
{goals.map(singleGoal => (
<Grid
className={classes.paper}
key={singleGoal._id}
spacing={1}
container
item
direction="row"
alignItems="center"
component={Paper}
>
<Grid
item
container
direction="column"
justify="center"
alignItems="center"
xs={3}
>
<Avatar className={classes.avatar} src={singleGoal.avatar} />
<Typography variant="caption">
{singleGoal.first_name} {singleGoal.last_name}
</Typography>
<Typography variant="caption" className={classes.postedOn}>
Posted on{" "}
<Moment format="MM/DD/YYYY">{singleGoal.date}</Moment>
</Typography>
</Grid>
<Grid container item direction="column" xs={9}>
<Typography variant="body1">{singleGoal.text}</Typography>
<Grid item className={classes.actionButtons}>
<Button size="small" onClick={e => addLike(singleGoal._id)}>
<ThumbUpAltIcon />
</Button>
<Typography variant="caption">
{singleGoal.likes.length}
</Typography>
<Button
size="small"
onClick={e => removeLike(singleGoal._id)}
>
<ThumbDownAltIcon />
</Button>
<Button href={`/goal/${singleGoal._id}`} size="small">
<ChatIcon />
</Button>
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DoneIcon />
</Button>
)}
{!auth.loading && singleGoal.user === auth.user._id && (
<Button size="small">
<DeleteIcon />
</Button>
)}
</Grid>
</Grid>
</Grid>
))}
</Grid>
</Container>
</main>
</>
);
};
Goals.propTypes = {
getGoals: PropTypes.func.isRequired,
goal: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
goal: state.goal,
auth: state.auth
});
export default connect(mapStateToProps, { getGoals, addLike, removeLike })(
Goals
);
There exists a flaw in your conditional test.
state.goals.map(goal =>
console.log("goal id", goal._id) === console.log("payload id", payload.goalId) // What is this? it will always evaluate to true
? { ...goal, likes: payload.likes }
: goal
)
console.log('EQUAL?', console.log() === console.log()); // true
console.log('EQUAL?', console.log(3) === console.log(3)); // true
console.log('EQUAL?', console.log(3) === console.log('three')); // true
console.log('EQUAL?', console.log('apple') === console.log({})); // true
console.log('EQUAL?', console.log(42) === console.log(-42)); // true
The function console.log is a void return, i.e. undefined, so you are comparing undefined === undefined, which is always true.
console.log(undefined === undefined); // true
You are spreading in the new 'likes' value to every goal object.
Try instead:
state.goals.map(
goal => goal._id === payload.goalId
? { ...goal, likes: payload.likes }
: goal
)

How can I avoid to get too much recursion when storing data using firebase?

I'm creating a small application using only React.js, material-ui and firebase. I don't want to use Redux now in order to be familiar more with react.
I create a form which is described by:
User.jsx:
import React, { Component } from 'react'
import Button from '#material-ui/core/Button'
import Grid from '#material-ui/core/Grid';
import { withStyles } from '#material-ui/core/styles';
import PropTypes from 'prop-types';
import Typography from '#material-ui/core/Typography';
import moment from 'moment';
import db from '../db/config';
import InputTextField from './textField';
import RadioGroup from './radioGroup';
import SnackBar from './snackBar';
const styles = (theme) => ({
button: {
margin: theme.spacing.unit,
},
root: {
display: 'flex',
marginTop: theme.spacing.unit * 8,
padding: theme.spacing.unit * 3,
},
item: {
padding: theme.spacing.unit * 2
}
});
class User extends Component {
state = {
birthday: moment().format('YYYY-MM-DD'),
message: '',
name: '',
open: false,
gender: 'male',
};
handleChange = name => event => {
this.setState({
[name]: event.target.value,
});
};
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
handleSubmit = (event) => {
event.preventDefault();
const {
birthday,
name,
gender,
} = this.state;
console.log(`birthday: ${birthday} \n` +
`Name: ${name} \n` +
`gender: ${gender} \n`)
const ref = db.ref('users/');
ref.orderByChild('name').on('child_added', (snapshot) => {
const existedName = (snapshot.val().name).toLowerCase().trim();
const newName = name.toLowerCase().trim();
if(existedName === newName){
this.setState({
open: true,
message: 'Name already exists!!',
})
} else {
ref.push({
name: name.trim(),
gender,
birthday
})
.then(() => {
this.setState({
open: true,
message: 'saved successfully!!',
})
return true;
})
.catch((error) => {
this.setState({
open: true,
message: `Error adding baby: ${error}`,
})
return false;
});
}
})
}
render(){
const { classes } = this.props;
const {
birthday,
message,
name,
open,
gender,
} = this.state;
return (
<div className={classes.root}>
<Grid
container
spacing={40}
justify='center'
direction="column"
alignItems="center"
>
{open && (
<SnackBar
handleClose={this.handleClose}
message={message}
open
/>
)}
<Typography
align="center"
gutterBottom
variant="title"
>
Add New User
</Typography>
<form onSubmit={this.handleSubmit} className={classes.form}>
<Grid item className={classes.item} xs={12}>
<InputTextField
label="Name"
handleChange={this.handleChange('name')}
required
value={name}
type="text"
/>
</Grid>
<Grid item className={classes.item}>
<InputTextField
label="Birthday"
handleChange={this.handleChange('birthday')}
required
value={birthday}
type="date"
InputLabelProps={{shrink: true}}
/>
</Grid>
<Grid item className={classes.item}>
<RadioGroup
name="Gender"
handleChange={this.handleChange('gender')}
value={gender}
/>
</Grid>
<Grid item className={classes.item}>
<Grid
container
direction="row"
>
<Grid item>
<Button
variant="contained"
color="primary"
className={classes.button}
>
Cancel
</Button>
</Grid>
<Grid item>
<Button
variant="contained"
color="primary"
className={classes.button}
type='submit'
>
Save
</Button>
</Grid>
</Grid>
</Grid>
</form>
</Grid>
</div>
)
}
}
User.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(User);
SnackBar.jsx:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
import Snackbar from '#material-ui/core/Snackbar';
import IconButton from '#material-ui/core/IconButton';
import CloseIcon from '#material-ui/icons/Close';
const styles = theme => ({
close: {
width: theme.spacing.unit * 4,
height: theme.spacing.unit * 4,
},
});
const SimpleSnackbar = (props) => {
const {
classes,
handleClose,
message,
open,
} = props;
return (
<div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={open}
autoHideDuration={6000}
onClose={handleClose}
ContentProps={{
'aria-describedby': 'message-id',
}}
message={<span id="message-id">{message}</span>}
action={[
<Button key="undo" color="secondary" size="small" onClick={handleClose}>
UNDO
</Button>,
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={handleClose}
>
<CloseIcon />
</IconButton>,
]}
/>
</div>
);
}
SimpleSnackbar.propTypes = {
classes: PropTypes.object.isRequired,
handleClose: PropTypes.func.isRequired,
message: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
};
export default withStyles(styles)(SimpleSnackbar);
When I enter the different attributes of the form, I got this warning in the console: Firebase Warning
Added to the message too much recursion described by Firebase: too much recursion
I'm beginner to firebase and it's my first app using it.I think that I missed something or I didn't use the suitable function in order to fetch if a given name already exists. Unless it will be saved. I will be grateful if anyone tried to help me to fix the warning and the error displayed on the console.
I tried this solution and it works:
handleSubmit = (event) => {
event.preventDefault();
const {
birthday,
name,
gender,
} = this.state;
const ref = db.ref('users/');
const onChildAdded = (snapshot) => {
if(snapshot.exists()){
this.setState({
open: true,
message: 'Name already exists!!',
})
} else {
ref.push({
name: name.trim(),
gender,
birthday
})
.then(() => {
this.setState({
open: true,
message: 'saved successfully!!',
})
return true;
})
.catch((error) => {
this.setState({
open: true,
message: `Error adding baby: ${error}`,
})
return false;
});
}
}
ref.orderByChild('name').equalTo(name).once('value').then(onChildAdded);
}

Categories

Resources