I'm trying to implement google authentication on my nextjs app. i'm implementing this in my layout component,
when i use user = null and isLoadingUser the button is loading fine but when changed to user = session?.user and isloadingUser = 'loading' the button is set being set to disabled?
Layout coponent code:
import { Fragment, useState } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import Link from 'next/link';
import Image from 'next/image';
import PropTypes from 'prop-types';
import AuthModal from './AuthModal';
import { Menu, Transition } from '#headlessui/react';
import {
HeartIcon,
HomeIcon,
LogoutIcon,
PlusIcon,
SparklesIcon,
UserIcon,
} from '#heroicons/react/outline';
import { ChevronDownIcon } from '#heroicons/react/solid';
import { useSession, signOut } from 'next-auth/react';
const menuItems = [
{
label: 'List a new home',
icon: PlusIcon,
href: '/list',
},
{
label: 'My homes',
icon: HomeIcon,
href: '/homes',
},
{
label: 'Favorites',
icon: HeartIcon,
href: '/favorites',
},
{
label: 'Logout',
icon: LogoutIcon,
onClick: signOut,
},
];
const Layout = ({ children = null }) => {
const router = useRouter();
const { data: session, status } = useSession()
const [showModal, setShowModal] = useState(false);
const user = session?.user
const isLoadingUser = 'loading';
const openModal = () => setShowModal(true);
const closeModal = () => setShowModal(false);
return (
<>
<Head>
<title>SupaVacation | The Modern Dev</title>
<meta
name="title"
content="Learn how to Build a Fullstack App with Next.js, PlanetScale & Prisma | The Modern Dev"
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="min-h-screen flex flex-col">
<header className="h-16 w-full shadow-md">
<div className="h-full container mx-auto">
<div className="h-full px-4 flex justify-between items-center space-x-4">
<Link href="/">
<a className="flex items-center space-x-1">
<SparklesIcon className="shrink-0 w-8 h-8 text-rose-500" />
<span className="text-xl font-semibold tracking-wide">
Supa<span className="text-rose-600">Vacation</span>
</span>
</a>
</Link>
<div className="flex items-center space-x-4">
<Link href="/create">
<a className="hidden sm:block hover:bg-gray-200 transition px-3 py-1 rounded-md">
List your home
</a>
</Link>
{isLoadingUser ? (
<div className="h-8 w-[75px] bg-gray-200 animate-pulse rounded-md" />
) : user ? (
<Menu as="div" className="relative z-50">
<Menu.Button className="flex items-center space-x-px group">
<div className="shrink-0 flex items-center justify-center rounded-full overflow-hidden relative bg-gray-200 w-9 h-9">
{user?.image ? (
<Image
src={user?.image}
alt={user?.name || 'Avatar'}
layout="fill"
/>
) : (
<UserIcon className="text-gray-400 w-6 h-6" />
)}
</div>
<ChevronDownIcon className="w-5 h-5 shrink-0 text-gray-500 group-hover:text-current" />
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Menu.Items className="absolute right-0 w-72 overflow-hidden mt-1 divide-y divide-gray-100 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="flex items-center space-x-2 py-4 px-4 mb-2">
<div className="shrink-0 flex items-center justify-center rounded-full overflow-hidden relative bg-gray-200 w-9 h-9">
{user?.image ? (
<Image
src={user?.image}
alt={user?.name || 'Avatar'}
layout="fill"
/>
) : (
<UserIcon className="text-gray-400 w-6 h-6" />
)}
</div>
<div className="flex flex-col truncate">
<span>{user?.name}</span>
<span className="text-sm text-gray-500">
{user?.email}
</span>
</div>
</div>
<div className="py-2">
{menuItems.map(
({ label, href, onClick, icon: Icon }) => (
<div
key={label}
className="px-2 last:border-t last:pt-2 last:mt-2"
>
<Menu.Item>
{href ? (
<Link href={href}>
<a className="flex items-center space-x-2 py-2 px-4 rounded-md hover:bg-gray-100">
<Icon className="w-5 h-5 shrink-0 text-gray-500" />
<span>{label}</span>
</a>
</Link>
) : (
<button
className="w-full flex items-center space-x-2 py-2 px-4 rounded-md hover:bg-gray-100"
onClick={onClick}
>
<Icon className="w-5 h-5 shrink-0 text-gray-500" />
<span>{label}</span>
</button>
)}
</Menu.Item>
</div>
)
)}
</div>
</Menu.Items>
</Transition>
</Menu>
) : (
<button
type="button"
onClick={openModal}
className="ml-4 px-4 py-1 rounded-md bg-rose-600 hover:bg-rose-500 focus:outline-none focus:ring-4 focus:ring-rose-500 focus:ring-opacity-50 text-white transition"
>
Log in
</button>
)}
</div>
</div>
</div>
</header>
<main className="flex-grow container mx-auto">
<div className="px-4 py-12">
{typeof children === 'function' ? children(openModal) : children}
</div>
</main>
<AuthModal show={showModal} onClose={closeModal} />
</div>
</>
);
};
Layout.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
};
export default Layout;
Related
As I mention in my title Using tailwindcss and flowbite react Navbar. Toggle hamburger menu does not work properly.
I'm using flowbite react navbar component plugin for tailwindcss and got this weird hamburger menu with words in it. It supposes to be an icon instead of an Open main menu with an icon. Please refer to the code and image below for info.
Anyone had a solution for this?
Here is my code:
<Navbar
fluid={true}
rounded={false}>
<Navbar.Brand className='container flex flex-wrap items-center justify-between mx-auto'>
<Link
to='/'
className='flex items-center'>
<img
src={Logo}
className='h-6 mr-3 sm:h-9'
alt='Flowbite Logo'
/>
<span className='self-center text-xl font-semibold whitespace-nowrap dark:text-white'>
Logo here
</span>
</Link>
<div className='hidden lg:block'>
<Link
className='dark:text-white p-5'
to='/'>
Home
</Link>
<Link
className='dark:text-white p-5'
to='/academy'>
Academy
</Link>
<Link
className='dark:text-white p-5'
to='/signal'>
Signal
</Link>
</div>
<div className='flex md:order-2'>
{theme === 'dark' ? (
<button
onClick={handleThemeSwitch}
type='button'
className='text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-3 md:mr-0 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800'>
Light Mode
</button>
) : (
<button
onClick={handleThemeSwitch}
type='button'
className='text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-3 md:mr-0 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800'>
Dark Mode
</button>
)}
</div>
<Navbar.Toggle />
<Navbar.Collapse className='p-5'>
<Link to='/'>
<Navbar.Link>Home</Navbar.Link>
</Link>
<Link to='/academy'>
<Navbar.Link>Academy</Navbar.Link>
</Link>
<Link to='/signal'>
<Navbar.Link>Signal Group</Navbar.Link>
</Link>
</Navbar.Collapse>
</Navbar.Brand>
</Navbar>
Nvm i will just revamp the whole thing manually instead using their navbar toggle
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Navbar } from 'flowbite-react';
import Logo from '../assets/logo.svg';
import { BsMenuUp } from 'react-icons/bs';
import { IconContext } from 'react-icons';
const Navigation = () => {
const [theme, setTheme] = useState('light');
const [toggle, setToggle] = useState(false);
useEffect(() => {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [theme]);
const handleThemeSwitch = () => {
setTheme(theme === 'dark' ? 'light' : 'dark');
};
return (
<Navbar fluid={true} rounded={false}>
<Navbar.Brand className="container flex flex-wrap items-center justify-between mx-auto">
<Link to="/" className="flex items-center">
<img src={Logo} className="h-6 mx-3 sm:h-9" alt="Flowbite Logo" />
<span className="self-center text-xl font-semibold whitespace-nowrap dark:text-white">
Some Logo Here
</span>
</Link>
<div className="hidden lg:block">
<Link className="dark:text-white p-5" to="/">
Home
</Link>
<Link className="dark:text-white p-5" to="/academy">
Academy
</Link>
<Link className="dark:text-white p-5" to="/signal">
About
</Link>
</div>
<div className="flex md:order-2">
{theme === 'dark' ? (
<button
onClick={handleThemeSwitch}
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-3 md:mr-0 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Light Mode
</button>
) : (
<button
onClick={handleThemeSwitch}
type="button"
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center mr-3 md:mr-0 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Dark Mode
</button>
)}
<div className="lg:hidden flex align-middle justify-center p-1 text-gray-800 dark:text-white">
<IconContext.Provider value={{ size: 30 }}>
<BsMenuUp onClick={(e) => setToggle(!toggle)} />
</IconContext.Provider>
</div>
</div>
</Navbar.Brand>
{toggle ? (
<div className="p-2 m-5 bg-white dark:bg-gray-600 rounded-lg dark:text-white w-full">
<div>
<Navbar.Link as={Link} to="/">
Home
</Navbar.Link>
<Navbar.Link as={Link} to="/academy">
Academy
</Navbar.Link>
<Navbar.Link as={Link}>About</Navbar.Link>
</div>
</div>
) : null}
</Navbar>
);
};
export default Navigation;
I kept getting this error: Error: Hydration failed because the initial UI does not match what was rendered on the server.
Here is my code:
import { Fragment } from "react";
import { Menu, Transition } from "#headlessui/react";
import { ChevronDownIcon } from "#heroicons/react/20/solid";
import React from "react";
import { BsFillRecordCircleFill, BsFunnel } from "react-icons/bs";
import { FiCircle } from "react-icons/fi";
import Link from "next/link";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const Filter = () => {
return (
<div className="">
{/* Menu */}
<Menu as="div" className="relative inline-block text-left">
<div>
<Menu.Button className="inline-flex w-full justify-center rounded-md border border-gray-300 bg-[#1F3C71] text-white px-4 py-3 text-sm font-medium shadow-sm focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:ring-offset-gray-100 gap-1 items-center">
<BsFunnel />
Filter
<p className="bg-white text-[#1F3C71] rounded-full w-5 h-5">2</p>
</Menu.Button>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="absolute z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div className="py-1">
<div className="flex items-center justify-between py-2 px-5">
<p className="font-bold">Filters</p>
<p className="font-semibold text-[#1F3C71]">Apply</p>
</div>
<hr />
<Menu.Item>
{({ active }) => (
<main className="flex items-center">
<BsFillRecordCircleFill className="ml-6 text-[#1F3C71]" />
<Link
href="#"
className={classNames(
active ? "bg-white text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
Status
</Link>
</main>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<main className="flex items-center">
<FiCircle className="ml-6" />
<Link
href="#"
className={classNames(
active ? "bg-white text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
Email
</Link>
</main>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<main className="flex items-center">
<BsFillRecordCircleFill className="ml-6 text-[#1F3C71]" />
<Link
href="#"
className={classNames(
active ? "bg-white text-gray-900" : "text-gray-700",
"block px-4 py-2 text-sm"
)}
>
Data Range
</Link>
</main>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
</div>
);
};
export default Filter;
I try to change the Link tag but still having the issue.
I also got this error as well: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
import { client, urlFor } from '../client';
import { Link, useNavigate } from 'react-router-dom';
import { v4 as uuidv4 } from 'uuid';
import { MdDownloadForOffline } from 'react-icons/md';
import { AiTwotoneDelete } from 'react-icons/ai';
import { BsFillArrowUpRightCircleFill } from 'react-icons/bs';
import { fetchUser } from '../utils/fetchUser';
const Pin = ({ pin: { postedBy, image, _id, destination, save } }) => {
// console.log(postedBy);
const [postHovered, setPostHovered] = useState(false);
const navigate = useNavigate();
const user = fetchUser();
const alreadySaved = !!save?.filter((item) => item.postedBy._id === user.sub)
?.length;
const savePin = (id) => {
if (!alreadySaved) {
client
.patch(id)
.setIfMissing({ save: [] })
.insert('after', 'save[-1]', [
{
_key: uuidv4(),
userId: user.sub,
postedBy: {
_type: 'postedBy',
_ref: user.sub,
},
},
])
.commit()
.then(() => {
window.location.reload();
});
}
};
const deletePin = (id) => {
client.delete(id).then(() => {
window.location.reload();
});
};
return (
<div className='m-2'>
<div
onMouseEnter={() => setPostHovered(true)}
onMouseLeave={() => setPostHovered(false)}
onClick={() => navigate(`/pin-detail/${_id}`)}
className='relative cursor-zoom-in w-auto hover:shadow-lg rounded-lg overflow-hidden transition-all duration-500 ease-in-out'
>
<img
className='rounded-lg w-full'
src={urlFor(image).width(700).url()}
alt='user-post'
/>
{postHovered && (
<div
className='absolute top-0 w-full h-full flex flex-col justify-between p-1 pr-2 pt-2 pb-2 z-50'
style={{ height: '100%' }}
>
<div className='flex items-center justify-between'>
<div className='flex gap-2'>
<a
href={`${image?.asset?.url}`}
download
onClick={(e) => e.stopPropagation()}
className='bg-white w-9 h-9 rounded-full flex items-center justify-center text-dark text-xl opacity-75 hover:shadow-md outline-none'
>
<MdDownloadForOffline />
</a>
{alreadySaved ? (
<button className='bg-red-500 opacity-70 hover:opacity-100 text-white font-bold px-5 py-1 text-base rounded-3xl hover:shadow-md outlined-none'>
{save?.length} Saved
</button>
) : (
<button
onClick={(e) => {
e.stopPropagation();
savePin(_id);
}}
type='button'
className='bg-red-500 opacity-70 hover:opacity-100 text-white font-bold px-5 py-1 text-base rounded-3xl hover:shadow-md outlined-none'
>
Save
</button>
)}
</div>
<div className='flex justify-between items-center gap-2 w-full'>
{destination && (
<a
href={destination}
target='_blank'
rel='noreferrer'
className='bg-white flex items-center gap-2 text-black font-bold p-2 pl-4 pr-4 rounded-full opacity-70 hover:opacity-100 hover:shadow-md'
onClick={(e) => e.stopPropagation()}
>
<BsFillArrowUpRightCircleFill />
{destination.length > 15
? destination.slice(0, 15)
: destination}
</a>
)}
{postedBy?._id === user.sub && (
<button
onClick={(e) => {
e.stopPropagation();
deletePin(_id);
}}
className='bottom-0 bg-white w-9 h-9 rounded-full flex items-center justify-center text-dark text-xl opacity-75 hover:opacity-100 hover:shadow-md outline-none'
>
<AiTwotoneDelete />
</button>
)}
</div>
</div>
</div>
)}
</div>
<Link
to={`user-profile/${user?.sub}`}
className='flex gap-2 mt-2 items-center'
>
<img
className='w-8 h-8 rounded-full object-cover'
src={postedBy?.image}
alt='user-profile'
/>
<p className='font-semibold capitalize'>{postedBy.usernName}</p>
</Link>
{console.log(alreadySaved)}
</div>
);
};
export default Pin;
Here in the code, a user will save the pin (or post like snap) then button would be updated with saved button, but it is taking too much time to get updated , but when I save the pin sanity updates the pin's save array with the userId who have saved the pin (I can clearly see it on sanity studio in realtime).
I'm trying too split my application into multiple components, and I'm trying to create a dashboard component, however, how do I go about implementing next-auth within the component, previously I had getServerSideProps but as this is a component I can't do that.
Here is my component
import {
SearchIcon,
BellIcon,
UserCircleIcon,
ChevronDownIcon,
UserIcon,
LogoutIcon
} from '#heroicons/react/outline';
import { signOut } from 'next-auth/client';
export default function Navigation(...session) {
return (
<>
<div className="py-6 px-8 lg:h-16 lg:flex justify-between items-center bg-blue-700 text-white">
<div className="flex-1">
<div className="lg:pr-4 lg:py-4">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
<SearchIcon className="h-5 w-5 text-white" aria-hidden="true" />
</div>
<input
id="search"
name="search"
className="block w-full bg-blue-800 border border-blue-800 rounded-md py-2 pl-10 pr-3 text-sm placeholder-white focus:outline-none focus:ring-1 focus:ring-white focus:border-white sm:text-sm"
placeholder="Search"
type="search"
/>
</div>
</div>
</div>
<div className="relative flex items-center justify-center mt-8 lg:mt-0">
<div className="relative mr-6">
<BellIcon className="w-5" />
<small className="text-xs absolute -top-1 -right-2 -mt-2 bg-red-500 rounded-full py-0.5 px-1.5">
1
</small>
</div>
<details className="relative">
<summary className="flex items-center">
<UserCircleIcon className="w-5 mr-2" />
<h2>
{session.user.firstname} {session.user.lastname}
</h2>
<ChevronDownIcon
className="ml-2 flex-shrink-0 h-4 w-4 text-blue-200"
aria-hidden="true"
/>
</summary>
<div className="w-full mt-4 pb-4 absolute shadow-lg flex flex-col justify-center px-2 space-y-1 bg-blue-700">
<Link href="/dashboard/myprofile">
<a className="bg-blue-800 text-white hover:bg-blue-600 flex items-center px-2 py-2 text-sm font-medium rounded-md">
<UserIcon className="w-5 h-5 mr-2" />
Profile
</a>
</Link>
<Link href="#">
<a
onClick={signOut}
className="bg-blue-800 text-white hover:bg-blue-600 flex items-center px-2 py-2 text-sm font-medium rounded-md">
<LogoutIcon className="w-5 h-5 mr-2" />
Sign out
</a>
</Link>
</div>
</details>
</div>
</div>
</>
);
}
Currently what I'm getting is the following
TypeError: Cannot read properties of undefined (reading 'firstname')
edit #1
Here is my _app.js [UPDATED]
import Head from 'next/head';
import { Provider } from 'next-auth/client';
// assets
import '../styles/global.css';
import '../javascripts/app.js';
// components
import Footer from './components/Footer';
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<meta name="theme-color" content="#1E40AF" />
</Head>
<section className="flex flex-col min-h-screen">
<Provider session={pageProps.session}>
<Component {...pageProps} className="flex-1" />
</Provider>
</section>
<Footer />
</>
);
}
export default MyApp;
To fix this I did the following
_app.js - Now using provider to share the session across all pages.
import Head from 'next/head';
import { Provider } from 'next-auth/client';
// assets
import '../styles/global.css';
import '../javascripts/app.js';
// components
import Footer from './components/Footer';
function MyApp({ Component, pageProps }) {
return (
<>
<Head>
<meta name="theme-color" content="#1E40AF" />
</Head>
<section className="flex flex-col min-h-screen">
<Provider session={pageProps.session}>
<Component {...pageProps} className="flex-1" />
</Provider>
</section>
<Footer />
</>
);
}
export default MyApp;
and for the component I now have the following
import {
SearchIcon,
BellIcon,
UserCircleIcon,
ChevronDownIcon,
UserIcon,
LogoutIcon
} from '#heroicons/react/outline';
import { useSession, signOut } from 'next-auth/client';
import Link from 'next/link';
export default function Navigation() {
const [session, loading] = useSession()
return (
<>
<div className="py-6 px-8 lg:h-16 lg:flex justify-between items-center bg-blue-700 text-white">
<div className="flex-1">
<div className="lg:pr-4 lg:py-4">
<label htmlFor="search" className="sr-only">
Search
</label>
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
<SearchIcon className="h-5 w-5 text-white" aria-hidden="true" />
</div>
<input
id="search"
name="search"
className="block w-full bg-blue-800 border border-blue-800 rounded-md py-2 pl-10 pr-3 text-sm placeholder-white focus:outline-none focus:ring-1 focus:ring-white focus:border-white sm:text-sm"
placeholder="Search"
type="search"
/>
</div>
</div>
</div>
<div className="relative flex items-center justify-center mt-8 lg:mt-0">
<div className="relative mr-6">
<BellIcon className="w-5" />
<small className="text-xs absolute -top-1 -right-2 -mt-2 bg-red-500 rounded-full py-0.5 px-1.5">
1
</small>
</div>
<details className="relative">
<summary className="flex items-center">
<UserCircleIcon className="w-5 mr-2" />
{ session &&
<h2>
{session.firstname} { session.lastname}
</h2>
}
<ChevronDownIcon
className="ml-2 flex-shrink-0 h-4 w-4 text-blue-200"
aria-hidden="true"
/>
</summary>
<div className="w-full mt-4 pb-4 absolute shadow-lg flex flex-col justify-center px-2 space-y-1 bg-blue-700">
<Link href="/dashboard/myprofile">
<a className="bg-blue-800 text-white hover:bg-blue-600 flex items-center px-2 py-2 text-sm font-medium rounded-md">
<UserIcon className="w-5 h-5 mr-2" />
Profile
</a>
</Link>
<Link href="#">
<a
onClick={signOut}
className="bg-blue-800 text-white hover:bg-blue-600 flex items-center px-2 py-2 text-sm font-medium rounded-md">
<LogoutIcon className="w-5 h-5 mr-2" />
Sign out
</a>
</Link>
</div>
</details>
</div>
</div>
</>
);
}
I am new to Ract and building a multi step form in Next.js, where I also use Context. So my project structure is pretty wild and I don't get how / where to change the steps.status when moving to next step in the stepper. So far I have my context, managing half of states, basically the formData, but also the 'original' state of my stepper:
import { useState, createContext, useContext } from "react";
export const FormContext = createContext();
export default function FormProvider({ children }) {
const [data, setData] = useState({});
const [steps, setSteps] = useState([
{ name: 'Vertrag', status: 'current' },
{ name: 'Dateneingabe', status: 'upcoming' },
{ name: 'Bestätigung', status: 'upcoming' },
]);
const setFormValues = (values) => {
setData((prevValues) => ({
...prevValues,
...values,
}));
};
return (
<FormContext.Provider value={{ steps, data, setFormValues }}>
{children}
</FormContext.Provider>
);
}
export const useFormData = () => useContext(FormContext);
In Stepper.js I therefore import my formData:
import { CheckIcon } from '#heroicons/react/solid'
import { useContext } from 'react'
import { FormContext } from "../context";
// status: 'complete', 'current', 'upcoming'
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Stepper() {
const formData = useContext(FormContext);
const steps = formData.steps
return (
<nav aria-label="Progress">
<div className="flex items-center flex-col">
<ol className="flex items-center sm:flex-col md:flex-row mx-auto mt-32 mb-8">
{steps.map((step, stepIdx) => (
<li key={step.name} className={classNames(stepIdx !== steps.length - 1 ? 'pr-16 sm:pr-32' : '', 'relative')}>
{step.status === 'complete' ? (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-yellow-500" />
</div>
<a
href="#"
className="relative w-8 h-8 flex items-center justify-center bg-yellow-500 rounded-full hover:bg-yellow-500"
>
<span className="h-9 flex flex-col items-center">
<span className="relative top-2 z-10 w-8 h-8 flex items-center justify-center rounded-full group-hover:bg-indigo-800">
<CheckIcon className="w-5 h-5 text-white" aria-hidden="true" />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600 mt-8">{step.name}</span>
</span>
</a>
</>
) : step.status === 'current' ? (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-gray-200" />
</div>
<a
href="#"
className="relative w-8 h-8 flex items-center justify-center bg-white border-2 border-yellow-500 rounded-full"
aria-current="step"
>
<span className="h-9 flex flex-col items-center">
<span className="z-10 w-8 h-8 flex items-center justify-center rounded-full group-hover:bg-indigo-800">
<span className="relative h-2.5 w-2.5 bg-yellow-500 rounded-full relative" style={{top: '0.8rem'}} />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600" style={{marginTop: '2.72rem'}}>{step.name}</span>
</span>
</a>
</>
) : (
<>
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="h-0.5 w-full bg-gray-200" />
</div>
<a
href="#"
className="group relative w-8 h-8 flex items-center justify-center bg-white border-2 border-gray-300 rounded-full hover:border-gray-400"
>
<span className="h-9 flex flex-col items-center">
<span className="z-10 w-8 h-8 flex items-center justify-center rounded-full">
<span className="relative h-2.5 w-2.5 bg-transparent rounded-full group-hover:bg-gray-300" style={{top: '0.8rem'}} />
</span>
<span className="text-xs font-semibold tracking-wide text-gray-600" style={{marginTop: '2.72rem'}}>{step.name}</span>
</span>
</a>
</>
)}
</li>
))}
</ol>
</div>
</nav>
)
Moreover I have index.js page, where all the components come together
import { useState } from "react";
import Head from "next/head";
import Stepper from '../components/Stepper'
import styles from "../styles/styles.module.scss";
import FormCard from "../components/FormCard";
import Navbar from "../components/Navbar";
import { CheckIcon } from '#heroicons/react/solid'
import {
PersonalInfo,
ConfirmPurchase,
ContractInfo,
} from "../components/Forms";
import FormCompleted from "../components/FormCompleted";
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
const App = () => {
const [formStep, setFormStep] = useState(0);
const nextFormStep = () => setFormStep((currentStep) => currentStep + 1);
const prevFormStep = () => setFormStep((currentStep) => currentStep - 1);
const [activeStep, setActiveStep] = useState(0);
const handleNextStep = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
const handlePrevoiusStep = () => {
setActiveStep((prevActiveStep) => prevActiveStep - 1);
};
return (
<div>
<Head>
<title>Next.js Multi Step Form</title>
</Head>
< Navbar />
< Stepper activeStep={activeStep} />
<div className={styles.container}>
<FormCard currentStep={formStep} prevFormStep={prevFormStep}>
{formStep >= 0 && (
<ContractInfo formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep >= 1 && (
<PersonalInfo formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep >= 2 && (
<ConfirmPurchase formStep={formStep} nextFormStep={nextFormStep} />
)}
{formStep > 2 && <FormCompleted />}
</FormCard>
</div>
<div className="mt-1 mb-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
onClick={ () => { prevFormStep(); handlePrevoiusStep() }}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Back
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
onClick={ () => { nextFormStep(); handleNextStep() }}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-yellow-500 hover:bg-yallow-600 md:py-4 md:text-lg md:px-10"
>
Next
</a>
</div>
</div>
</div>
);
};
export default App;
As you see, Stepper is managed in three different files. But I am at least capable to change the activeStep index when clicking on Buttons, which already was a huge challenge for me. So now I also need the design to change. So that activeStep gets the step.status === 'current'. All stepps index < activeStep index should get step.status === 'complete' and logically all stepps index > activeStep index - step.status === 'upcoming'. Now I tried to handle this in index.js, but of course get back step is undefined, even though it is defined in Stepper.js through context.