I was trying to implement a navbar functionality in my web app which should be switching the information rendered based on a boolean residing in my redux status. Now, when the user is not logged in, the object fetched using my used selector is an empty one which means that is a truthy value hence not enabling me to toggle the element on the navabr as i wish. Is there a way to do that without modifying my redux status ?
Thank you in advance.
import './App.css';
import { Route, Routes, Link, Redirect, Navigate } from "react-router-dom";
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import SearchBar from "./components/searchBar";
import Main from './components/main';
import Login from './components/login';
import Register from './components/register';
let linkStyle = { textDecoration: "none", color: "white" };
function App() {
// fetching redux status
let user = useSelector(state => state.loginStatus.user)
let loginStatus = useSelector(state => state.loginStatus.isLoggedIn)
console.log(loginStatus)
const [isActive, setIsActive] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [avatar, setAvatar] = useState({});
useEffect(() => {
if (loginStatus) {
setIsLoggedIn(loginStatus)
setAvatar(user)
}
}, [loginStatus, isLoggedIn, isActive, avatar])
return (
<div className="App">
<nav className="navbar">
<div className="logo"><Link to={'/'} onClick={() => setIsActive(false)}><img src='https://cdn-icons-png.flaticon.com/512/201/201623.png' /></Link></div>
<h1 className="title">Travel.com</h1>
{
isLoggedIn && (<ul className='menu'>
<li className="link"><Link style={linkStyle} to='/login' onClick={() => setIsActive(true)}>Log-in</Link></li>
<li className="link"><Link style={linkStyle} to='/register' onClick={() => setIsActive(true)}>Register</Link></li>
</ul>)
}
{ /*
isLoggedIn && (
<div className="avatar">
<h6> Hi avatar.user_name!</h6>
</div>
) */
}
</nav>
If I understand your question correctly then a simple solution could be to check if the object is empty using something like: Boolean(Object.keys(logInStatus).length)
this will return false if logInStatus is an empty object, and true if it has any properties. You would pass this as the argument into setIsLoggedIn() in your useEffect().
You said you don't want to change the Redux store, however I would call into question the naming of the isLoggedIn property. By starting a property with is the name implies that it is a boolean value, or at the very least that it will coerse into a boolean value which corresponds to its name. This is something to watch out for as someone who doesn't know the codebase well would most likely end up making an assumption based on conventions, resulting in a situation like this with code behaving the opposite way to how you would expect.
Related
On the first image you can see next.js rendered this element twice
I used tables and thought that it is because of them but then I tried to remove tables and put jut and it still renders twice so I don't know what it can be.
Next.js does not renders only that element but the first from this object
const Sections = {
1: Locations,
0: Departments, // here it will render this one twice
2: Managers,
3: JobTitles,
};
Maybe it has something to do with useState and my statemanagment in this code below
Component that renders twice.
const Locations = () => {
return <div>hdjsad</div>;
};
// Tab Sections
import Locations from ''
import Departments from ''
import Managers from ''
import JobTitles from ''
import Icons from "../../Icons";
import TabItem from "./TabItem";
const tabs_text = ["Locations", "Departments", "Managers", "Job Titles"];
const Sections = {
0: Locations, // THIS IS THE COMPONENT WHICH RENDERS TWICE
1: Departments,
2: Managers,
3: JobTitles,
};
const SettingsTab = () => {
const [active, setActive] = useState<number>(0);
const select = useCallback((id: number) => {
return () => setActive(id);
}, []);
const ActiveSection = useMemo(() => Sections[active], [active]);
return (
<section className={"mb-[24px]"}>
<header
className={"w-full flex items-center mb-[34px] pl-[24px] pr-[12px]"}
>
<div className={"flex space-x-[8px] !mb-0 overflow-x-scroll"}>
{tabs_text.map((tab_text, i) => {
return (
<div onClick={select(i)} key={i}>
<TabItem active={+active === i}>{tab_text}</TabItem>
</div>
);
})}
</div>
<ImportLocationsAndFilter />
</header>
<ActiveSection />
</section>
);
};
APP.js
import { AppProps } from "next/app";
import "antd/dist/antd.css";
import "../styles/global.css";
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
I can't comment yet so I'll do it here. I know react says in the official docs to never rely on UseMemo or Use callback for functionality. It says you should create your application so it works without them, and then add them for performance reasons. What would happen if you took the useMemo out and put
ActiveSelection = Selections[active]
I don't think it'll fix your problem but it might give you more insight into what's causing it.
I just imported my tabs dynamically and set SSR: false.
It has to do something with next.js hydration.
https://nextjs.org/docs/advanced-features/dynamic-import
dynamic(
() => import(""),
{
ssr: false,
}
);
It's strange behaviour / bug related to next.js ssr to fix it wrap your Component in a div like this:
function MyApp({ Component, pageProps }: AppProps) {
return <div id=#root><Component {...pageProps} /></div>;
}
As a newbie in React, it seems that re-rendering of components is the thing not to do.
Therefore, for example, if I want to create a menu following this architecture :
App is parent of Menu, which have a map function which creates the MenuItem components
menu items come from a data source (here it's const data)
when I click on a MenuItem, it updates the state with the selected MenuItem value
for now it's fine, except that all the components are re-rendered (seen in the various console.log)
Here's the code :
App
import React, { useState} from "react"
import Menu from "./menu";
function App() {
const data = ["MenuItem1", "MenuItem2", "MenuItem3", "MenuItem4", "MenuItem5", "MenuItem6"]
const [selectedItem, setMenuItem] = useState(null)
const handleMenuItem = (menuItem) => {
setMenuItem(menuItem)
}
return (
<div className="App">
<Menu items = {data} handleMenuItem = {handleMenuItem}></Menu>
<div>{selectedItem}</div>
</div>
);
}
export default App;
Menu
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
return (
<>
{props.items.map((item, index) => {
return <MenuItem key = {index} handleMenuItem = {props.handleMenuItem} value = {item}></MenuItem>
})
}
{console.log("menuItem")}
</>
)
};
export default React.memo(Menu);
MenuItem
import React from "react";
const MenuItem = (props) => {
return (
<>
<div onClick={() => props.handleMenuItem(props.value)}>
<p>{props.value}</p>
</div>
{console.log("render du MenuItem")}
</>
)
};
export default React.memo(MenuItem);
as you might see, I've used the React.memo in the end of MenuItem but it does not work, as well as the PureComponent
If someone has an idea, that'd be great to have some advice.
Have a great day
Wrap your handleMenuItem function with useCallback to avoid rerendering when the function changes. This will create a single function reference that will be used in the MenuItem as props and will avoid rereading since it's the same function instance always.
I have used an empty dependency array in this case which is correct for your use case. If your function has any state references then they should be added to the array.
const handleMenuItem = useCallback((menuItem) => {
setMenuItem(menuItem);
}, []);
There's a lot to unpack here so let's get started.
The way hooks are designed to prevent re-rendering components unnecessarily is by making sure you use the same instance of any unchanged variables, most specifically for object, functions, and arrays. I say that because string, number, and boolean equality is simple 'abc' === 'abc' resolves to true, but [] === [] would be false, as those are two DIFFERENT empty arrays being compared, and equality in JS for objects and functions and arrays only returns true when the two sides being compared are the exact same item.
That said, react provides ways to cache values and only update them (by creating new instances) when they need to be updated (because their dependencies change). Let's start with your app.js
import React, {useState, useCallback} from "react"
import Menu from "./menu";
// move this out of the function so that a new copy isn't created every time
// the App component re-renders
const data = ["MenuItem1", "MenuItem2", "MenuItem3", "MenuItem4", "MenuItem5", "MenuItem6"]
function App() {
const [selectedItem, setMenuItem] = useState(null);
// cache this with useCallback. The second parameter (the dependency
// array) is an empty array because there are no items that, should they
// change, we should create a new copy. That is to say we should never
// need to make a new copy because we have no dependencies that could
// change. This will now be the same instance of the same function each
// re-render.
const handleMenuItem = useCallback((menuItem) => setMenuItem(menuItem), []);
return (
<div className="App">
<Menu items={data} handleMenuItem={handleMenuItem}></Menu>
<div>{selectedItem}</div>
</div>
);
}
export default App;
Previously, handleMenuItem was set to a new copy of that function every time the App component was re-rendered, and data was also set to a new array (with the same entries) on each re-render. This would cause the child component (Menu) to re-render each time App was re-rendered. We don't want that. We only want child components to re-render if ABSOLUTELY necessary.
Next is the Menu component. There are pretty much no changes here, although I would urge you not to put spaces around your = within your JSX (key={index} not key = {index}.
import React from "react";
import MenuItem from "./menuItem";
const Menu = (props) => {
return (
<>
{props.items.map((item, index) => {
return <MenuItem key={index} handleMenuItem={props.handleMenuItem} value={item}/>
})
}
{console.log("menuItem")}
</>
)
};
export default React.memo(Menu);
For MenuItem, let's cache that click handler.
import React from "react";
const MenuItem = (props) => {
// cache this function
const handleClick = useCallback(() => props.handleMenuItem(props.value), [props.value]);
return (
<>
<div onClick={handleClick}>
<p>{props.value}</p>
</div>
{console.log("render du MenuItem")}
</>
)
};
export default React.memo(MenuItem);
I'm still a beginner in ReactJS and I'm creating a project that works with a list of pokemons. The user selects a type of pokemon, and then I must return a list according to the user's selection.
I have a list with all pokemons, but some pokemons can belong to more than one type, as shown in this example below:
Could you tell me how to create a list with only the type of pokemon that the user selected? I think I can do this using reducer(), but I have no idea how to do it.
Here's my code I put into codesandbox
import React from "react";
import { useHistory } from "react-router-dom";
import { Button } from "#material-ui/core";
import { types } from "./data";
import "./styles.css";
const App = () => {
const history = useHistory();
const handleType = (type) => {
history.push({
pathname: "/list",
state: type
});
};
return (
<div className="content">
<h3>Pokémon Types</h3>
{types.results.map((type) => (
<Button
key={type.name}
style={{
margin: "5px"
}}
variant="contained"
onClick={() => handleType(type.name)}
>
{type.name}
</Button>
))}
</div>
);
};
export default App;
import React from "react";
import { useLocation } from "react-router-dom";
import { pokemons } from "../data";
const List = () => {
const { state } = useLocation();
console.log("state: ", state);
console.log(pokemons);
return <div>List</div>;
};
export default List;
Thank you in advance for any help.
You have a lot of ways to do that, but since you are still learning and you got a nice shot of code, I will introduce useMemo for you:
you can add useMemo to memorize and process data, then get the result direct...
look at this example:
const pk = useMemo(() => {
if (!state) return "State Empty!";
let result = [];
pokemons.forEach((v, i) => {
if (v.type.includes(state)) {
result.push(<li key={v.name + i}>{v.name}</li>);
}
});
return result;
}, [pokemons, state]);
return <ul>{pk}</ul>;
By this code, I got your list, check details in a simple loop, and then retrieve the needed list...
Notes:
In key I set name and i, but it's not totally correct, but it seems there is duplication on data, and why its not totally correct?, since we need to make sure to prevent re-render when no real change, but index if order change that's mean re-render...
You can use anyway like reducer, filter, or create a separate component and put it nested of useMemo
You can enhance data style to can check or retrieve data fast by Hash table...
Demo
This is my code for LeftBar Component
I want to get the data stored in the "contacts" document in the firebase but getting an empty array. Idk know why this is happening. And one thing if anyone can tell me how to do it with class-based component ( how to use that use effect thing into ComponentDidMount and ComponentDidUpdate) as I have two other class Components which are also using the same functionality Please help
import React, {useState , useEffect} from "react";
import { Avatar, IconButton } from "#material-ui/core";
import AddCircleIcon from "#material-ui/icons/AddCircle";
import MoreIcon from "#material-ui/icons/More";
import ChatBubbleOutlineIcon from "#material-ui/icons/ChatBubbleOutline";
import SearchIcon from '#material-ui/icons/Search';
import LeftChats from './LeftChats';
import "./LeftBar.css";
import db from './firebase'
function LeftBar () {
const [contacts, setContacts] = useState([])
useEffect(() => {
db.collection("contacts").onSnapshot((snapshot)=> setContacts(
snapshot.docs.map((doc)=> ({
id : doc.id,
data : doc.data(),
}))
))
},[])
console.log(contacts);
return (
<div className="leftbar">
<div className="left-header">
<Avatar />
<div className="left-right-header">
<IconButton>
<AddCircleIcon />
</IconButton>
<IconButton>
<MoreIcon />
</IconButton>
<IconButton>
<ChatBubbleOutlineIcon />
</IconButton>
</div>
</div>
<div className="left-search">
<div className='input-container'>
<SearchIcon/>
<input type='text' placeholder='Search...'/>
</div>
</div>
<div className="left-chats">
{
contacts.map( contact =>(
<LeftChats key={contact.id} id = {contact.id} username=
{contact.data.name}/>
))
}
</div>
</div>
);
}
export default LeftBar;
This is my LeftChat Component
import React, { Component } from 'react'
import { Avatar} from "#material-ui/core";
class LeftChats extends Component {
constructor(props) {
super(props)
this.state = {
data : ''
}
}
render()
{
console.log(this.props)
return (
<div className='leftchats'>
<Avatar/>
<div className='chats-info'>
<h2>{this.props.username}</h2>
<p>Some text message...</p>
</div>
</div>
)
}
}
export default LeftChats
Using
<LeftChats key={contact.id} id = {contact.id} username=
{contact.data.name}/>
means that you are passing {contact.data.name} value to your custom LeftChats component. But your data prop is empty. Check your console to see whether you get data or not, since you've used console.log(this.props) inside <LeftChats />.
And for the sake of completness, you should import all from firebase, unless you know exactly what you'are doing:
import * as firebase from 'firebase';
import 'firebase/firestore';
Then have these configuration set appropriately:
const firebaseConfig = {
apiKey: "AIsdfghjzaSyC0ZHz8ooSoi05Vt2X7UL7qV9Aga1o", // use your own
authDomain: "dj-himmels.firebaseapp.com",
databaseURL: "https://dj-himmels.firebaseio.com",
projectId: "dj.himmels",
storageBucket: "dj-himmels.appspot.com",
messagingSenderId: "285437504174",
appId: "1:285566504174:web:0a9ttgb4a968acd08f7ff"
};
which is missing from your code.
In a real react native app for production, you should be using AsyncStorage to proceed your data.
Note: For your next questions try to ask one question at a time, it helps to figure out your real need and answer it accordingly. Right now you seem to be having many issues.
EDITED AFTER THE POSTER COMMENT
1- First of all, you are passing {contact.data.name} to username instead of {contacts.data.name}, because you used React hook useState to define contacts as const [contacts, setContacts] = useState([]) and not as const [contact, setContact] = useState([])
2- Second of all, if at all the
<LeftChats key={contact.id} id = {contact.id} username=
{contact.data.name}/>
component found the {contacts.data.name}, value to pass to username= contacts.data.name}, you will still get empty result because using useState([]) inside const [contacts, setContacts] = useState([]) means that you have set Contact value to empty array.
Nevertheless, you have tried to send value to Contact using React
useEffect hook like so useEffect(() => {
db.collection("contacts").onSnapshot((snapshot)=> setContacts(
snapshot.docs.map((doc)=> ({
id : doc.id,
data : doc.data(),
}))
))
},[]),
which was not triggered before component receives the initial empty array of contact. And your data value fetched from firebase may also be empty, that's why I meant by you are having many issues in the same time.
MY SUGGESTION:
1- Console.log the doc value to check whether it is empty or not. If so, then the problem is in the snapshot.docs.map((doc) otherwise it may be in the empty data inside the
this.state = {
data : ''
}
I make suggestions because I don't really know your real architecture. Also take a look at the react useEffect hooks here You may need to set a conditional value inside the [] of your
useEffect(() => {
},[x])
to reset Contact after that condition. Ultimately remember that there is still a great amount of value in sticking with functional components rather than going back to the class based components. Hope it help !
So, I have been investigating this bug for a while now and I think I got to the root cause of it. I have a navbar that changes if the user is logged in or not. On server render (I am using gatsby), this property is obviously always false meaning that I will get the guest navbar from the server on initial render.
I have the following hook for checking isLoggedIn:
import { useContext, useState, useEffect } from 'react';
import { UserContext } from '../contexts/UserContext';
import { isAuthenticated } from '../services/pilates-studio/localstorage';
const useIsLoggedIn = () => {
const { user } = useContext(UserContext);
const [isLoggedIn, setIsLoggedIn] = useState(isAuthenticated);
useEffect(() => {
console.log('we update isLoggedIn');
setIsLoggedIn(!!user || isAuthenticated);
}, [user]);
return isLoggedIn;
};
export default useIsLoggedIn;
isAuthenticated retrieves the access token from the localStorage, so as soon as we hydrate the application on the client, and the user is already logged in, isAuthenticated will result to true.
So the first state of isLoggedIn on the client side will be true. So we get the guest navbar form the server but since there is no state change ever, we are not rerendering the navbar even so technically the navbar state isLoggedIn has changed.
My navbar:
const NavBarContainer = () => {
const isLoggedIn = useIsLoggedIn();
return (
<NavBar logo={Logo} menu={NavMenu} isMenuOpen={isMenuOpen}>
<NavBarItem key="courses" label="Kurse" link={isLoggedIn ? '/courses' : '/courses/starter'} />
<NavBarItem key="appointment" label="Termine" link="/appointment" show={isLoggedIn} />
<NavBarItem key="trainers" label="Trainer" link="/trainers" />
<NavBarItem key="login" label="Login" link="/login" show={!isLoggedIn} floatRight />
<NavMenuButton key="menu" label="Menu" onClick={handleMenuClicked} icon={<UserIcon />} show={isLoggedIn} floatRight />
</NavBar>
);
};
export default NavBarContainer;
If I change the initial state of my hook to
const [isLoggedIn, setIsLoggedIn] = useState(false);
I get it to rerender but now I have a navbar that "blinks"/"flickers" on inital loading since it rerenders from guest to logged in navbar in a second. I would like to avoid that flickering.
I am quite new to SSR and gatsby. How would you solve the flickering? How can I have the inital state of isLoggedIn being set to true and still force a rerender?
Thanks for the support.