How to build carousel tailwind css flexbox with react slick - reactjs

I first time build carousel with tailwind css and react slick.
Post component has own height calc(100vh - 65px). after I use flexbox like this:
data.map((i, index) => (
<div
key={index}
className="min-h-screen h-direct w-full mb-5 flex flex-col items-center p-2 md:px-6"
ref={index === data.length - 1 ? lastBookElementRef : null}
>
<Post
index={index}
item={i}
isModal={false}
t={t}
user={user}
/>
</div>
))
on Post page:
/* eslint-disable jsx-a11y/media-has-caption */
/* eslint-disable jsx-a11y/img-redundant-alt */
/* eslint-disable react/jsx-props-no-multi-spaces */
/* eslint-disable no-unused-expressions */
import { ArrowLeftOutlined, ArrowRightOutlined, DeleteOutlined, FileSyncOutlined, HeartFilled, HeartOutlined, LikeOutlined, UserOutlined } from '#ant-design/icons';
import axios from 'axios';
import Link from 'next/link';
import React, { useEffect, useRef, useState } from 'react';
import ReactPlayer from 'react-player';
import Slider from 'react-slick';
import { BallTriangle } from 'react-loader-spinner';
import calculateTime from '../calculateTime';
import { useSocket } from '../context/socket';
import Modal from './Modal';
const Post = ({ item, isModal, user, t }) => {
const [myLikes, setMyLikes] = useState(item.likes);
const [isOpen, setIsOpen] = useState(false);
const socket = useSocket();
const parentRef = useRef(null);
const scrollRef = useRef(null);
const [imageLoading, setImageLoading] = useState(item.files);
const imageLoaded = (id) => {
setImageLoading((prev) => prev.filter((t) => t.path !== id));
};
const [currentItem, setCurrentItem] = useState(0);
const settings = {
dots: false,
infinite: true,
speed: 500,
slidesToShow: 1,
slidesToScroll: 1,
initialSlide: 0,
swipeToSlide: true,
autoplay: false,
arrows: false,
lazyLoad: true,
};
const slide = useRef();
function numberWithCommas(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
const addOrRemoveLike = async (postId) => {
const isLike = myLikes.filter((like) => like.user._id === user._id).length > 0;
try {
await axios({
method: 'PUT',
url: `${process.env.SERVER}/posts/${isLike ? 'unliked' : 'liked'}/${postId}`,
withCredentials: true,
});
if (!isLike) {
const users = [item.user._id];
if (item.user._id !== user._id) socket.emit('newnotification', { users });
}
isLike ? setMyLikes((prev) => prev.filter((p) => p.user._id !== user._id)) : setMyLikes((prev) => ([{ user }, ...prev]));
} catch (error) {
console.error(error);
}
};
const handleScroll = (direction) => {
const { current } = scrollRef;
setCurrentItem((prev) => (prev + 1) % (item.files.length));
const scrollAmount = current.offsetWidth ? current.offsetWidth : 100;
if (direction === 'left') {
current.scrollLeft -= scrollAmount;
} else {
current.scrollLeft += scrollAmount;
}
};
return (
<div className={`flex-1 w-[60%] border-b gap-3
md:w-full ${isModal ? 'flex-row' : 'flex-col'} flex items-center justify-start`}
>
{/* Top */}
<div className=" flex w-full flex-row items-center gap-3 ">
<div className="border overflow-hidden rounded-full w-14 h-14 sm:w-10 sm:h-10 flex items-center justify-center bg-nft-gray-2 dark:bg-nft-gray-3">
{
item.user.logo.length > 0
? <img src={`${process.env.SERVER}/${item.user.logo}`} className="w-full h-full object-cover" />
: <UserOutlined className="text-2xl sm:text-xl" />
}
</div>
<div className="flex-1 flex flex-row items-center justify-between gap-2">
<div className="flex-1 flex flex-col justify-start ">
<Link href={`user/${item.user.username}`}>
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
<a className="text-blue-600 dark:text-blue-500 font-medium"> {item.user.username} </a>
</Link>
<div className="w-10/12">
<h1 className="text-xs font-thin">
{item.location}
</h1>
<h1 className="text-xs font-thin">
<span className="font-medium">posted: </span>
{calculateTime(item.date, t)}
</h1>
</div>
</div>
{
user._id === item.user._id && (
<div>
<DeleteOutlined className="transition-all duration-500 text-xl cursor-pointer hover:text-nft-black-1 dark:hover:text-nft-gray-1" />
</div>
)
}
</div>
</div>
{/* Bottom */}
<div className="flex-1 w-full flex flex-col gap-1">
{/* FIles */}
<div
className="flex-2 w-full border bg-nft-gray-1 dark:bg-nft-black-1
relative
"
>
<ArrowLeftOutlined
className="absolute top-1/2 -translate-y-1/2 left-0 -translate-x-1/2
bg-nft-black-1 text-white text-xl flex justify-center items-center p-1 rounded-full cursor-pointer
hover:text-2xl transition-all duration-500
dark:bg-nft-gray-3 z-[5]
"
onClick={() => slide.current.slickPrev()}
/>
<ArrowRightOutlined
className="absolute top-1/2 -translate-y-1/2 right-0 translate-x-1/2
bg-nft-black-1 text-white text-xl flex justify-center items-center p-1 rounded-full cursor-pointer
hover:text-2xl transition-all duration-500
dark:bg-nft-gray-3 z-[5]
"
onClick={() => slide.current.slickNext()}
/>
<div className="h-[70%] w-full border bg-nft-gray-1 dark:bg-nft-black-1">
<div className="relative h-full">
<Slider {...settings} ref={slide}>
{
item.files.map((i, item) => (
<div key={item} className="w-full h-full flex justify-center items-center ">
{
i.type.startsWith('image')
? (
<img
onLoad={() => imageLoaded(i.path)}
src={`${process.env.SERVER}/${i.path}`}
className={` object-cover
${imageLoading.filter((t) => t.path === i.path).length > 0 && 'hidden'}
`}
alt="image"
/>
)
: (
<video
src={`${process.env.SERVER}/${i.path}`}
className="object-contain"
controls
controlsList="nodownload"
/>
)
}
</div>
))
}
</Slider>
</div>
</div>
</div>
{/* Statistics */}
<div className="flex-1 flex flex-col gap-2">
<div className="flex w-full flex-row justify-between items-center">
<div className=" flex flex-row gap-3 transition-all duration-500 items-center justify-start relative">
{
myLikes.filter((like) => like.user._id === user._id).length > 0
? (
<HeartFilled
className={`text-2xl text-red-500 dark:text-red-600 ${user.id === item.user._id && 'cursor-pointer'}`}
onClick={() => addOrRemoveLike(item._id)}
/>
)
: (
<HeartOutlined
className="text-2xl"
onClick={() => addOrRemoveLike(item._id)}
/>
)
}
{
myLikes.length > 0 && (
<div
className={`text-sm font-medium ${user._id === item.user._id && 'cursor-pointer '}`}
onClick={() => { setIsOpen(user._id === item.user._id); }}
>
{numberWithCommas(myLikes.length).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')} likes
</div>
)
}
</div>
<div className="text-sm relative before:content-[''] before:absolute before:block before:w-full before:h-[2px]
before:bottom-0 before:left-0 before:bg-blue-500 dark:before:bg-blue-600
before:hover:scale-x-100 before:scale-x-0 before:origin-top-left
before:transition-all before:ease-in-out before:duration-300
cursor-pointer text-blue-500 dark:text-blue-600
"
>Go to this post
</div>
</div>
<div className="flex flex-row gap-2 flex-wrap">
{item.tags.map((i) => (
<div key={i} className="flex flex-row items-center bg-blue-500 px-1 text-white dark:bg-blue-600 transition-all duration-500">
<p className="text-sm ">#{i}</p>
</div>
))}
</div>
<div className="flex flex-row">
<p className="text-sm truncate whitespace-nowrap overflow-hidden">{item.description}</p>
</div>
</div>
</div>
{
isOpen && (
<Modal
setClose={setIsOpen}
header="Likes"
>
{
myLikes.map((i, index) => (
<div
key={index}
className={` ${index !== myLikes.length - 1 && 'mb-2'} flex flex-row justify-center items-center cursor-pointer`}
>
<div className="w-full flex-1 p-2 shadow dark:bg-nft-black-2 flex flex-row items-center justify-between">
<div className="flex flex-row gap-5 flex-1 items-center">
<div className="h-10 w-10 rounded-full flex justify-center items-center bg-nft-gray-1 dark:bg-nft-black-1 overflow-hidden">
{
i.user.logo.length > 0
? (
<img
src={`${process.env.SERVER}/${i.user.logo}`}
className="w-full h-full object-cover"
/>
)
: <UserOutlined />
}
</div>
<div className="flex flex-col items-start justify-between">
<p>{i.user.username}</p>
{((i.user.firstName.length > 0) || (i.user.lastName.length > 0))
&& (
<h1
className="font-medium text-sm"
>{`${i.user.firstName} ${i.user.lastName}`}
</h1>
)}
</div>
</div>
</div>
</div>
))
}
</Modal>
)
}
</div>
);
};
export default Post;
I use react-slick for carousel. But height dont get. I parent div heigth h-[70%]. When I see without images everything good but after add react slick carousel images height increased. How do this?

Related

why sanity is taking time to update the react application UI?

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).

How to return the complete list when the input is empty?

The search filter works practically as I wish, you enter a city and when you press the button it returns the results of the clinics that match the list of cities. The only problem is that I can only do a single search, then I have to reload the page, I need to delete the city from the input so that the complete list appears again and I can do another search, using typescript is complicating this part. To make it clearer, what I am trying to do is that whenever I enter a new city in the search bar and press the button, I get the result of the clinics in that city, now it just gives me the result only once, the next search gives me the result of clinic not found.
import React, { useState, useEffect } from 'react'
import { getClinic } from '../../api/drupalAPI'
import {Clinic} from '#icofcv/common';
import contentUtils from '../../lib/contentUtils'
import Loader from '../spinner/Loader';
interface Props {
showModalLocator: boolean,
closeModalLocator: () => void
}
export const ClinicLocator: React.FC<Props> = ({ children, showModalLocator, closeModalLocator }) => {
const [clinicList, setClinicList] = useState<Clinic[] | undefined >([]);
const [text, setText] = useState("")
const textInput = () => {
text === "" ? clinicList : null
}
const fetchClinicList = async () => {
getClinic().then((response)=>{
console.log(response)
setClinicList(response)
}).catch ( (error) => {
console.error(error);
throw error;
});
}
const handleChange = () => {
const filterClinicList = clinicList && clinicList?.length > 0
? clinicList?.filter((clinic) => clinic?.province?.toLowerCase() === text.toLowerCase())
: undefined;
setClinicList(filterClinicList)
}
useEffect (() => {
fetchClinicList();
}, []);
return (
<>
<div>
{showModalLocator ? (
<>
<div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none">
<div className="relative p-2 w-full max-w-3xl h-full md:h-auto">
{/*content*/}
<div className="relative bg-white rounded-lg shadow">
{/*header*/}
<div className="flex justify-between items-start px-4 py-3 rounded-t border-b">
<h3 className="text-lg font-medium">Localizador de clinicas</h3>
<button className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center" onClick={closeModalLocator}>
<svg aria-hidden="true" className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
{/*body*/}
<div className="relative px-3 py-3 flex-auto overflow-auto modal-body">
<h2 className="text-sm font-medium mb-2">¿Dónde te encuentras?</h2>
<input
value={text}
onChange= {(e) => {setText(e.target.value)
textInput}}
type="search"
className="w-100 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2"
placeholder="Introduce una ubicación"
/>
<div>
<h2 className="text-sm font-medium my-3">Resultados</h2>
<div className="w-100">
<iframe className="w-100" src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2664.3238269926374!2d-0.3805919350162851!3d39.46959682083709!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0xd604f4bee0957f3%3A0x6686ff7d230b3965!2zQy4gZGUgU2FudC
BWaWNlbnQgTcOgcnRpciwgNjEsIHBpc28gMsK6LCBwdGEgMsKqLCA0NjAwMiBWYWzDqG5jaWEsIEVzcGHDsWE!5e0!3m2!1ses!2sus!4v1662388390673!5m2!1ses!2sus" loading="lazy"></iframe>
</div>
<div className="md:mt-4 overflow-auto relative py-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
{clinicList && clinicList?.length === 0 && (
<div>Clinica no encontrada</div>
)}
{!clinicList ? <Loader /> :
clinicList.map((clinicFilter) => (
<div className="card bg-white px-2 py-3 h-36 md:h-32">
<button key={clinicFilter.id} type="button" className="text-left">
<div className="flex items-center gap-2 md:gap-4 md:gap-4">
<img className="h-24 w-2/5 min-w-40 object-cover object-center rounded-lg" src={contentUtils.getLargeImageUrl(clinicFilter.logo)} alt="#"/>
<div className="w-3/5">
<div className="text-md font-medium leading-5 clinic-title uppercase">{clinicFilter.title}</div>
<div className="flex items-center gap-2">
<div className="text-neutral-500 text-sm">{clinicFilter.propsPhone}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.mobile}</div>
</div>
<div className="text-teal-600 text-sm underline clinic-mail">{clinicFilter.email}</div>
<div className="text-neutral-500 text-sm">{clinicFilter.registry}</div>
</div>
</div>
</button>
</div>
))
}
</div>
</div>
</div>
</div>
{/*footer*/}
<div className="flex items-center justify-end px-4 py-2 border-t border-solid border-slate-200 rounded-b gap-2">
<button className="btn text-black text-sm background-transparent px-8 outline-none focus:outline-none focus:ring-teal-600 focus:border-teal-600" type="button" onClick={closeModalLocator}>Cancelar</button>
<button className="btn bg-teal-600 hover:bg-teal-700 text-white text-sm active:bg-teal-700 px-8 outline-none focus:outline-none" type="button" onClick={handleChange} >Buscar</button>
</div>
</div>
</div>
</div>
<div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
</>
) : null}
</div>
</>
)
}
In your handleChange just return the List itself when text is null
const filterClinicList = clinicList && clinicList?.length > 0
? clinicList?.filter((clinic) => (!text || clinic?.province?.toLowerCase() === text.toLowerCase()))
: undefined; //!text evaluates to true when text is '' i.e. short circuit the filter
```
ideally what you do would be:
```
let filterClinicList = clinicList;
//only filter when you *need* to filter
if (text) {
filterClinicList = clinicList?.filter((clinic) => (!text || clinic?.province?.toLowerCase() === text.toLowerCase()));
}
```

The react-icons are not loading in my React/TailwindCSS navbar

I'm using TailwindCSS and react-icons, but I can't seem to load the icons used to toggle my menu.
import { useState } from "react";
import { HiMenuAlt4 } from "react-icons/hi";
import { AiOutlineClose } from "react-icons/ai";
import logo from "../../images/logo.png";
const NavbarItem = ({ title, classProps }) => {
return (
<li className={`mx-4 cursor-pointer ${classProps}`}>
{title}
</li>
);
};
const Navbar = () => {
const [toggleMenu, setToggleMenu] = useState(0);
return (
<nav className="w-full flex md:justify-center justify-between items-center p-4">
<div className="md:flex-[0.5] flex-initial justify-center items-center"
<img src={logo} alt="logo" className="w-32 cursor-pointer" />
</div>
<ul className="text-white md:flex hidden list-none justify-between items-center flex-initial">
{["Market", "Exchange", "Tutorials", "Wallets"].map((item, index) => (
<NavbarItem key={item + index} title={item} />
))}
<li className="bg-[#2952e3] py-2 px-7 mx-4 rounded-full cursor-pointer hover:bg-[#2546bd]">
Login
</li>
</ul>
<div className="flex relative">
{toggleMenu ? (
<AiOutlineClose
fontSize={28}
className="text-white md:hidden cursor-pointer"
onClick={() => setToggleMenu(false)}
/>
) : (
<HiMenuAlt4
fontSize={28}
className="text-white md:hidden cursor-pointer"
onClick={() => setToggleMenu(true)}
/>
)}
</div>
</nav>
);
};
export default Navbar;

React Stepper change Status onClick

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.

Pass selected component to next step of form react

I have a multi step form, which is a quiz. I already set up the context to be able to pass data.
import { useState, createContext, useContext } from "react";
export const QuizContext = createContext();
export default function QuizProvider({ children }) {
const [data, setData] = useState({});
const setQuizValues = (values) => {
setData((prevValues) => ({
...prevValues,
...values,
}));
};
return (
<QuizContext.Provider value={{ data, setQuizValues }}>
{children}
</QuizContext.Provider>
);
}
export const useQuizData = () => useContext(QuizContext);
So, my first step is selecting a card.
The code below:
import { Card } from "../../stories/Card";
import { useQuizData } from "../../context/index"
import { useState } from "react";
const tacos = [
{
cathegory: 'Meat',
imgURL: 'https://images.unsplash.com/photo-1560781290-7dc94c0f8f4f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3024&q=80'
},
{
cathegory: 'Fish',
imgURL: 'https://images.unsplash.com/photo-1510130387422-82bed34b37e9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=975&q=80'
},
{
cathegory: 'Veggi',
imgURL: 'https://images.unsplash.com/photo-1572527129705-a6c197003d61?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=975&q=80'
},
]
export const TacoCathegories = ({quizStep, prevQuizStep, nextQuizStep}) => {
// const { setQuizValues } = useQuizData();
const [isSelected, setisSelected] = useState();
const handleSubmit = (values) => {
setQuizValues(values);
prevQuizStep();
nextQuizStep();
};
return (
<div className={quizStep === 0 ? 'block': 'hidden'}>
<div className="text-center">
<h2 className="text-3xl font-extrabold tracking-tight text-gray-600 sm:text-4xl">What is your favourite taco group?</h2>
</div>
<div className="max-w-7xl mx-auto py-24 px-4 sm:px-6 lg:px-8">
<div className="mt space-y-12 lg:space-y-0 lg:grid lg:grid-cols-3 lg:gap-x-8">
{tacos.map((taco, index) => (
<Card
role="button"
key={index}
title={taco.cathegory}
source={taco.imgURL}
text={`image of ${taco.cathegory} `}
selected={isSelected === index}
onChange={() => setisSelected(index)}
/>
))}
</div>
{tacos[isSelected] && <p>{tacos[isSelected].cathegory}</p>}
<div className="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
className="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-gray-200 hover:bg-gray-200 focus:outline-none md:py-4 md:text-lg md:px-10 cursor-not-allowed"
>
Back
</a>
</div>
<div className="mt-3 sm:mt-0 sm:ml-3">
<a
onClick={nextQuizStep}
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>
</div>
);
}
I am able to get the category of selected card tacos[isSelected].cathegory. Depending on the category I need to render different content in Step 2 of my multistep form. Basically, if I choose Meat I will render cards with Meat Tacos, If I choose Fish - with Fish Tacos. For now second step is empty, because I couldn't figure out how to pass selected category to second step.
export const TacoTypes = ({quizStep, prevQuizStep, nextQuizStep}) => {
return (
<div className={quizStep === 1 ? 'block' : 'hidden'}>
<div>
<p>Taco Types are: all you find in the object</p>
</div>
<div className="mt-5 sm:mt-8 sm:flex sm:justify-center lg:justify-center">
<div className="rounded-md shadow">
<a role="button" tabIndex={0}
onClick={prevQuizStep}
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={nextQuizStep}
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>
)
}
I am new to react, so any tipp would be helpful!
Sandbox: https://codesandbox.io/s/agitated-euler-pny5n

Resources