Next.js shared component (header) gets rerendered on new route - reactjs

Im trying to share a header component across all pages for my Next.js app but the header gets rerendered when I try to navigate between routes with Next's Link component. I have created a layout component which is supposed to render the header together with all the possible routes. Basically I don't want the header to get rerendered but visiting a different route using Next.js router it gets rerendered for some reason.
_app.tsx
function MyApp({ Component, pageProps }: AppProps) {
return <QueryClientProvider client={queryClient}>
<AuthProvider>
<LayoutMain>
<Component {...pageProps} />
</LayoutMain>
</AuthProvider>
</QueryClientProvider>
}
LayoutMain.tsx
(This is where I the <Component/> is provided from _app.tsx is supposed to get rendered together with the Header)
function LayoutMain({children}: {children: React.ReactNode}){
const {user} = useContext(AuthContext)
if(!user) return <Login/>
return <>
<Header/>
{children}
</>
}
Header.tsx
function Header() {
console.log('Header rendered')
const {user} = useContext(AuthContext)
return <div className="flex w-full border-b-[1px] border-opacity-25 border-black justify-center bg-white">
<div className="flex justify-between py-2 items-center w-[1000px] my-2 mx-4fvgd">
<Image className="cursor-pointer" objectFit="cover" src="/iglogo.svg" width="100" height="35"/>
<input className="border-[1px] hidden md:block bg-[#fafafa] border-gray-600 border-opacity-30 py-1 px-2 rounded-sm" type="text" placeholder="Search"/>
<div className="flex justify-center gap-5">
<HomeIcon className="cursor-pointer"/>
<SearchIcon className="cursor-pointer"/>
<SendOutlinedIcon className="cursor-pointer -rotate-45 -translate-y-1"/>
<AddCircleOutlineIcon className="cursor-pointer"/>
<FavoriteBorderIcon className="cursor-pointer"/>
<Link href={`/profile/${user?.id}`}>
<div ><Image className="rounded-full cursor-pointer" src="/weeknd.png" objectFit="cover" width="22" height="22"/></div>
</Link>
</div>
</div>
</div>
}

In the React framework, calling the component function is not necessarily rendering the HTML again. If you don't want the header function to get called or the vdom it returns to get diffed on page changes it would make sense to memo the header.
const HeaderMemo = React.memo(Header);
// in the layout
<HeaderMemo user={user} />

Related

ReactJS error is "This component must be used inside a <RecoilRoot> component."

But I'm twisting it slightly, where I don't have a layout but rather a per page component that I'd like to add to the header.
I am getting the following error:
Account.jsx looks something like this:
import { useRecoilValue } from "recoil";
import { userProfile } from "../../../recoil";
export default function Account() {
const profile = useRecoilValue(userProfile);
return (
<div className="w-screen h-full ">
<header>
<Navbar profile={dataMe} />
</header>
<main className="h-screen relative">
<div className='h-screen flex bg-gray-bg my-15 static'>
<div className='w-full mt-10 m-auto bg-white rounded-lg border'>
<div>
<div>
<dataMe />
</div>
<DetailAccount />
</div>
</div>
</div>
</main>
</div >
);
};
To use Recoil (A state management library for React) properly You have to add RecoilRoot wrap component(s). As we can read in documentation :
Components that use recoil state need RecoilRoot to appear somewhere
in the parent tree.
A good place to put this is in your root component
Example from official docs
import React from 'react';
import Account from './Account.jsx';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';
function App() {
return (
<RecoilRoot>
<ComponentsThatUseRecoilState />
{/* in Your case <Account /> component */}
</RecoilRoot>
);
}
You have to wrap your component(s) inside the RecoilRoot if you want the component to be using the Recoil functionality. As of documentations: https://recoiljs.org/docs/api-reference/core/RecoilRoot/
So you have to wrap your code inside <RecoilRoot> before you can use the useRecoilValue hook.
Example:
import {RecoilRoot} from 'recoil';
function AppRoot() {
return (
<RecoilRoot>
<ComponentThatUsesRecoil />
</RecoilRoot>
);
}
It's recommended to use this on root (App.tsx or what ever your root element is called) so all your other components can use the Recoil hooks as intended without having to wrap your components in a RecoilRoot every single time.

