ReactJS: Is there a way to avoid child component re-rendering when drawer opens? - node.js

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

Related

Invalid hook call

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

Can't resolve 'react-transition-group/Transition' in 'C:\...;

I have an app which displays blogposts.
The app works perfectly without styles.
However, when I import a component from EITHER React Bootstrap OR material-ui. I get the following error message:
Can't resolve 'react-transition-group/Transition' in 'C:\Users\xxxxx\Documents\Web Development\bloglist-frontend\node_modules\react-bootstrap\esm'
How do you work around this message?
The App:
import { useState, useEffect } from "react";
import Blog from "./components/Blog";
import blogService from "./services/blogs";
import loginService from "./services/login";
import Notification from "./components/Notification";
import ErrorNotification from "./components/ErrorNotification";
import BlogForm from "./components/BlogForm";
import Togglable from "./components/Togglable";
import { useDispatch, useSelector } from 'react-redux'
import { setBlogs } from './reducers/blogReducer'
import User from './components/User'
import SingleBlog from './components/SingleBlog'
import styled from 'styled-components'
import LoginForm from './components/LoginForm'
import Blogs from './components/Blogs'
import Logout from './components/Logout'
import { Table } from 'react-bootstrap'
import {
BrowserRouter as Router,
Routes, Route, Link
} from "react-router-dom"
const Page = styled.div`
padding: 1em;
background: papayawhip;
`
const Navigation = styled.div`
background: BurlyWood;
padding: 1em;
`
const Footer = styled.div`
background: Chocolate;
padding: 1em;
margin-top: 1em;
`
const App = () => {
/* const [blogs, setBlogs] = useState([]); */
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [user, setUser] = useState(null);
const [errorMessage, setErrorMessage] = useState(null);
const [newAuthor, setNewAuthor] = useState("");
const [newTitle, setNewTitle] = useState("");
const [newUrl, setNewUrl] = useState("");
const [failureMessage, setFailureMessage] = useState(null);
const dispatch = useDispatch()
useEffect(() => {
blogService
.getAll().then(blogs => dispatch(setBlogs(blogs)))
}, [dispatch])
const blogs = useSelector(state => state)
/*
useEffect(() => {
blogService.getAll().then((blogs) => setBlogs(blogs));
}, []); */
useEffect(() => {
const loggedUserJSON = window.localStorage.getItem("loggedBlogappUser");
if (loggedUserJSON) {
const user = JSON.parse(loggedUserJSON);
setUser(user);
blogService.setToken(user.token);
}
}, []);
const handleAuthorChange = (event) => {
setNewAuthor(event.target.value);
};
const handleTitleChange = (event) => {
setNewTitle(event.target.value);
};
const handleUrlChange = (event) => {
setNewUrl(event.target.value);
};
const handleLogin = async (event) => {
event.preventDefault();
try {
const user = await loginService.login({
username,
password,
});
window.localStorage.setItem("loggedBlogappUser", JSON.stringify(user));
blogService.setToken(user.token);
setUser(user);
setUsername("");
setPassword("");
setErrorMessage(`${user.name} is now logged in!`);
setTimeout(() => {
setErrorMessage(null);
}, 5000);
} catch (exception) {
setFailureMessage("Wrong username or password");
setTimeout(() => {
setFailureMessage(null);
}, 5000);
setErrorMessage("Wrong credentials");
setTimeout(() => {
setErrorMessage(null);
}, 5000);
}
};
const handleLogout = () => {
window.localStorage.removeItem("loggedBlogappUser");
};
const padding = {
padding: 5
}
/* const createBlog = async () => {
try {
const blogObject = {
author: newAuthor,
title: newTitle,
url: newUrl,
likes: 0,
};
await blogService.create(blogObject);
setBlogs(blogs.concat(blogObject));
setNewTitle("");
setNewAuthor("");
setNewUrl("");
setErrorMessage(`${blogObject.title} has been added!`);
setTimeout(() => {
setErrorMessage(null);
}, 5000);
} catch (exception) {
setErrorMessage(`blog couldn't be added`);
}
};
*/
/* const handleLike = (id) => {
const blog = blogs.find((b) => b.id === id);
const changedBlog = { ...blog, likes: blog.likes + 1 };
blogService
.update(id, changedBlog)
.then((returnedBlog) => {
setBlogs(blogs.map((blog) => (blog.id !== id ? blog : returnedBlog)));
})
.catch((error) => {
alert(`the blog '${blog.content} was already deleted from the server`);
setBlogs(blogs.filter((b) => b.id !== id));
});
}; */
/* const handleDelete = (id) => {
const blog = blogs.find((b) => b.id === id);
if (window.confirm(`are you sure you want to remove ${blog.title}`)) {
blogService
.remove(id)
.then(setBlogs(blogs.filter((b) => b.id !== id)))
.catch((error) => {
alert(`${error}`);
});
} else {
alert("deletion cancelled");
}
};
*/
/* const arrayforSort = [...blogs] */
if (!blogs) {
return null
}
if (blogs.length > 1)
{
return (
<Router>
<Page>
<Navigation>
<div>
<Link style={padding} to="/">home</Link>
<Link style={padding} to="/blogform">blogform</Link>
<Link style={padding} to="/users">Users</Link>
<Link style={padding} to="/login">Login</Link>
</div>
</Navigation>
<Routes>
<Route path="/blogform" element={<BlogForm newAuthor={newAuthor}
newTitle={newTitle}
newUrl={newUrl}
handleAuthorChange={handleAuthorChange}
handleUrlChange={handleUrlChange}
handleTitleChange={handleTitleChange}
setErrorMessage={setErrorMessage}
blogs={blogs}/>} />
<Route path="/login" element={user === null ? (
<LoginForm handleLogin={handleLogin} setUsername={setUsername} setPassword={setPassword} username={username} password={password} />
) : (
<div>
<p>{user.name} logged-in</p>
<Logout handleLogout={handleLogout} />
</div>
)} />
<Route path="/" element={<Blogs blogs={blogs} />} />
<Route path="/users" element={<User/>} />
</Routes>
<div>
<Notification message={errorMessage} />
<ErrorNotification message={failureMessage} />
<Togglable buttonLabel="new blog">
<BlogForm
newAuthor={newAuthor}
newTitle={newTitle}
newUrl={newUrl}
handleAuthorChange={handleAuthorChange}
handleUrlChange={handleUrlChange}
handleTitleChange={handleTitleChange}
setErrorMessage={setErrorMessage}
blogs={blogs}
/>
</Togglable>
<div>
</div>
</div>
</Page>
</Router>
);
} else {
return (
<SingleBlog blogs={blogs} setBlogs={setBlogs}/>
)
}
};
export default App;

