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 });
}
Related
This is where i generate the token
import React, { useState, useEffect } from 'react';
import { Paper, Stepper, Step, StepLabel, Typography, CircularProgress, Divider, Button } from '#material-ui/core';
import { commerce } from '../../../lib/commerce';
import useStyles from './styles';
import AddressForm from '../AddressForm';
import PaymentForm from '../PaymentForm';
const steps = ['Shipping address', 'Payment details'];
const Checkout = ({ cart }) => {
const [activeStep, setActiveStep] = useState(0);
const [checkoutToken, setCheckoutToken] = useState(null);
const classes = useStyles();
useEffect(() => {
if (cart.id) {
const generateToken = async () => {
try {
const token = await commerce.checkout.generateToken(cart.id, { type: 'cart' });
setCheckoutToken(token)
} catch (error){
console.log(error);
}
};
generateToken();
}
}, [cart]);
const Confirmation = () => (
<div>
Confirmation
</div>
)
const Form = () => activeStep === 0
? <AddressForm checkoutToken={checkoutToken} />
: <PaymentForm />
return (
<>
<div className={classes.toolbar} />
<main className={classes.layout} >
<Paper className={classes.paper}>
<Typography variant='h4' align='center'>Checkout</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((step) => (
<Step key={step}>
<StepLabel>{step}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? <Confirmation /> : checkoutToken && <Form />}
</Paper>
</main>
</>
)
}
export default Checkout
Here is my App.js
import React, { useState, useEffect, Fragment } from 'react'
import { commerce } from './lib/commerce';
import { Products, Navbar, Cart, Checkout } from './components';
import { BrowserRouter as Router, Routes, Route} from 'react-router-dom';
const App = () => {
const [products, setProducts] = useState([]);
const [cart, setCart] = useState({});
const fetchProducts = async () => {
const { data } = await commerce.products.list();
setProducts(data);
}
const fetchCart = async () => {
setCart(await commerce.cart.retrieve())
}
const handleAddToCart = async ( productId, quantity) =>{
const { cart } = await commerce.cart.add(productId, quantity);
setCart(cart);
}
const handleUpdateCartQty = async (productId, quantity) => {
const { cart } = await commerce.cart.update(productId, { quantity });
setCart(cart);
}
const handleRemoveFromCart = async (productId) => {
const { cart } = await commerce.cart.remove(productId);
setCart(cart);
}
const handleEmptyCart = async () => {
const { cart } = await commerce.cart.empty();
setCart(cart);
}
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
return (
<Router>
<div>
<Navbar totalItems={cart.total_items} />
<Routes>
<Route exact path='/' element={<Products products={products} onAddToCart={handleAddToCart} />} />
<Route exact path='/cart' element={<Cart cart={cart} handleUpdateCartQty={handleUpdateCartQty} handleAddToCart={handleAddToCart} handleRemoveFromCart={handleRemoveFromCart} handleEmptyCart={handleEmptyCart} />} />
<Route exact path='/checkout' element={ <Checkout cart={cart} />} />
</Routes>
</div>
</Router>
)
}
export default App;
And here is my cart.jsx incase their is anything relevant there
import React from 'react'
import { Container, Typography, Button, Grid} from '#material-ui/core';
import { Link } from 'react-router-dom';
import useStyles from './styles';
import CartItem from './CartItem/CartItem';
const Cart = ({ cart, handleUpdateCartQty, handleRemoveFromCart, handleEmptyCart }) => {
const classes = useStyles();
const EmptyCart = () => (
<Typography variant='subtitle1'>
You have no items in your shopping cart.
<Link to='/' className={classes.link}>Add Items!</Link>
</Typography>
);
const FilledCart = () => (
<>
<Grid container spacing={3}>
{ cart.line_items.map((item) => (
<Grid item xs={12} sm={4} key={item.id}>
<CartItem item={item} onUpdateCartQty={handleUpdateCartQty} onRemoveFromCart={handleRemoveFromCart} />
</Grid>
))}
</Grid>
<div className={classes.cardDetails}>
<Typography variant='h4'>
Subtotal: {cart.subtotal.formatted_with_symbol}
<div>
<Button className={classes.emptyButton} size='large' type='button' variant='contained' color='secondary' onClick={handleEmptyCart}>
Empty Cart
</Button>
<Button component={Link} to='/checkout' className={classes.checkoutButton} size='large' type='button' variant='contained' color='primary'>
Checkout
</Button>
</div>
</Typography>
</div>
</>
);
// Wait for cart to load items
if(!cart.line_items){
return '...loading';
}
return (
<Container>
<div className={classes.toolbar} />
<Typography className={classes.title} varaint='h3' gutterBottom >Your Shopping Cart</Typography>
{ !cart.line_items.length ? <EmptyCart /> : <FilledCart />}
</Container>
)
}
export default Cart
[error messages][1]
[1]: https://i.stack.imgur.com/vlard.png
Warning: Expected onSubmit listener to be a function, instead got a
value of string type. form
FormProvider#http://localhost:3000/static/js/bundle.js:76722:7
AddressForm#http://localhost:3000/static/js/bundle.js:1096:7 Form div
Paper#http://localhost:3000/static/js/bundle.js:12332:17
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 main
Checkout#http://localhost:3000/static/js/bundle.js:1332:7
Routes#http://localhost:3000/static/js/bundle.js:67209:7 div
Router#http://localhost:3000/static/js/bundle.js:67146:7
BrowserRouter#http://localhost:3000/static/js/bundle.js:65952:7
App#http://localhost:3000/static/js/bundle.js:347:82
Warning: A component is changing a controlled input to be
uncontrolled. This is likely caused by the value changing from a
defined to undefined, which should not happen. Decide between using a
controlled or uncontrolled input element for the lifetime of the
component. More info: https://reactjs.org/link/controlled-components
input SelectInput#http://localhost:3000/static/js/bundle.js:13482:19
div InputBase#http://localhost:3000/static/js/bundle.js:8257:25
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25
Input#http://localhost:3000/static/js/bundle.js:9146:26
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25
Select#http://localhost:3000/static/js/bundle.js:13182:26
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 div
Grid#http://localhost:3000/static/js/bundle.js:7352:29
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 div
Grid#http://localhost:3000/static/js/bundle.js:7352:29
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 form
FormProvider#http://localhost:3000/static/js/bundle.js:76722:7
AddressForm#http://localhost:3000/static/js/bundle.js:1096:7 Form div
Paper#http://localhost:3000/static/js/bundle.js:12332:17
WithStyles#http://localhost:3000/static/js/bundle.js:19639:25 main
Checkout#http://localhost:3000/static/js/bundle.js:1332:7
Routes#http://localhost:3000/static/js/bundle.js:67209:7
I was trying to render multiple props in a single component both the props are from different apis which are working together and the problem is i want to map the props in a single component to display a list of posts. someone said me do this by creating a variable (array) in my component. Then, spreading the properties (props) into the variable e.g myVariable.push(...posts, ...externalPosts). But i can't seem to figure out how do i achieve the results the Component renders another child component called to which i want to pass on the props.
App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Posts } from './components';
const App = () => {
const [ posts, setPosts ] = useState([]);
const [ postsExternal, setPostsExternal ] = useState([]);
const fetchPostsAll = () => {
axios.get(`http://localhost:2000/posts`).then(({ data }) => {
let externalPosts = [];
setPosts(data);
console.log(data);
data.map(({ external_id }) => {
axios
.get(`http://localhost:2000/posts${external_id}`)
.then(({ data }) => {
console.log(data);
externalPosts.push(data);
});
setPostsExternal(externalPosts);
});
});
}
useEffect(() => {
fetchPostsAll();
}, []);
return (
<div>
<Navbar/>
<Posts posts={posts} postsExternal={postsExternal} />
</div>
);
};
export default App;
Posts.js
import React from 'react';
import Post from './Post/Post';
const Posts = ({ posts, postsExternal }) => {
return (
<main>
<Container fluid>
<Row className="p-2">
{ posts.map((post) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post post={post} postExternal={postsExternal}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
Post.js
import React from 'react';
const Post = ({ post, postExternal }) => {
return (
<Figure>
<span>{post.title}</span>
<span>{postExternal.rating}</span>
</Figure>
)
}
export default Post;
The problem is with the Posts.js file while mapping i want to map both the props and pass those single item props to Post.js
Do:
import React from 'react';
import Post from './Post/Post';
const Posts = ({ posts, postsExternal }) => {
return (
<main>
<Container fluid>
<Row className="p-2">
{ posts.map((post, index) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post post={post} postExternal={postsExternal[index]}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
Edited:
import React from 'react';
import Post from './Post/Post';
const Posts = ({ posts, postsExternal }) => {
return (
<main>
<Container fluid>
<Row className="p-2">
{posts.length !== 0 && postsExternal.length !== 0 && posts.map((post, index) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post post={post} postExternal={postsExternal[index]}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
Make sure your data is loaded.
fetchPostsAll function:
const fetchPostsAll = () => {
axios.get(`http://localhost:2000/posts`)
.then(({ data }) => {
console.log(data);
const externalPosts = data.map(({ external_id }) => {
return axios
.get(`http://localhost:2000/posts${external_id}`)
.then(({ data }) => {
return data;
});
})
Promise
.all(externalPosts)
.then(externalPosts => {
setPosts(data);
setExternalPosts(externalPosts);
});
})
;
}
You just don't desctructure the props and pass it as is.
import React from 'react';
import Post from './Post/Post';
const Posts = (props) => {
const { posts } = props;
return (
<main>
<Container fluid>
<Row className="p-2">
{ posts.map((post) => (
<Col className="p-lg-4 p-sm-3" key={post.id} xs={6} sm={4} md={3} lg={3} xl={2}>
<Post {...props} post={post}/>
</Col>
))}
</Row>
</Container>
</main>
);
};
export default Posts;
And you can do this in your App.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import { Posts } from './components';
const App = () => {
const [ posts, setPosts ] = useState([]);
const [ postsExternal, setPostsExternal ] = useState([]);
const fetchPostsAll = () => {
axios.get(`http://localhost:2000/posts`).then(({ data }) => {
let externalPosts = [];
setPosts(data);
console.log(data);
data.map(({ external_id }) => {
axios
.get(`http://localhost:2000/posts${external_id}`)
.then(({ data }) => {
console.log(data);
externalPosts.push(data);
});
setPostsExternal(externalPosts);
});
});
}
useEffect(() => {
fetchPostsAll();
}, []);
const props = { posts, postsExternal };
return (
<div>
<Navbar/>
<Posts {...props} />
</div>
);
};
export default App;
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/>} />
It was working a while ago, the searchBar component was a class component and I tried to change it to a functional component but suddenly I receive error 400. I tried to create a new youtube API but it keeps showing.
is it because of api?
Here is my App.js
import { Grid } from '#material-ui/core'
import React, { useState } from 'react'
import youtube from './api/youtube'
import { SearchBar, VideoDetail, VideoList } from './components'
const App = () => {
const [videos, setVideo] = useState([])
const [selectdvideo, setSelectedVideo] = useState(null)
const onFormSubmit = async (value) => {
const response = await youtube.get('search', {
params: {
part: 'snippet',
maxResults: 5,
key: '',
q: value,
},
})
console.log('hello')
setVideo(response.data.items)
setSelectedVideo(response.data.items[0])
}
return (
<Grid justifyContent="center" container spacing={10}>
<Grid item xs={12}>
<Grid container spacing={10}>
<Grid item xs={12}>
<SearchBar onFormSubmit={onFormSubmit} />
</Grid>
<Grid item xs={8}>
<VideoDetail video={selectdvideo} />
</Grid>
<Grid item xs={4}>
<VideoList videos={videos} onSelectVideo={setSelectedVideo} />
</Grid>
</Grid>
</Grid>
</Grid>
)
}
export default App
This is the searchBar
import { Paper, TextField } from '#material-ui/core'
import React, { useState } from 'react'
const SearchBar = ({ onFormSubmit }) => {
const [searchTerm, setSearchTerm] = useState('')
const handleChange = (event) => setSearchTerm(event.target.value)
const onKeyPress = (event) => {
if (event.key === 'Enter') {
onFormSubmit(searchTerm)
}
}
return (
<Paper elavtion={6} style={{ padding: '25px' }}>
<TextField
fullWidth
label="Search..."
value={searchTerm}
onChange={handleChange}
onKeyPress={onKeyPress}
/>
</Paper>
)
}
export default SearchBar
[enter image description here][1]
I receive this error
https://ibb.co/Vpz9nKG
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);
}