How to test a component in react using testing library depends on AppContext

I need to test a component(ServiceRequestDetails).That component has a layout that uses AppCOntext data.
While testing ServiceRequestDetails I got the error
TypeError: Cannot read properties of undefined (reading 'darkMode')..
ServiceRequestDetails.js Page. From here I tested text "Service Request Details".But I got an error.Because in the layout component I used Appcontext.
export function ServiceRequestDetails(props) {
const ctx = useContext(AppContext);
return (
<>
<Layout
page="servicerequestdetails"
title={navigationLinks()}
subTitle={pageSubTitle()}
showHelp={true}
helpText={"Help"}
helpIcon={<QuestionCircleOutlined />}
onHelpClick={() => setShowHelpModal(!showHelpModal)}
helpToggle={showHelpModal}
helpTemplate={<ViewServiceRequest />}
>
<div className="flex items-center">
<div className="pr-2">
<img src="/images/icons/services.svg" alt="filter" />
</div>
<h3 className="text-lg font-medium">Service Request Details</h3>
</div>
</Layout>
</>
)
}
ServiceRequestDetails.test.js.It is my test case for service request details page.
From here how to resolve AppCOntext issue.
test('Testing For title', () => {
render(
<Router>
<ServiceRequestDetails/>
</Router>
)
expect(screen.getByText("Service Request Details")).toBeInTheDocument();
})

Error: React Hook "useDocumentOnce" is called conditionally. React Hooks must be called in the exact same order in every component render

I was making an app using NextJS, TailwindCSS, Firebase and It is working 100% correctly on local host but on deploying on Vercel, I get the following error :
15:18 Error: React Hook "useRouter" is called conditionally. React Hooks must be called in the exact same order in every component render. react-hooks/rules-of-hooks
17:39 Error: React Hook "useDocumentOnce" is called conditionally. React Hooks must be called in the exact same order in every component render. react-hooks/rules-of-hooks
I am not able to understand why I get this error when deploying my app to Vercel.Maybe it's something related to redirect link in google API console
Here's my [id].js file :
import TextEditor from "../../components/TextEditor";
import Button from "#material-tailwind/react/Button";
import Icon from "#material-tailwind/react/Icon";
import { useRouter } from "next/dist/client/router";
import { db } from "../../firebase";
import { useDocumentOnce } from "react-firebase-hooks/firestore";
import { getSession, signOut, useSession } from "next-auth/client";
import Login from "../../components/Login";
function Doc() {
const [session, loading] = useSession();
console.log(session);
if (!session) return <Login />;
const router = useRouter();
const { id } = router.query;
const [snapshot, loadingSnapshot] = useDocumentOnce(
db.collection("userDocs").doc(session.user.email).collection("docs").doc(id)
);
// Redirect if user tries to access a URL they do not have access to...
if (!loadingSnapshot && !snapshot?.data()?.fileName) {
// Filename will not be present if the user doesnt have access...
router.replace("/");
}
return (
<div>
<header className="flex justify-between items-center p-3 pb-1">
<span onClick={() => router.push("/")} className="cursor-pointer">
<Icon name="description" size="5xl" color="blue" />
</span>
<div className="flex-grow px-2">
<h2 className="text-lg text-left">{snapshot?.data()?.fileName}</h2>
<div className="flex items-center text-sm -ml-1 h-8 text-gray-600 space-x-1">
<p className="option">File</p>
<p className="option">Edit</p>
<p className="option">View</p>
<p className="option">Insert</p>
<p className="option">Format</p>
<p className="option">Tools</p>
</div>
</div>
<Button
color="lightBlue"
buttonType="filled"
size="regular"
className="hidden md:!inline-flex h-10"
rounded={false}
block={false}
iconOnly={false}
ripple="light"
>
<Icon name="people" size="md" /> SHARE
</Button>
<img
onClick={signOut}
className="cursor-pointer h-10 w-10 rounded-full ml-2"
src={session.user.image}
alt=""
/>
</header>
<TextEditor />
</div>
);
}
export default Doc;
export async function getServerSideProps(context) {
const session = await getSession(context);
return {
props: {
session,
},
};
}
You have early return before calling useDocumentOnce on line
if (!session) return <Login />
Which is not good as the error says. You should make sure that you always render the same amount of hooks on each render. In your case you are calling 3 hooks. But if session is not defined you only render 1 hook. That is the problem you have to resolve.