Not gettting all the data from the MongoDB

I have a route /courses and when I do search for courses from the frontend(ReactJs) it takes me to this URL http://localhost:3001/courses/search?searchQuery=introducton but when I again click on courses button(that is on my navbar), it takes me to /courses but It doesn't show all the courses, it only shows the searched query that I got from the above query. Every time I need to refresh the page to get all the courses.
Courses.js file
import React, { useState } from "react"
import CourseHeader from "./CourseHeader"
import CoursesList from "./CoursesList"
import FilterCourse from "./FilterCourse"
import Filtertech from "./Filtertech"
import { useDispatch, useSelector } from "react-redux"
import { useHistory } from "react-router"
import { getCoursesBySearch } from "../../actions/courses"
const Courses = () => {
const dispatch = useDispatch()
const [search, setSearch] = useState("")
const history = useHistory()
const searchCourses = () => {
if (search.trim()) {
dispatch(getCoursesBySearch({ search }))
history.push(`/courses/search?searchQuery=${search || "none"}`)
} else {
history.push("/courses")
}
}
const handleKeyPress = (e) => {
if (e.keyCode === 13) {
searchCourses()
}
}
const courses = useSelector((state) => state.courses)
return (
<div className="row-start-2 row-end-3 col-start-1 col-end-2 mx-auto w-full max-w-5xl ">
<div className="grid grid-cols-layout-course gap-x-3">
<div>
<FilterCourse />
<Filtertech />
</div>
<div>
<CourseHeader
handleKeyPress={handleKeyPress}
setSearch={setSearch}
search={search}
/>
<CoursesList courses={courses} />
</div>
</div>
</div>
)
}
export default Courses
CourseList.js
import React from "react"
import CourseCard from "./CourseCard"
const CoursesList = ({ courses }) => {
return (
<div className="">
{courses && courses.map((course) => <CourseCard data={course} key={course._id} />)}
</div>
)
}
export default CoursesList
App.js
import "./App.css"
import "./assets/output.css"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"
import Navbar from "./components/layouts/Navbar"
import Courses from "./components/courses/Courses"
import { useEffect } from "react"
import { getCourses } from "./actions/courses"
function App() {
const dispatch = useDispatch()
useEffect(() => {
dispatch(getCourses())
}, [dispatch])
return (
<Router>
<div className="App min-h-screen grid grid-rows-layout-desktop grid-cols-layout-desktop gap-10">
<Navbar />
<Switch>
<Route path="/courses">
<Courses />
</Route>
</Switch>
<Footer />
</div>
</Router>
)
}
export default App
Anyone please help me with this

