Toggling between components on a click in React.js - reactjs

I have a component which acts as the front of my single page site.
I have the following within that:
<div className="flex mb-0 items-center">
<h2 className="capitalize tracking-tight text-4xl mt-16 mr-8 md:mt-24 font-black hvr-underline-from-left pb-1">
recent projects
</h2>
<h2 className="capitalize tracking-tight text-4xl mt-16 md:mt-24 font-black text-gray-400 hvr-underline-from-left pb-1">
open source
</h2>
</div>
<Projects />
<OpenSource />
Currently I have the components displaying underneath each other. I want to be able to Toggle between the two, if a user clicks "recent projects" it shows the <Projects /> component. If a users clicks "open source" it hides the <Projects /> component and displays the <OpenSource/> one.
I would love to create an animation like a slide with something like react spring. But happy to just have them toggling at the moment.
any advice?

import React, { useState, useCallback } from "react";
export default function App() {
const [activeComponent, setActiveComponent] = useState("projects");
const modifyActiveComponent = useCallback(
newActiveComponent => {
setActiveComponent(newActiveComponent);
},
[setActiveComponent]
);
return (
<>
<div className="flex mb-0 items-center">
<h2
onClick={() => modifyActiveComponent("projects")}
className="capitalize tracking-tight text-4xl mt-16 mr-8 md:mt-24 font-black hvr-underline-from-left pb-1"
>
recent projects
</h2>
<h2
onClick={() => modifyActiveComponent("open_source")}
className="capitalize tracking-tight text-4xl mt-16 md:mt-24 font-black text-gray-400 hvr-underline-from-left pb-1"
>
open source
</h2>
</div>
{activeComponent === "projects" && <h1>Projects</h1>}
{activeComponent === "open_source" && <h1>OpenSource</h1>}
</>
);
}
Above I created a simple example to toggle two components. I used the useState hook to save the active component. I added the useCallback hook to code as well to handle the click on an item and update the active component afterwards.
You can play demo: https://codesandbox.io/s/lucid-carson-jghdi?file=/src/App.js:0-1042