Background doesn't resize in responsive tailwindcss

I'm busing tailwindcss for my css. So for this I defined the image inside the tailwind config. Then I applied a cover on it but the image doesn't scale when I change the size of the screen. I thought by adding max-w-full min-h-full would fixe the problem but nothing.
import React from 'react'
import { CountdownEvent } from './CountdownEvent'
import { CarouselSaf } from './CarouselSaf'
import { Navbar } from './Navbar'
export const Home = () => {
return (
<section className="h-screen">
<div className="bg-safthon bg-no-repeat bg-cover max-w-full min-h-full">
<Navbar />
<div className="flex h-screen items-center">
<CountdownEvent />
</div>
</div>
</section>
)
}
Try to keep it simple. Check this demo.
<div class="min-h-screen max-h-screen bg-cover bg-center bg-no-repeat"
style="background-image: url('https://i.ytimg.com/vi/odM92ap8_c0/maxresdefault.jpg')">
</div>

having problems with context

i understand context and the ability to pass state down without prop drilling (to me it honestly seems like the same amount of work... i was using redux for global state management and besides the boilerplate the ability to use reducers, action creators, AND the redux dev tools makes it seem way more useful to me)
even using useContext hook the amount of code required is really the same fricking thing as just prop drilling to me... but maybe i'm missing something
ALL that aside.. i am currently trying to have a simple nav bar feature where when a burger menu is opened the backdrop of the home page turns to a faded darker opacity. i don't want a library. i like to do this all myself.
this is my App file. i was assuming i could just pass the state down (open) which would indicate if the nav is open. BUT HOW can i change the state of open IF THE only thing i can pass is open and not setOpen... i cant have an onClick event on the entire Nav bar component. i need the onclick even to be in the Nav, but all that is being passed is the state itself. every time i think i understand react some new unique situation comes up where i run into similar problems with state
// components
import Nav from './components/Nav'
import Homepage from './components/Homepage'
import React, {useState, createContext} from "react";
import {
BrowserRouter as Router,
Route,
} from "react-router-dom";
// pages
import About from './components/pages/About'
import Stories from './components/pages/Stories'
import News from './components/pages/News'
import ThemeContext from './ThemeContext';
// imports
function App() {
const [open, setOpen]=useState(false)
return (
<>
<Router>
<ThemeContext.Provider value={open}>
<Nav />
<Route path='/' exact>
<Homepage />
</Route>
<Route path='/about' component={About} exact />
<Route path='/stories' component={Stories} exact />
<Route path='/news' component={News} exact />
</ThemeContext.Provider>
</Router>
</>
);
}
export default App;
here is my nav component
import React, {useEffect, useState, useContext, useRef} from 'react'
import '../styles/custom.css'
import MenuDrop from './MenuDrop.js'
import {Link} from 'react-router-dom'
import ThemeContext from '../ThemeContext.js'
const Nav = () => {
const [active, setActive]=useState(false)
const [touched, setTouched]=useState(false)
const myRef=useRef()
const open=useContext(ThemeContext)
const clicker=()=>{
if(window.innerWidth<450){
setActive(!active);
}
}
useEffect(()=>{
console.log(open)
},[active])
const toucher=()=>{
setTouched(true);
}
const toucherOff=()=>{
setTouched(false);
}
return (
/* NAVBAR */
<>
<div className={`shadow-xl ${open?'dark':'light'}`}>
<div className='nav c overflow-hidden w-full nav-main md:pt-8 pt-4 flex justify-between md:justify-around items-center flex-row flex-shadow-2xl'>
<h1 className=' mx-2 antialiased text-lg md:text-3xl font-bold text-green-700 font-second-bold'>HUNTER SOLICITERS</h1>
<div className={`${active?'nav-items-active':'nav-items'} bg-white items-center md:items-center flex flex-col justify-center md:flex-row md:justify-around list-none w-1/3 flex-shadow-2xl`}>
<Link onClick={()=>setActive(false)} to='/about' className='lg:mt-0 mt-20 text-green-800 hover:text-green-800 font-semibold duration-200 antialiased p-2 cursor-pointer lg:my-0 my-6 text-sm lg:hover:text-white lg:hover:bg-red-900 font-main'>About</Link>
<li onClick={()=>setActive(false)} className='text-green-800 hover:text-green-800 font-semibold duration-200 antialiased p-2 cursor-pointer lg:my-0 my-6 text-sm lg:hover:text-white lg:hover:bg-red-900 font-main'>Contact</li>
<Link onClick={()=>setActive(false)} to='/stories' className='lg:mt-0 text-green-800 hover:text-green-800 font-semibold duration-200 antialiased p-2 cursor-pointer lg:my-0 my-6 text-sm lg:hover:text-white lg:hover:bg-red-900 font-main'>Stories</Link>
<Link onClick={()=>setActive(false)} to='/news' className='lg:mt-0 text-green-800 hover:text-green-800 font-semibold duration-200 antialiased p-2 cursor-pointer lg:my-0 my-6 text-sm lg:hover:text-white lg:hover:bg-red-900 font-main'>News</Link>
<div onMouseLeave={()=>toucherOff()} onMouseEnter={()=>setTouched(true)} className='flex flex-row items-center'>
<li onClick={()=>setActive(false)} className='text-green-800 hover:text-green-800 font-semibold duration-200 antialiased p-2 cursor-pointer lg:my-0 my-6 text-sm lg:hover:text-white lg:hover:bg-red-900 font-main'>Team</li>
<svg className='hover:bg-red-900 text-black hover:text-white arrowTwo' xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
<div onClick={()=>clicker()} ref={myRef} className={active?'mx-2 burger-active ':'burger mx-2'}>
<div className={active?'toggle1 line-one':'line-one'}></div>
<div className={active?' toggle2 line-two':'line-two'}></div>
<div className={active?' toggle3 line-three':'line-three'}></div>
</div>
</div>
<MenuDrop toucher={toucher} setTouched={setTouched} toucherOff={toucherOff} touched={touched}/>
</div>
</>
)
}
export default Nav
and here is my homepage component
import React, {useContext, useEffect} from 'react'
// COMPONENTS
import Banner from './Banner'
import Footer from './Footer'
import GreenBanner from './GreenBanner'
import HomepageContentCards from './HomepageContentCards'
import HomepageContent from './HomepageContent'
import HomepageTeam from './HomepageTeam'
import WrappedMap from './Map'
import ThemeContext from '../ThemeContext'
import '../styles/custom.css'
// ASSETS
import imageTwo from '../images/home-image-two.jpg'
import imageThree from '../images/home-image-three.jpg'
import imageFour from '../images/home-image-four.jpg'
const Homepage = () => {
useEffect(()=>{
window.scrollTo(0, 0);
console.log(open)
})
const open=useContext(ThemeContext)
return (
<div className={`${open==='false'?'mt-0':'mt-20'} bg-container mt-10 md:mt-0 shadow-2xl`}>
<Banner />
<div className='shadow-xl grid grid-cols-1 gap-0 lg:gap-0 lg:grid-cols-12'>
<div class=" col-span-7">
<HomepageContent />
</div>
<div class="">
<HomepageTeam />
</div>
</div>
<GreenBanner />
<div className='grid grid-cols-1 gap-2 lg:gap-3 sm:grid-cols-3'>
<HomepageContentCards image={imageTwo}/>
<HomepageContentCards image={imageThree} />
<HomepageContentCards image={imageFour} />
</div>
<div className='mt-8 lg:mt-0'>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places$key=${process.env.REACT_APP_GOOGLE_KEY}`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
<Footer />
</div>
)
}
export default Homepage
im using tailwind so ignore all the classes etc. basically im trying to have a simple state (whether the nav bar is open and burger has been pressed to open it or not).... if it is open the rest of the background stuff (homepage) becomes a dark color to faded opacity by using conditional rendering-->open?'dark':'light'
i can hand the state down but how do i pass the function (setOpen) from the hook down or how can i change the state from a child component if the state and hook is created in the above parent component (App)
i swear react makes state management silly AF sometime to me

Resources