Is there any way to stop the useEffect infinite loop even after provided with second empty array argument?

I am try to call dispatch method from useEffect hook in my React app but after rendering the useEffect continue looping.
here is my code....
category.actions.js
here is the actions perform
import axios from "../../apiFetch/axios";
import { categoryActions } from './constants';
export const getCategories = () => {
return async (dispatch) => {
dispatch({ type: categoryActions.GET_CATEGORY_ACTION})
const res = await axios.get('/category/getCategories');
console.log(res)
if(res.status === 200){
const {categoryList} = res.data;
dispatch({
type: categoryActions.GET_CATEGORY_SUCCESS,
payload: {
categories: categoryList
}
});
}else{
dispatch({
type: categoryActions.GET_CATEGORY_FAILURE,
payload: {
error: res.data.error
}
});
}
}
}
category.reducer.js
this is reducer code
import { categoryActions } from "../actions/constants";
const initialState = {
laoding: false,
categoryList: [],
error: null
}
const categoryReducer = (state = initialState, action) => {
switch(action.type){
case categoryActions.GET_CATEGORY_ACTION:
return{
loading: true
};
case categoryActions.GET_CATEGORY_SUCCESS:
return{
loading: false,
categoryList: action.payload.categories
};
case categoryActions.GET_CATEGORY_FAILURE:
return{
loading: false,
error: action.payload.error
}
default:
return{
...initialState
}
}
}
export default categoryReducer;
Category components where I used useEffect hooks
import Layout from '../../components/Layout';
import {Container, Row, Col} from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux';
import { getCategories } from '../../redux/actions';
const Category = () => {
const dispatch = useDispatch();
const state = useSelector(state => state.categoryReducer)
useEffect(() => {
dispatch(getCategories());
},[]);
return (
<Layout sidebar>
<Container>
<Row>
<Col>
<div style={{display: 'flex', justifyContent: 'space-between'}}>
<h3>Category</h3>
<button>Add</button>
</div>
</Col>
</Row>
</Container>
</Layout>
)
}
export default Category;
constant.js
here is the constant i used as type
export const categoryActions = {
GET_CATEGORY_ACTION: 'GET_CATEGORY_ACTION',
GET_CATEGORY_SUCCESS: 'GET_CATEGORY_SUCCESS',
GET_CATEGORY_FAILURE: 'GET_CATEGORY_FAILURE'
}
here is index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import store from './redux/store';
import { BrowserRouter as Router } from 'react-router-dom';
window.store = store;
ReactDOM.render(
<Provider store={store}>
<Router>
<React.StrictMode>
<App />
</React.StrictMode>
</Router>
</Provider>,
document.getElementById('root')
);
reportWebVitals();
here is App.js parent component
import Signin from './containers/Signin';
import Signup from './containers/Signup';
import PrivateRoute from './components/HOC/PrivateRoute';
import {useDispatch, useSelector} from 'react-redux';
import { isUserLogin } from './redux/actions';
import Products from './containers/Products';
import Orders from './containers/Orders';
import Category from './containers/Category';
function App() {
const auth = useSelector(state => state.authReducer);
const dispatch = useDispatch();
useEffect(() => {
if(!auth.authenticate){
dispatch(isUserLogin());
}
}, [])
return (
<div className="App">
<Switch>
<PrivateRoute path="/" exact component={Home} />
<PrivateRoute path="/products" component={Products} />
<PrivateRoute path="/orders" component={Orders} />
<PrivateRoute path="/category" component={Category} />
<Route path="/signup" component={Signup} />
<Route path="/signin" component={Signin} />
</Switch>
</div>
);
}
export default App;

