I am tring to create a video streaming app, and I got an error logging: "Invalid hook call" when i set "{ ready, tracks } = useMicrophoneAndCameraTracks();" and client = useClient();
I tried not calling the hook useMicrophoneAndCameraTracks and useClient, the error wasn't there but the video won't stream. Here is my code
Settings.js
import { createClient, createMicrophoneAndCameraTracks } from "agora-rtc-react";
const appId = "d13f5075f00d417092285bc8a52f17f2";
const token = "007eJxTYOjZ/X7lClbPsGXOv7vW/egUYDx+N+WSl9Bzddlf2fyLD5orMKQYGqeZGpibphkYpJgYmhtYGhlZmCYlWySaGqUZmqcZKT+bk9wQyMiwSdaKkZEBAkF8FobcxMw8BgYAbJIfFw=="
export const config = { mode: "rtc", codec: "vp8", appId: appId, token: token };
export const useClient = createClient(config);
export const useMicrophoneAndCameraTracks = createMicrophoneAndCameraTracks();
export const channelName = "main";
VideoCall.js
import { useState, useEffect } from "react";
import {
config,
useClient,
useMicrophoneAndCameraTracks,
channelName,
} from "./settings.js";
import { Grid } from "#material-ui/core";
import Video from "./Video";
import Controls from "./Controls";
export default function VideoCall(props) {
const { setInCall } = props;
const [users, setUsers] = useState([]);
const [start, setStart] = useState(false);
const client = useClient();
const { ready, tracks } = useMicrophoneAndCameraTracks();
useEffect(() => {
let init = async (name) => {
client.on("user-published", async (user, mediaType) => {
await client.subscribe(user, mediaType);
if (mediaType === "video") {
setUsers((prevUsers) => {
return [...prevUsers, user];
});
}
if (mediaType === "audio") {
user.audioTrack.play();
}
});
client.on("user-unpublished", (user, mediaType) => {
if (mediaType === "audio") {
if (user.audioTrack) user.audioTrack.stop();
}
if (mediaType === "video") {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
}
});
client.on("user-left", (user) => {
setUsers((prevUsers) => {
return prevUsers.filter((User) => User.uid !== user.uid);
});
});
try {
await client.join(config.appId, name, config.token, null);
} catch (error) {
console.log("error");
}
if (tracks) await client.publish([tracks[0], tracks[1]]);
setStart(true);
};
if (ready && tracks) {
try {
init(channelName);
} catch (error) {
console.log(error);
}
}
}, [channelName, client, ready, tracks]);
return (
<Grid container direction="column" style={{ height: "100%" }}>
<Grid item style={{ height: "5%" }}>
{ready && tracks && (
<Controls tracks={tracks} setStart={setStart} setInCall={setInCall} />
)}
</Grid>
<Grid item style={{ height: "95%" }}>
{start && tracks && <Video tracks={tracks} users={users} />}
</Grid>
</Grid>
);
}
App.js
import { useState } from "react";
import { Button } from "#material-ui/core";
import VideoCall from "./VideoCall";
function App() {
const [inCall, setInCall] = useState(false);
return (
<div className="App" style={{ height: "100%" }}>
{inCall ? (
<VideoCall setInCall={setInCall} />
) : (
<Button
variant="contained"
color="primary"
onClick={() => setInCall(true)}
>
Join Call
</Button>
)}
</div>
);
}
export default App;
Controls.js
import { useState } from 'react';
import { useClient } from './settings';
import { Grid, Button } from '#material-ui/core';
import MicIcon from '#material-ui/icons/Mic';
import MicOffIcon from '#material-ui/icons/MicOff';
import VideocamIcon from '#material-ui/icons/Videocam';
import VideocamOffIcon from '#material-ui/icons/VideocamOff';
import ExitToAppIcon from '#material-ui/icons/ExitToApp';
export default function Controls(props) {
const client = useClient();
const {tracks, setStart, setInCall} = props;
const [trackState, setTrackState] = useState({video: true, audio: true});
const mute = async(type)=> {
if(type === "audio") {
await tracks[0].setEnabled(!trackState.audio);
setTrackState((ps) => {
return {...ps, audio: !ps.audio}
})
}
else if(type === "video") {
await tracks[1].setEnabled(!trackState.video);
setTrackState((ps) => {
return {...ps, video: !ps.video}
})
}
}
const leaveChannel = async() => {
await client.leave();
client.removeAllListeners();
tracks[0].close();
tracks[1].close();
setStart(false);
setInCall(false);
}
return (
<Grid container spacing={2} alignItems='center'>
<Grid item>
<Button variant="contained" color={trackState.audio ? "primary" : "secondary"} onclick={() => mute("audio")}>
{trackState.audio ? <MicIcon/> : <MicOffIcon/>}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color={trackState.video ? "primary" : "secondary"} onclick={() => mute("video")}>
{trackState.video ? <VideocamIcon/> : <VideocamOffIcon/>}
</Button>
</Grid>
<Grid item>
<Button variant="contained" color="default" onclick={() => leaveChannel()}>
Leave <ExitToAppIcon/>
</Button>
</Grid>
</Grid>
)
}
Video.js
import { AgoraVideoPlayer } from "agora-rtc-react";
import { Grid } from "#material-ui/core";
import { useState, useEffect } from "react";
export default function Video(props) {
const { users, tracks } = props;
const [gridSpacing, setGridSpacing] = useState(12);
useEffect(() => {
setGridSpacing(Math.max(Math.floor(12 / (users.length + 1)), 4));
}, [users, tracks]);
return (
<Grid container style={{ height: "100%" }}>
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={tracks[1]}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
{users.length > 0 &&
users.map((user) => {
if (user.videoTrack) {
return (
<Grid item xs={gridSpacing}>
<AgoraVideoPlayer
videoTrack={user.videoTrack}
key={user.uid}
style={{ height: "100%", width: "100%" }}
/>
</Grid>
);
} else return null;
})}
</Grid>
);
}
Without more detailed code, we can't help you. You probably broke a rule regarding react hooks.
The official docs state the following:
There are three common reasons you might be seeing it:
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
Read more about it here:
https://reactjs.org/warnings/invalid-hook-call-warning.html
Related
How can I conver the MUI component to html string, so all the styles we be included as well?
My main goal is to create a dynamic template form and to save as PDF file on server side.
i'm trying to send the component as html string, in order to generate PDF file. There is only one problem, all mui styles got lost in pdf file
This is what I'm doing now:
client:
const formTemplate = (user)=>{
return (
<Grid container>
<Grid item lg={4} xs={12}>
<TextField fullWidth
defaultValue={user.firstName} label={"First Name"} />
</Grid>
<Grid item lg={4} xs={12}>
<TextField fullWidth
defaultValue={user.familyName} label={"FamilyName"} />
</Grid>
</Grid>)
}
function saveForm(user){
const formTamplate = ReactDOMServer.renderToString(formTemplate(user))
try {
const response = await axios.post(`${url}/consent/sign`, {
email: user.email,
formTamplate: formTamplate
});
dispatch({
type: ActionType.setEnvelopeInfo,
payload: {
envelope: response.data.envelope,
},
});
} catch (err) {
console.log(err);
}
}
server:
router.post("/sign", async (req, res) => {
const { email, formTamplate } = req.body;
const formTemplatePath = `${signed_docs}\\${name}.pdf`;
let options = { format: "A4" };
let file = { content: formTamplate};
try {
await html_to_pdf.generatePdf(file, options).then((pdfBuffer: any) => {
console.log({ pdfBuffer: pdfBuffer });
console.log(typeof pdfBuffer);
fs.writeFile(formTemplatePath, pdfBuffer, (err) => {
if (err) {
console.log({ files_err: err });
return;
}
});
console.log("PDF Buffer:-", pdfBuffer);
});
} catch (err) {
console.log({ pdf_err: err });
}
import { useRef } from 'react';
import './App.css';
import { Button, Box, Link, Typography, Grid } from '#mui/material'
import ReactToPrint from 'react-to-print';
function App() {
const reference = useRef();
return (
<>
<Button onClick={()=>{
console.log(reference.current)
}}>ref</Button>
<Box sx={{textAlign:'center'}}>
<ReactToPrint
trigger={() => <Link>Print</Link>}
content={() => reference.current!}/>
{/* Place the component here and set the reference */}
<Box ref={reference} sx={{textAlign:"center"}}>
<br/>
<Typography variant='h4'>Booking details</Typography>
<Grid container direction="column" spacing={1}>
<Grid container item direction="row" justifyContent='space-around'>
<Typography variant="body1" color="initial">VAT</Typography>
<Typography variant="body1" color="initial">$XXX</Typography>
</Grid>
<Grid container item direction="row" justifyContent='space-around'>
<Typography variant="body1" color="initial">VAT</Typography>
<Typography variant="body1" color="initial">$XXX</Typography>
</Grid>
</Grid>
</Box>
</Box>
</>
);
}
export default App;
console.log(reference.current) logs this output
click here to see
useRef hook can be used and the reference has that everything converted to html.
suggestion: react-to-print can be used to directly convert mui components easily, have a look into it. Hope this helps!
I am trying to create a test, that checks wether a component draws correctly or not. But when i try to run the test i get this error message:
TypeError: Cannot read property 'searchForGames' of undefined
I have tried adding som mock data in searchForGames, but i cant get it to work:(
Here is my code:
search.tsx
import * as React from "react";
import { Component } from "react-simplified";
import { CardGroup, GameCard, Container, NavBar, SubHeader, Alert } from "./widgets";
import { igdbService, Game } from "./services";
export class Search extends Component<{
match: { params: { searchString: string } };
}> {
games: Game[] = [];
render() {
return (
<Container>
{this.games.length !== 0 ? (
<CardGroup>
{this.games.map((game) => {
return (
<div key={game.id} className="col">
<NavBar.Link to={"/games/" + game.id}>
<GameCard
name={game.name}
url={game.cover ? game.cover.url.replace("thumb", "cover_big") : ""}
/>
</NavBar.Link>
</div>
);
})}
</CardGroup>
) : (
<div className="d-flex flex-column justify-content-center">
<h4 className="mx-auto">The game you are looking for could not be found in the IGDB database..</h4>
<img className="w-40 mx-auto" alt="Sadge" src="https://c.tenor.com/kZ0XPsvtqw8AAAAi/cat-farsi-sad.gif"/>
</div>
)}
</Container>
);
}
mounted() {
this.getGames();
}
getGames() {
igdbService
.searchForGames(this.props.match.params.searchString)
.then((response) => (this.games = response))
}
}
search.test.tsx
import {Search} from "../src/Search"
import * as React from "react";
import { shallow } from 'enzyme';
import { NavBar, GameCard } from "../src/widgets";
jest.mock("../src/services", () => {
class Service {
searchForGames(string: searchString) {
return Promise.resolve(
[
{
id:143755,
cover: {
id:1311803,
url: '//images.igdb.com/igdb/image/upload/t_thumb/co2tp7.jpg'
},
name: "Terrorist Killer",
}
]
)
}
getGames() {
return Promise.resolve();
}
}
return new Service();
})
test("Search draws correctly", (done) => {
const wrapper = shallow (<Search match={{params: {searchString: "Terrorist"} }}/>)
setTimeout(() => {
expect(
wrapper.containsAllMatchingElements([
<div>
<NavBar.Link to="/games/143755">
<GameCard
name="Terrorist Killer"
url="//images.igdb.com/igdb/image/upload/t_thumb/co2tp7.jpg"
/>
</NavBar.Link>
</div>
])
).toEqual(true);
done();
})
})
Please I would be happy if anyone would help me
I have a problem, I can not use the server-side functions, I call the functions with axios, and execute it in react hooks.
I actually build chat, which is why I use react hook, because I want messages to be updated all the time.
I also use firestore. There I save the messages, and receive them through the server side function.
It's a component of the chat - it's causing me problems, I do not understand why.
The server side functions work great, I tested them in postman, and they worked. The problem is that I can't run them in a function component. I do not know what I'm doing wrong.
The error I get here is in the line chat.users.length> 0?, When I make this comparison I get that chat.users is undefined, but I do not understand why because I initialize it at first, using a server side function , Which gives the necessary information
I'm very confused, and I'm new here on the site, I'm trying to figure out why it has not worked for two whole days
I think I might be confused by syntax, for example using an unnecessary dispatch inside component of the chat
i got this error:
enter image description here
component of the chat
import React, { useEffect, useState } from 'react';
import './style.css';
import { useDispatch, useSelector } from 'react-redux';
import { getRealtimeUsers, updateMessage, getRealtimeConversations } from '../../redux/actions/chatActions';
import { Fragment } from 'react';
const User = (props) => {
const { chat, onClick } = props;
return (
<div onClick={() => onClick(chat)} className="displayName">
<div className="displayPic">
<img src="https://i.pinimg.com/originals/be/ac/96/beac96b8e13d2198fd4bb1d5ef56cdcf.jpg" alt="" />
</div>
<div style={{ display: 'flex', flex: 1, justifyContent: 'space-between', margin: '0 10px' }}>
<span style={{ fontWeight: 500 }}>{chat.firstName} {chat.lastName}</span>
<span className={chat.isOnline ? `onlineStatus` : `onlineStatus off`}></span>
</div>
</div>
);
}
const HomePage = (props) => {
const dispatch = useDispatch();
const user = useSelector(state => state.user.credentials);
const chat = useSelector(state => state.chat);
const [chatStarted, setChatStarted] = useState(false);
const [chatUser, setChatUser] = useState('');
const [message, setMessage] = useState('');
const [userUid, setUserUid] = useState(null);
let unsubscribe;
useEffect(() => {
//unsubscribe = dispatch(getRealtimeUsers(user.handle))
dispatch(getRealtimeUsers());
}, []);
//console.log(user);
//componentWillUnmount
useEffect(() => {
return () => {
//cleanup
//unsubscribe.then(f => f()).catch(error => console.log(error));
unsubscribe.then(f => f()).catch(error => console.log(error));
}
}, []);
//function
const initChat = (chat) => {
setChatStarted(true)
setChatUser(`${chat.firstName} ${chat.lastName}`)
setUserUid(chat.handle);
console.log(chat);
dispatch(getRealtimeConversations({ uid_1: user.handle, uid_2: chat.handle }));
}
const submitMessage = (e) => {
const msgObj = {
user_uid_1: user.handle,
user_uid_2: userUid,
message
}
if (message !== "") {
dispatch(updateMessage(msgObj))
.then(() => {
setMessage('')
});
}
//console.log(msgObj);
}
return (
<Fragment>
<section className="container">
<div className="listOfUsers">
{console.log(chat)}
{
//chat.users != undefined
chat.users.length > 0 ?
chat.users.map(user => {
return (
<User
onClick={initChat}
key={user.handle}
user={user}
/>
);
})
: null
}
</div>
<div className="chatArea">
<div className="chatHeader">
{
chatStarted ? chatUser : ''
}
</div>
<div className="messageSections">
{
chatStarted ?
chat.conversations.map(con =>
<div style={{ textAlign: con.user_uid_1 == user.handle ? 'right' : 'left' }}>
<p className="messageStyle" >{con.message}</p>
</div>)
: null
}
</div>
{
chatStarted ?
<div className="chatControls">
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Write Message"
/>
<button onClick={submitMessage}>Send</button>
</div> : null
}
</div>
</section>
</Fragment>
);
}
export default HomePage;
This is the axios:
app.get('/realtimeUsers', FBAuth, getRealtimeUsers );
app.post('/updateMessage', FBAuth, updateMessage);
app.get('/realtimeConversations', FBAuth, getRealtimeConversations);
And this is the server side functions - They work 100% - I checked them many times and they worked.:
const { db } = require('../util/admin');
exports.getRealtimeUsers = (req, res) => {
db.collection("users")
.onSnapshot((querySnapshot) => {
const users = [];
querySnapshot.forEach(function (doc) {
if (doc.data().handle != req.user.handle) {
users.push(doc.data());
}
});
return res.json(users);
});
}
exports.updateMessage = (req, res) => {
db.collection('conversations')
.add({
...req.body,
isView: false,
createdAt: new Date()
})
.then(() => {
return res.json({ message: "Conversations added successfully" });
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.code });
});
}
exports.getRealtimeConversations = (req, res) => {
console.log(JSON.stringify("testing"));
console.log(JSON.stringify(req.query));
console.log(JSON.parse(req.query.user));
console.log(JSON.parse(req.query.user).uid_1);
console.log(JSON.parse(req.query.user).uid_2);
db.collection('conversations')
.where('user_uid_1', 'in', [JSON.parse(req.query.user).uid_1, JSON.parse(req.query.user).uid_2])
.orderBy('createdAt', 'asc')
.onSnapshot((querySnapshot) => {
const conversations = [];
querySnapshot.forEach(doc => {
console.log(JSON.stringify(doc));
if (
(doc.data().user_uid_1 == JSON.parse(req.query.user).uid_1 && doc.data().user_uid_2 == JSON.parse(req.query.user).uid_2)
||
(doc.data().user_uid_1 == JSON.parse(req.query.user).uid_2 && doc.data().user_uid_2 == JSON.parse(req.query.user).uid_1)
) {
conversations.push(doc.data())
}
});
console.log(conversations);
return res.json(conversations);
})
//return res.json([]);
}
this is the actions that used in the client side, here i call to the axios:
import { userConstants } from "../types";
import axios from 'axios';
export const getRealtimeUsers = () => (dispatch) => {
dispatch({ type: `${userConstants.GET_REALTIME_USERS}_REQUEST` });
axios
.get('/realtimeUsers')
.then((res) => {
console.log(res);
dispatch({
type: `${userConstants.GET_REALTIME_USERS}_SUCCESS`,
payload: res.data
});
})
.catch((err) => console.log(err))
}
export const updateMessage = (msgObj) => (dispatch) => {
axios.post('/updateMessage', msgObj)
.then(() => { })
.catch((err) => console.log(err));
}
export const getRealtimeConversations = (user) => (dispatch) => {
//user = { uid_1: "from visualcode", uid_2: "userUid" };
console.log(JSON.stringify(user));
axios.get('/realtimeConversations',
{
params: {
user: JSON.stringify(user)
//uid_1:JSON.stringify("user.handle"),
//uid_2:JSON.stringify("userUid")
}
}
)
.then((res) => {
dispatch({
type: userConstants.GET_REALTIME_MESSAGES,
payload: res.data
});
})
.catch((err) => console.log(err))
}
I am not able to understand your whole code flow, i.e., how the chat.users will be populated before initChat is called.
But still, for your problem, you should always put a check for undefined values while iterating through an array.
<div className="listOfUsers">
{console.log(chat)}
{
//chat.users != undefined
chat && chat.users && chat.users.length > 0 &&
chat.users.map(user => {
return (
<User
onClick={initChat}
key={user.handle}
user={user}
/>
);
})
}
</div>
I'm looking for a way to be able to open the drawer, with the least possible re-rendering of the DOM.
Every time the drawer opens, a re-rendering of the route component occurs, however I think it is unnecessary since the child components have not changed and are only re-rendered due to the fact that I am updating the state to open the drawer.
Aprecio su ayuda y orientacion con este tema
This is my code for Appbar.js
import React, { useState, Fragment, lazy, Suspense, useCallback} from "react";
import { Switch,Router, Route, Link, useLocation } from "react-router-dom";
import { createBrowserHistory } from "history";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import IconButton from "#material-ui/core/IconButton";
import MenuIcon from "#material-ui/icons/Menu";
import logo_LH_webp from '../../imagenes/logo.webp';
import logo_LH_png from '../../imagenes/logo.png';
import AccountCircle from '#material-ui/icons/AccountCircle';
import { useSelector } from 'react-redux'
import { isEmpty } from 'react-redux-firebase'
import { useFirebase } from "react-redux-firebase";
import { store } from '../../index'
import { setMesa } from "../../slices/cart";
import axios from 'axios';
import {MemoMyDrawer} from './MyDrawer'
import Button from '#material-ui/core/Button';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import PopupState, { bindTrigger, bindMenu } from 'material-ui-popup-state';
const drawerWidth = 240;
const history = createBrowserHistory();
const styles = theme => ({
root: {
flexGrow: 1
},
flex: {
flex: 1
},
drawerPaper: {
position: "relative",
width: drawerWidth
},
menuButton: {
marginLeft: "auto",
},
toolbarMargin: theme.mixins.toolbar,
aboveDrawer: {
zIndex: theme.zIndex.drawer + 1
},
appBar: {
backgroundColor: "white"
}
});
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
const menuId = 'primary-search-account-menu';
const getQuery = () => {
if (typeof window !== "undefined") {
return new URLSearchParams(window.location.search);
}
return new URLSearchParams();
};
function AppBarInteraction({ classes, variant }) {
const query = getQuery();
console.log("MESA")
console.log()
const cart = useSelector(state => state.cart);
let valor = getQuery().get('q');
if (cart.mesa === "")
{
store.dispatch(setMesa(valor));
}
else
{
valor = cart.mesa;
}
const auth = useSelector(state => state.firebase.auth);
console.log('Auth vacio')
console.log(isEmpty(auth))
console.log(auth.isEmpty);
const [drawer, setDrawer] = useState(false);
const firebase = useFirebase();
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
console.log("usuario");
console.log(user);
//setAutorizado(true);
const token = await user.getIdToken();
axios.defaults.headers.common['Authorization'] = "Bearer " + token;
} else {
console.log("No logueado");
//setAutorizado(false);
}
});
// const [autorizado, setAutorizado] = React.useState(null);
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
console.log('Autorizado?');
//console.log(autorizado);
const toggleDrawer = useCallback(() => {
console.log(drawer)
setDrawer(!drawer);
}, [drawer]);
const logout = () => {
//setAutorizado(false);
firebase.logout();
}
const perfil = () => {
history.push("/perfil");
}
const MemoMyDraw = (drawer) => (
<MemoMyDrawer
open={drawer}
variant="temporary"
onClose={toggleDrawer}
toggleDrawer={toggleDrawer}
/>
)
const MenuPropio = () => (
// <Menu
// id="menu-appbar"
// anchorEl={anchorEl}
// open={open}
// onClose={handleClose}
// >
// <MenuItem onClick={perfil}>Perfil de Usuario</MenuItem>
// <MenuItem onClick={logout}>Cerrar Sesión</MenuItem>
// </Menu>
<PopupState variant="popover" popupId="demo-popup-menu">
{(popupState) => (
<React.Fragment>
<IconButton
aria-label="account of current user"
aria-controls="menu-appbar"
aria-haspopup="true"
color="primary"
{...bindTrigger(popupState)}
>
<AccountCircle />
</IconButton>
<Menu {...bindMenu(popupState)}>
<MenuItem onClick={perfil}>Perfil de Usuario</MenuItem>
<MenuItem onClick={logout}>Cerrar Sesión</MenuItem>
</Menu>
</React.Fragment>
)}
</PopupState>
)
return (
<div className={classes.root}>
<Fragment>
<Router history={history}>
<AppBar position="fixed" className={classes.appBar}>
<Toolbar>
<Link to={"/" + "?q=" + valor} ><picture><source srcSet={logo_LH_webp} type="image/webp" /><img alt="logo" srcSet={logo_LH_png} height="56" width="77" /></picture></Link>
<IconButton
color="default"
aria-label="open drawer"
edge="end"
onClick={toggleDrawer} //{handleDrawerToggle}
className={classes.menuButton}
>
<MenuIcon />
</IconButton>
{true && (
<Fragment>
<div>
<MenuPropio />
</div>
</Fragment>
)}
</Toolbar>
</AppBar>
</Router>
<div className={classes.toolbarMargin} />
</Fragment>
{MemoMyDraw(drawer)}
</div>
);
}
export default React.memo(withStyles(styles)(AppBarInteraction));
And this is the code for MyDrawer.js
import React, { Suspense, lazy} from "react";
import { withStyles } from "#material-ui/core/styles";
import Drawer from "#material-ui/core/Drawer";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import PrivateRoute from "../PrivateRoute/PrivateRoute"
import clsx from "clsx";
import Divider from "#material-ui/core/Divider";
import { Switch,Router, Route, Link, useLocation } from "react-router-dom";
import ListItemIcon from "#material-ui/core/ListItemIcon";
import InboxIcon from "#material-ui/icons/MoveToInbox";
import MailIcon from "#material-ui/icons/Mail";
import { createBrowserHistory } from "history";
import { useSelector } from 'react-redux'
import { isLoaded, isEmpty } from 'react-redux-firebase'
import Prueba from '../Prueba/Prueba';
const Cart = lazy(() => import ('../Cart/Cart'));
const Feedback = lazy(() => import ('../Feedback/Feedback'));
const Perfil = lazy(() => import ('../Perfil/Perfil'));
const OneClick = lazy(() => import ('../OneClick/OneClick'));
const Login = lazy(() => import ('../Login/Login'));
const Menu_LH = lazy(() => import ('../Menu/Menu'));
const Principal = lazy(() => import('../Principal/Principal'));
const ProductList = lazy(() => import( '../ProductList/ProductList'));
const Carta= lazy(() => import( '../Carta/Carta'));
const ProductosCategoria = lazy(() => import('../ProductosCategoria/ProductosCategoria'));
const InfoProducto = lazy(() => import('../InfoProducto/InfoProducto'));
const Confirmacion = lazy(() => import('../Confirmacion/Confirmacion'));
const drawerWidth = 240;
const history = createBrowserHistory();
function AuthIsLoaded({ children }) {
const auth = useSelector(state => state.firebase.auth)
if (!isLoaded(auth)) return <div></div>;
return children
}
console.log("DRAWER----------------------------")
const getQuery = () => {
if (typeof window !== "undefined") {
return new URLSearchParams(window.location.search);
}
return new URLSearchParams();
};
function switchResult(a){
switch(a){
case 'Tu Pedido Actual':
return 'pedido';
break;
case 'Carta':
return 'carta';
break;
case 'Inicio':
return '';
break;
case 'Carta':
return 'Carta';
break;
case 'Prueba':
return 'Prueba';
break;
case 'Comentarios':
return 'feedback';
break;
default:
return "OK";
}
}
const styles = theme => ({
root: {
flexGrow: 1
},
flex: {
flex: 1
},
drawerPaper: {
position: "relative",
width: drawerWidth
},
menuButton: {
marginLeft: "auto",
},
toolbarMargin: theme.mixins.toolbar,
aboveDrawer: {
zIndex: theme.zIndex.drawer + 1
},
appBar: {
backgroundColor: "white"
}
});
const MyDrawer = withStyles(styles)(
({ classes, variant, open, onClose, toggleDrawer }) => (
<Router history={history}>
<Drawer
variant="temporary"
open={open}
onClose={onClose}
classes={{
paper: classes.drawerPaper
}}
>
<div
className={clsx({
[classes.toolbarMargin]: variant === "persistent"
})}
/>
<Divider />
<List>
{["Inicio", "Carta" , "Tu Pedido Actual", "Comentarios" ].map((text, index) => (
<ListItem key={text} onClick={toggleDrawer} component={Link} to={"/" + switchResult(text) + "?q=" + getQuery().get('q')}>
<ListItemIcon>
{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}
</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
<main className={classes.content}>
<Suspense fallback={<div>Cargando...</div>}>
<Switch>
<AuthIsLoaded>
<PrivateRoute exact path = "/" component={Principal}></PrivateRoute>
<PrivateRoute exact path="/perfil" component={Perfil}></PrivateRoute>
<PrivateRoute exact path="/OneClick"component={OneClick}></PrivateRoute>
{/* <Route exact path="/Menu" render={() => <div id="G"><ProductList /></div>} /> */}
<PrivateRoute path="/carta"component={Carta}></PrivateRoute>
<PrivateRoute path="/pedido"component={Cart}></PrivateRoute>
{/* <Route path="/carta" render={() => <div id="G"><ProductList /></div>} /> */}
{/* <PrivateRoute exact path="/"><div id="G"><Principal /></div></PrivateRoute> */}
<Route exact path="/Categoria/:categoriaId"><div id="ProductosCategoria"><ProductosCategoria /></div></Route>
<Route exact path="/login"><div id="Login"><Login /></div></Route>
<Route exact path="/feedback" > <div id="Feedback"><Feedback /></div></Route>
</AuthIsLoaded>
</Switch>
</Suspense>
</main>
</Router>
)
);
export const MemoMyDrawer = React.memo(withStyles(styles)(MyDrawer));
You have to separate the reference to drawer's state open from MyDrawer because when open chage re-render MyDrawer. My solution was to extends Drawer and manage its state with redux using this code:
import { useSelector, useDispatch } from "react-redux";
function VDrawer(props){
const dispatch = useDispatch();
const onClose=() => {dispatch({ type: "drawer"})};
const drawer = useSelector((state) => state.drawer);
return <Drawer variant="temporary"
open={drawer}
onClose={onClose}
ModalProps={{
keepMounted: true,
}}>
{props.children}
</Drawer>
}
In this way the state drawer wich open/close drawer don't affect to MyDrawer
I'm doing a simple tutorial about React and Node, by building a CRUD app.
TypeError: Cannot read property 'length' of undefined
Error in this file: PostsManager.js:102
return (
<Fragment>
<Typography variant="display1">Posts Manager</Typography>
{this.state.posts.length > 0 ? (
<Paper elevation={1} className={classes.posts}>
<List>
This is my file PostsManager.js, it come from this tutorial https://developer.okta.com/blog/2018/07/10/build-a-basic-crud-app-with-node-and-react
import React, { Component, Fragment } from 'react';
import { withAuth } from '#okta/okta-react';
import { withRouter, Route, Redirect, Link } from 'react-router-dom';
import {
withStyles,
Typography,
Button,
IconButton,
Paper,
List,
ListItem,
ListItemText,
ListItemSecondaryAction,
} from '#material-ui/core';
import { Delete as DeleteIcon, Add as AddIcon } from '#material-ui/icons';
import moment from 'moment';
import { find, orderBy } from 'lodash';
import { compose } from 'recompose';
import PostEditor from '../components/PostEditor';
const styles = theme => ({
posts: {
marginTop: 2 * theme.spacing.unit,
},
fab: {
position: 'absolute',
bottom: 3 * theme.spacing.unit,
right: 3 * theme.spacing.unit,
[theme.breakpoints.down('xs')]: {
bottom: 2 * theme.spacing.unit,
right: 2 * theme.spacing.unit,
},
},
});
const API = process.env.REACT_APP_API || 'http://localhost:3001';
class PostsManager extends Component {
state = {
loading: true,
posts: [],
};
componentDidMount() {
this.getPosts();
}
async fetch(method, endpoint, body) {
try {
const response = await fetch(`${API}${endpoint}`, {
method,
body: body && JSON.stringify(body),
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization: `Bearer ${await this.props.auth.getAccessToken()}`,
},
});
return await response.json();
} catch (error) {
console.error(error);
}
}
async getPosts() {
this.setState({ loading: false, posts: await this.fetch('get', '/posts') });
}
savePost = async (post) => {
if (post.id) {
await this.fetch('put', `/posts/${post.id}`, post);
} else {
await this.fetch('post', '/posts', post);
}
this.props.history.goBack();
this.getPosts();
}
async deletePost(post) {
if (window.confirm(`Are you sure you want to delete "${post.title}"`)) {
await this.fetch('delete', `/posts/${post.id}`);
this.getPosts();
}
}
renderPostEditor = ({ match: { params: { id } } }) => {
if (this.state.loading) return null;
const post = find(this.state.posts, { id: Number(id) });
if (!post && id !== 'new') return <Redirect to="/posts" />;
return <PostEditor post={post} onSave={this.savePost} />;
};
render() {
const { classes } = this.props;
return (
<Fragment>
<Typography variant="display1">Posts Manager</Typography>
{this.state.posts.length > 0 ? (
<Paper elevation={1} className={classes.posts}>
<List>
{orderBy(this.state.posts, ['updatedAt', 'title'], ['desc', 'asc']).map(post => (
<ListItem key={post.id} button component={Link} to={`/posts/${post.id}`}>
<ListItemText
primary={post.title}
secondary={post.updatedAt && `Updated ${moment(post.updatedAt).fromNow()}`}
/>
<ListItemSecondaryAction>
<IconButton onClick={() => this.deletePost(post)} color="inherit">
<DeleteIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
</Paper>
) : (
!this.state.loading && <Typography variant="subheading">No posts to display</Typography>
)}
<Button
variant="fab"
color="secondary"
aria-label="add"
className={classes.fab}
component={Link}
to="/posts/new"
>
<AddIcon />
</Button>
<Route exact path="/posts/:id" render={this.renderPostEditor} />
</Fragment>
);
}
}
export default compose(
withAuth,
withRouter,
withStyles(styles),
)(PostsManager);
Try to replace
state = {
loading: true,
posts: [],
};
With:
this.state = {
loading: true,
posts: [],
};
If you can get the length using a console.log(this.state.posts.lenght). But when you run the code if length is undefined. Try this one,
return (
<Fragment>
<Typography variant="display1">Posts Manager</Typography>
{ (this.state && this.state.posts && this.state.posts.length) > 0 ? (
<Paper elevation={1} className={classes.posts}>
<List>