I made this example with react transition group: https://codesandbox.io/s/epic-curie-nq3wo?file=/src/App.js
There you can see the animation effect, you can define of each transition and a lot of funny stuff.
(I did a small trick in the .section-exit to be able to show a smooth transition, which is the position: absolute without overlapping sections.
Documentation: https://github.com/reactjs/react-transition-group
JS: https://codesandbox.io/s/epic-curie-nq3wo?file=/src/App.js
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { CSSTransition } from "react-transition-group";
import "./styles.css";
function App() {
const sections = ["PROJECTS", "OPENSOURCE"];
const [activeSection, setActiveSection] = useState(sections[0]);
const setProject = () => setActiveSection(sections[0]);
const setOpenSource = () => setActiveSection(sections[1]);
return (
<div>
<button onClick={setProject}>Project</button>
<button onClick={setOpenSource}>Open Source</button>
<CSSTransition
classNames="section"
in={activeSection === sections[0]}
timeout={300}
unmountOnExit
>
<h1>Projects</h1>
</CSSTransition>
<CSSTransition
classNames="section"
in={activeSection === sections[1]}
timeout={300}
unmountOnExit
>
<h1>Open Source</h1>
</CSSTransition>
</div>
);
}
export default App;
CSS: https://codesandbox.io/s/epic-curie-nq3wo?file=/src/styles.css
.section-enter {
opacity: 0;
transform: translateX(100%);
}
.section-enter-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.section-exit {
opacity: 1;
position: absolute;
}
.section-exit-active {
opacity: 0;
transform: translateX(100%);
transition: opacity 300, transform 300;
}
There you can see the effect.

Related

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

How to create reusable custom modal component in React?

I have a problem with the concept of modals in React. When using server side rendered templates with jQuery I was used to have one empty global modal template always available (included in base template that was always extended). Then when making AJAX call I just populated modal..something like this:
$('.modal-global-content').html(content);
$('.modal-global').show();
So how do I make this concept in React?
There are a few ways of doing this. The first involves passing in the modal state from a parent component. Here's how to do this - first with the parent App.js component:
// App.js
import React from "react";
import Modal from "./Modal";
const App = () => {
const [showModal, updateShowModal] = React.useState(false);
const toggleModal = () => updateShowModal(state => !state);
return (
<div>
<h1>Not a modal</h1>
<button onClick={toggleModal}>Show Modal</button>
<Modal canShow={showModal} updateModalState={toggleModal} />
</div>
);
}
export default App;
And here's the Modal.js child component that will render the modal:
// Modal.js
import React from "react";
const modalStyles = {
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "blue"
};
const Modal = ({ canShow, updateModalState }) => {
if (canShow) {
return (
<div style={modalStyles}>
<h1>I'm a Modal!</h1>
<button onClick={updateModalState}>Hide Me</button>
</div>
);
}
return null;
};
export default Modal;
This way is perfectly fine, but it can get a bit repetitive if you're reusing the modal in many places throughout your app. So instead, I would recommend using the context API.
Define a context object for your modal state, create a provider near the top of your application, then whenever you have a child component that needs to render the modal, you can render a consumer of the modal context. This way you can easily nest your modal deeper in your component tree without having to pass callbacks all the way down. Here's how to do this - first by creating a context.js file:
// context.js
import React from "react";
export const ModalContext = React.createContext();
Now the updated App.js file:
// App.js
import React from "react";
import { ModalContext } from "./context";
import Modal from "./Modal";
const App = () => {
const [showModal, updateShowModal] = React.useState(false);
const toggleModal = () => updateShowModal(state => !state);
return (
<ModalContext.Provider value={{ showModal, toggleModal }}>
<div>
<h1>Not a modal</h1>
<button onClick={toggleModal}>Show Modal</button>
<Modal canShow={showModal} updateModalState={toggleModal} />
</div>
</ModalContext.Provider>
);
}
export default App;
And lastly the updated Modal.js file:
// Modal.js
import React from "react";
import { ModalContext } from "./context";
const modalStyles = {
position: "fixed",
top: 0,
left: 0,
width: "100vw",
height: "100vh",
background: "blue"
};
const Modal = () => {
return (
<ModalContext.Consumer>
{context => {
if (context.showModal) {
return (
<div style={modalStyles}>
<h1>I'm a Modal!</h1>
<button onClick={context.toggleModal}>Hide Me</button>
</div>
);
}
return null;
}}
</ModalContext.Consumer>
);
};
export default Modal;
Here's a Codesandbox link with a working version using context. I hope this helps!
One way you can solve this problem by using css and JSX.
this is the app and i can have anything like a button a link anything
Lets assume we have a link (react-router-dom) which redirects us to
a DeletePage
The Delete Page renders a Modal
You will provide the title and the actions of the Modal as props
const App = () => {
return(
<Link to="/something/someid">SomeAction</Link>
)
}
const DeletePage = () => {
return(
<Modal
title="Are you sure you want to delete this"
dismiss={() => history.replace("/")}
action={() => console.log("deleted") }
/>
)
}
Modal
const Modal = (props) => {
return(
<div>
<div className="background" onClick={props.dismiss}/>
<h1>{props.title}</h1>
<button onClick={props.dismiss}>Cancel</button>
<button onClick={props.action}>Delete</button>
</div>
)
}
set the z-index of the modal a high number
position: fixed of the modal component
when the user will click on the background the model will go away (
many ways to implement that like with modal state, redirect, etc i
have taken the redirect as one of the ways )
cancel button also has the same onClick function which is to dismiss
Delete button has the action function passed through props
this method has a flaw because of css because if your parent component
has a position property of relative then this will break.
The modal will remain inside the parent no matter how high the z-index is
To Save us here comes React-Portal
React portal creates a 'portal' in its own way
The react code you might have will render inside DOM with id of #root ( in most cases )
So to render our Modal as the top most layer we create another
DOM element eg <div id="modal"></div> in the public index.html file
The Modal react component code will slightly change
const Modal = (props) => {
return ReactDOM.createPortal(
<div>
<div className="background" onClick={props.dismiss}/>
<h1>{props.title}</h1>
<button onClick={props.dismiss}>Cancel</button>
<button onClick={props.action}>Delete</button>
</div>
),document.querySelector("#modal")
}
rest is all the same
Using React-Portal and Modal Generator
I have been toiling my days finding a good, standard way of doing modals in react. Some have suggested using local state modals, some using Modal Context providers and using a function to render a modal window, or using prebuilt ui libraries like ChakraUI that provides it's own Modal component. But using these can be a bit tricky since they tend to overcomplicate a relatively easy concept in web ui.
After searching for a bit, I have made peace with doing it the portal way, since it seems to be the most obvious way to do so. So the idea is, create a reusable modal component that takes children as props and using a local setState conditionally render each modal. That way, every modal related to a page or component is only present in that respective component.
Bonus:
For creating similar modals that uses the same design, you can use a jsx generator function that takes few colors and other properties as its arguments.
Working code:
// Generate modals for different types
// All use the same design
// IMPORTANT: Tailwind cannot deduce partial class names sent as arguments, and
// removes them from final bundle, safe to use inline styling
const _generateModal = (
initialTitle: string,
image: string,
buttonColor: string,
bgColor: string = "white",
textColor: string = "rgb(55 65 81)",
buttonText: string = "Continue"
) => {
return ({ title = initialTitle, text, isOpen, onClose }: Props) => {
if (!isOpen) return null;
return ReactDom.createPortal(
<div className="fixed inset-0 bg-black bg-opacity-80">
<div className="flex h-full flex-col items-center justify-center">
<div
className="relative flex h-1/2 w-1/2 flex-col items-center justify-evenly rounded-xl lg:w-1/4"
style={{ color: textColor, backgroundColor: bgColor }}
>
<RxCross2
className="absolute top-0 right-0 mr-5 mt-5 cursor-pointer text-2xl"
onClick={() => onClose()}
/>
<h1 className="text-center text-3xl font-thin">{title}</h1>
<h3 className="text-center text-xl font-light tracking-wider opacity-80">
{text}
</h3>
<img
src={image}
alt="modal image"
className="hidden w-1/6 lg:block lg:w-1/4"
/>
<button
onClick={() => onClose()}
className="rounded-full px-16 py-2 text-xl text-white"
style={{ backgroundColor: buttonColor }}
>
{buttonText}
</button>
</div>
</div>
</div>,
document.getElementById("modal-root") as HTMLElement
);
};
};
export const SuccessModal = _generateModal(
"Success!",
checkimg,
"rgb(21 128 61)" // green-700
);
export const InfoModal = _generateModal(
"Hey there!",
infoimg,
"rgb(59 130 246)" // blue-500
);
export const ErrorModal = _generateModal(
"Face-plant!",
errorimg,
"rgb(190 18 60)", // rose-700
"rgb(225 29 72)", // rose-600
"rgb(229 231 235)", // gray-200
"Try Again"
);

Best way to implement image enlargement on Thumbnails on Product Pages in Gatsby

List item
My product thumbnails don't enlarge on click using gatsby-remark-images-medium-zoom#1.2.1 or gatsby-remark-images-zoom
Followed readme on both and declared plugins in gatsby-config, also tried experimenting with option configurations. To see what I mean here's an example: https://store.shaka-surf.com/products/eco-medium-g5-surf-fins
import React from 'react'
import Img from 'gatsby-image'
import { graphql, useStaticQuery } from 'gatsby'
export default function Thumbnails({ src }) {
const { allMoltinProduct } = useStaticQuery(graphql`
query allMoltinProductsQuery {
allMoltinProduct {
nodes {
id
images {
childImageSharp {
sizes(sizes: "max-width: 801px) 100vw, 801px") {
srcSet
sizes
}
}
}
}
}
}
`)
return (
<div className="thumbnail-container">
{allMoltinProduct.nodes.map(product => (
<React.Fragment key={product.id}>
{product.images.map((image, index) => (
<ul className="thumbnail-list">
<li key={`image-${index}`} style={{display: 'inline-block', float:
'none', color: '#F2F4F5', width: '44px', height: '44px'}}>
<Img fluid={image.childImageSharp.fluid.src}
/>
<img sizes={image.childImageSharp.sizes.sizes} src=
{image.childImageSharp.fluid.src} srcSet=
{image.childImageSharp.sizes.srcSet} className="thumbnail-image medium-zoom- image" loading="lazy" alt={product.name} />
>
</li>
</ul>
))}
</React.Fragment>
))}
</div>
)
}
Would like the product thumbnails to enlarge.
UPDATE: managed to get Lightbox from this repo working on my gallery which is good enough for now. Will come back to the thumbnails at a later date.

Resources