React Hooks - How to pass props from child to parent component

In the bellow example, how can I pass imageAsUrl.imgUrl from the Child component (ImageUpload) to the parent component (UpdateCard).
CHILD Component
import React, { useState, useEffect } from 'react';
import { storage } from '../firebase';
const ImageUpload = () => {
const allInputs = { imgUrl: '' };
const [imageAsUrl, setImageAsUrl] = useState(allInputs);
const [image, setImage] = useState(null);
const handleChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0]);
}
};
useEffect(() => {
if (image) {
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
'state_changed',
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref('images')
.child(image.name)
.getDownloadURL()
.then((fireBaseUrl) => {
setImageAsUrl((prevObject) => ({
...prevObject,
imgUrl: fireBaseUrl,
}));
});
}
);
}
}, [image]);
return (
<>
<label className='custom-file-upload'>
<input type='file' onChange={handleChange} />
</label>
<img src={imageAsUrl.imgUrl} alt='sample' />
</>
);
};
export default ImageUpload;
PARENT Component
import React, { useState } from 'react';
import firebase from '../firebase';
import ImageUpload from './ImageUpload';
const UpdateCard = ({ card }) => {
const [originalText, setOriginalText] = useState(card.originalText);
const [translatedText, setTranslatedText] = useState(card.translatedText);
const onUpdate = () => {
const db = firebase.firestore();
db.collection('FlashCards')
.doc(card.id)
.set({ ...card, originalText, translatedText });
timeOutScroll();
};
return (
<>
<div>
{card.imageURL ? (
<img src={card.imageURL} alt='' className='img' />
) : (
<textarea
className='upload-textarea'
value={originalText}
onChange={(e) => {
setOriginalText(e.target.value);
}}
/>
)}
<ImageUpload />
</div>
<textarea
value={translatedText}
onChange={(e) => {
setTranslatedText(e.target.value);
}}
/>
<button onClick={onUpdate}>Update</button>
</>
);
};
export default UpdateCard;
Inside parent,You can define a callback function as prop ref to be called inside the child.
const ImageUpload = ({getURLtoParent}) =>{ <--------------------
const [imageAsUrl, setImageAsUrl] = useState(allInputs);
useEffect(() => {
uploadTask.on(
..............
...
);
if(imageAsUrl.imgUrl !== '')
getURLtoParent(imageAsUrl.imgUrl) <-----------------------
},[image])
}
const UpdateCart = () => {
const[imgURL,setimgURL] = useState(null)
return (
......
<ImageUpload getURLtoParent={ (url) => setimgURL(url) } /> <----------------
.........
)
}

Resources