I'm building an application where a list of images is available at the left sidebar, and on the right, there will be a div where we can drop the images.
I'm using the react-dnd library. I have followed the steps as shown in the docs, but I'm getting the error dropped is not function when I drop an image on the target.
sidebar.js
import React, { useState, createContext } from "react";
import NFTCards from "./NFTCards";
import { NFTDATA } from "../utils/data";
import uuid from "react-uuid";
import SiteLogo from "../widgets/SiteLogo";
export const SelectedNFTContext = createContext({ dropped: null });
function Sidebar() {
const [nftList, setNftList] = useState([...NFTDATA]);
const dropped = (id) => {
console.log(nftList);
const selectedNFT = nftList.filter((nft, i) => nft.id === id);
selectedNFT[0].status = "selected";
setNftList(
nftList.filter((nft, i) => nft.id !== id).concat(selectedNFT[0])
);
};
const searchNFT = (e) => {};
return (
<aside className="w-96" aria-label="Sidebar">
<div className="overflow-y-auto py-4 px-3 bg-gray-50 rounded h-screen dark:bg-gray-800">
{/* Sidebar Logo */}
<a href="/" className="flex items-center text-center pl-2.5 mb-5">
<SiteLogo className="mr-3 mt-6 h-12 sm:h-7" alt="Site Logo" />
</a>
{/* Search Bar */}
<div>
<form className="my-16">
<label
htmlFor="default-search"
className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300"
>
Search
</label>
<div className="relative">
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
<svg
aria-hidden="true"
className="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
></path>
</svg>
</div>
<input
onChange={searchNFT}
type="search"
id="default-search"
className="block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Search NFTs & Collections..."
required=""
/>
<button
type="submit"
className="text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-[#14E2B2] dark:text-black dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Search
</button>
</div>
</form>
</div>
<SelectedNFTContext.Provider value={{ dropped }}>
<div className="space-y-8 ">
{nftList
.filter((nft) => nft.status === "unselect")
.map((nft) => (
<NFTCards
index={nft.id}
key={uuid()}
id={nft.id}
imgURL={nft.imgURL}
title={nft.title}
/>
))}
</div>
</SelectedNFTContext.Provider>
</div>
</aside>
);
}
export default Sidebar;
Droppable.js
import React from "react";
import { useDrop } from "react-dnd";
import AnimatedButton from "../widgets/buttons";
import { SelectedNFTContext } from "./Sidebar";
function Dropabble() {
const dropped = React.useContext(SelectedNFTContext);
console.log(dropped);
const [{ isOver }, dropRef] = useDrop({
accept: "image",
drop: (item, monitor) => dropped(item.id),
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
});
return (
<div>
<h1 className="text-white ml-32 mt-24 text-4xl">
Drop here NFTs to Mint
</h1>
{/* Drag and Drop */}
<div
ref={dropRef}
className={
isOver
? "w-[50vw] h-[50vh] my-16 ml-32 border-dashed border-8 border-green-500 border-spacing-4"
: "w-[50vw] h-[50vh] my-16 ml-32 border-dashed border-8 border-spacing-4"
}
></div>
<AnimatedButton
onClickFunc={() => alert("Minted")}
className="relative bg-background text-2xl px-6 py-2 border-2 border-[#14E2B2] hover:text-black hover:bg-[#14E2B2] hover:transition-all rounded ml-32 my-6"
buttonName="Mint"
/>
</div>
);
}
export default Dropabble;
nftcards.js
import React from "react";
import { useDrag } from "react-dnd";
// import { NFTDATA } from "../utils/data";
function NFTCards({ index, id, imgURL, title }) {
const [{ isDragging }, dragRef] = useDrag({
type: "image",
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
});
return (
<ul index={index}>
<li ref={dragRef} className={isDragging ? "border-2 " : "border-0 "}>
<img
className=" text-center w-full h-80 bg-cover object-cover"
src={imgURL}
alt={title}
/>
</li>
</ul>
);
}
export default NFTCards;
Please leave a hint where I'm doing wrong or link to the best resource.
Thanks for the help
Link to GitHub repository 👇
Repo Link
You have
<SelectedNFTContext.Provider value={{ dropped }}>
which set the value of the context to the object {dropped},
and
const dropped = React.useContext(SelectedNFTContext);
which sets dropped to the value of the context (which is not a function but an object containing a function).
Either remove one pair of curly braces around dropped in the first place, or add one pair of curly braces around dropped in the second place and it should work.
Related
I've a form for add item to cart in my NextJS + TS app. This form is a separate component and I use forwardRef in this component but I'm getting this error.
forwardRef Error
How can i fix this error. Thanks a lot!
Food.tsx Component
import Image from "next/image";
import Link from "next/link";
import React, { FormEvent, Key, useRef } from "react";
import { FiPlus } from "react-icons/fi";
import { useDispatch } from "react-redux";
import useAddItemToCart from "../hooks/useAddItemToCart";
import { addItemToCart } from "../store/cartSlice";
import FoodForm from "./FoodForm";
const Food: React.FC<{
id: Object;
title: String;
desc: String;
price: Number;
kit: Boolean;
category: String;
ingredients: Array<string>;
image: String;
index: Key;
}> = ({ id, title, desc, price, kit, category, index, ingredients, image }) => {
const amountInputRef = useRef<HTMLInputElement>(null);
const { handlerAddItemToCart: handlerSubmit, amountIsValid } =
useAddItemToCart(id, title, price, image, amountInputRef);
return (
<div
key={index}
className="col-span-1 w-full bg-theme-dark-grey rounded-[30px] py-3"
>
<div className="flex items-center justify-between gap-x-3 min-w-full min-h-[145px] max-h-[145px] h-full pl-4 pr-2 py-2">
<div className="flex-1 flex flex-col h-full justify-between">
<Link href={`/food/${id}`}>
<h2
data-testid="food-title"
className="capitalize text-lg mb-1 font-medium"
>
{title}
</h2>
<div className="flex flex-row flex-wrap">
{ingredients.length > 7
? ingredients.slice(0, 7).map((el, i) => (
<p
key={i}
className="text-theme-dark-grey2 text-sm font-medium leading-2"
>
{el}
</p>
))
: ingredients.map((el, i) => (
<p
key={i}
className="text-theme-dark-grey2 text-sm font-medium leading-2"
>
{el}
</p>
))}
</div>
</Link>
<div className="mt-2 flex flex-row justify-between items-center">
<p className="font-bold text-3xl leading-none">{`$${price}`}</p>
<FoodForm
onSubmit={(e: FormEvent) => handlerSubmit(e)}
ref={amountInputRef}
/>
{/*<form
onSubmit={(e: FormEvent) => handlerSubmit(e)}
className="flex items-center gap-x-2"
>
<input
type="number"
className="border-none outline-none text-center text-xs w-14 h-6 px-4 bg-theme-dark-black rounded-full"
ref={amountInputRef}
/>
<button
type="submit"
className="border-none outline-none bg-theme-green rounded-xl text-2xl p-1 font-bold"
>
<FiPlus />
</button>
</form>*/}
</div>
{!amountIsValid && (
<p className="mt-4 text-left text-xs font-medium text-theme-dark-orange">
Please enter a amount (1-5).
</p>
)}
</div>
<Image
src={image as string}
width={115}
height={115}
alt="product"
className="object-cover object-center drop-shadow"
/>
</div>
</div>
);
};
export default Food;
FoodForm.tsx Component
import React, { forwardRef } from "react";
import { FiPlus } from "react-icons/fi";
const FoodForm: React.FC<{ onSubmit: any; ref: any }> = forwardRef(
({ onSubmit }, ref) => {
return (
<form onSubmit={onSubmit} className="flex items-center gap-x-2">
<input
type="number"
className="border-none outline-none text-center text-xs w-14 h-6 px-4 bg-theme-dark-black rounded-full"
ref={ref}
/>
<button
type="submit"
className="border-none outline-none bg-theme-green rounded-xl text-2xl p-1 font-bold"
>
<FiPlus />
</button>
</form>
);
}
);
export default FoodForm;
I've reviewed and tried similar errors and questions from different web sites, but I couldn't get any results.
UPDATE: I fixed the error by editing the FoodForm component as follows.
import React, { forwardRef } from "react";
import { FiPlus } from "react-icons/fi";
const FoodForm = forwardRef<HTMLInputElement, { onSubmit: any }>(
({ onSubmit }, ref) => {
return (
<form onSubmit={onSubmit} className="flex items-center gap-x-2">
<input
type="number"
className="border-none outline-none text-center text-xs w-14 h-6 px-4 bg-theme-dark-black rounded-full"
ref={ref}
/>
<button
type="submit"
className="border-none outline-none bg-theme-green rounded-xl text-2xl p-1 font-bold"
>
<FiPlus />
</button>
</form>
);
}
);
export default FoodForm;
Thanks!
I'm building a dynamic Alert UI with tailwindcss. I want to make this alert so that it can be used anywhere when making all requests. I also combine the Alert with the value in the global state. Overall it works well, however there are the following caveats:
this the global state of alert used redux toolki:
import { createSlice } from '#reduxjs/toolkit';
const uiSlice = createSlice({
name: 'UI',
initialState: {
offcanvasVisible: false,
alert: {
isShow: false,
variant: '',
message: '',
},
},
reducers: {
offcanvasToggle: (state) => {
state.offcanvasVisible = !state.offcanvasVisible;
},
showAlert: (state, action) => {
state.alert.isShow = true;
state.alert.message = action.payload.message;
state.alert.variant = action.payload.variant;
},
closeAlert: (state) => {
state.alert.isShow = false;
state.alert.message = '';
state.alert.variant = '';
},
},
});
export const { offcanvasToggle, showAlert, closeAlert } = uiSlice.actions;
export default uiSlice.reducer;
the following code in the Alert component:
import { BiNoEntry } from 'react-icons/bi';
import { IoCloseSharp } from 'react-icons/io5';
import { BsCheckCircleFill, BsFillInfoCircleFill } from 'react-icons/bs';
import { useDispatch, useSelector } from 'react-redux';
import { closeAlert } from '../../store/ui-slice';
import { useEffect, useState } from 'react';
const Alert = () => {
const [showState, setShowState] = useState(false);
const [variantState, setVariantState] = useState('');
const [messageState, setMessageState] = useState('');
const [classes, setClasses] = useState('');
const dispatch = useDispatch();
const { variant, message, isShow } = useSelector((state) => state.ui.alert);
useEffect(() => {
if (!isShow) return;
setShowState(isShow);
setVariantState(variant);
setMessageState(message);
if (variant === 'info') {
setClasses('bg-blue-400 border-blue-500');
}
if (variant === 'success') {
setClasses('bg-green-400 border-green-500');
}
if (variant === 'failed') {
setClasses('bg-red-400 border-red-500');
}
}, [isShow, variant, message]);
return (
<div
className={`${
!showState && 'hidden'
} container-custom w-full py-4 text-white flex flex-row gap-2 items-center justify-between ${classes}`}
>
<div className="flex flex-row gap-2 items-center">
<span>
{variantState === 'info' ? (
<BsFillInfoCircleFill size="24" />
) : variantState === 'success' ? (
<BsCheckCircleFill size="24" />
) : variantState === 'failed' ? (
<BiNoEntry size="24" />
) : (
''
)}
</span>
{messageState}
</div>
<button
onClick={() => {
dispatch(closeAlert());
}}
className="text-white"
>
<IoCloseSharp color="white" size="24" />
</button>
</div>
);
};
export default Alert;
Code in the Contact Component:
/* eslint-disable jsx-a11y/iframe-has-title */
import LandingLayout from '../../components/Layout/LandingLayout';
import Breadcrumb from '../../components/UI/Breadcrumb';
import Input from '../../components/Form/Input';
import TextArea from '../../components/Form/TextArea';
import { AiOutlineWhatsApp } from 'react-icons/ai';
import { BsInstagram } from 'react-icons/bs';
import {
MdOutlineAddLocationAlt,
MdOutlineEmail,
MdPhone,
} from 'react-icons/md';
import Button from '../../components/UI/Button';
import { useForm } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import {
sendingEmail,
turnOffLoadingSendEmail,
turnOnLoadingSendEmail,
} from '../../store/landing-slice';
import Spin from '../../components/UI/Spin';
import { showAlert } from '../../store/ui-slice';
const Kontak = () => {
const dispatch = useDispatch();
const { loading, success, error } = useSelector(
(state) => state.landing.sendEmail
);
const {
register,
handleSubmit,
formState: { errors, isValid },
reset,
} = useForm({
mode: 'all',
});
if (success !== null) {
dispatch(showAlert({ variant: 'success', message: success.message }));
}
const onSubmit = (data) => {
if (!isValid) return;
dispatch(turnOnLoadingSendEmail());
setTimeout(() => {
dispatch(sendingEmail(data));
dispatch(turnOffLoadingSendEmail());
reset();
}, 1000);
};
return (
<LandingLayout>
<Breadcrumb title="Kontak Kami" />
<section className="container-custom py-10 w-full h-[450px] overflow-hidden">
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3960.687915874803!2d107.64700641530236!3d-6.927857094994467!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x2e68e80a699f1971%3A0xca4c51951a56650c!2sPanti%20Asuhan%20Al-Hidayah!5e0!3m2!1sid!2sid!4v1666696368586!5m2!1sid!2sid"
width="100%"
height="100%"
style={{ border: 0 }}
allowFullScreen=""
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
/>
</section>
<section className="container-custom py-4 w-full flex flex-col gap-10 md:flex-row-reverse">
<form onSubmit={handleSubmit(onSubmit)} className="w-full">
<h2 className="text-gray-700 text-lg mb-3">
Hubungi kami melalui form dibawah ini.
</h2>
<div className="flex flex-col lg:flex-row lg:gap-3">
<Input
options={{
...register('name', {
required: 'Nama Lengkap tidak boleh kosong',
}),
}}
id="name"
label="Nama Lengkap"
requireIcon="true"
hasError={!!errors?.name}
errorMessage={errors?.name?.message}
/>
<Input
options={{
...register('email', {
required: 'E-Mail tidak boleh kosong',
pattern: {
value: /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Invalid email address',
},
}),
}}
id="email"
label="E-Mail"
requireIcon="true"
hasError={!!errors?.email}
errorMessage={errors?.email?.message}
/>
</div>
<Input
options={{
...register('subject', { required: 'Subjek tidak boleh kosong' }),
}}
id="subject"
label="Subjek"
requireIcon="true"
hasError={!!errors?.subject}
errorMessage={errors?.subject?.message}
/>
<TextArea
id="keterangan"
label="Keterangan"
options={{
...register('keterangan'),
rows: '4',
}}
></TextArea>
<Button
className="flex gap-2"
options={{
type: 'submit',
disabled: !isValid,
}}
>
{loading && <Spin />}
Kirim
</Button>
</form>
<div className="w-full flex flex-col gap-2 md:gap-4">
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<MdOutlineAddLocationAlt size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Alamat</h3>
Jl. Trs. St. Kiaracondong, RT.02/RW.08, Kebun Jayanti, Kec.
Kiaracondong, Kota Bandung, Jawa Barat 40281
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<MdPhone size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Telepon</h3>
022 7333116
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<AiOutlineWhatsApp size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Whatsapp</h3>
0882 43556 7721
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<BsInstagram size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">Instagram</h3>
yayasan_alhidayah
</div>
</div>
<div className="flex flex-row gap-2">
<span className="self-start rounded-full bg-gray-300 text-gray-700 p-3">
<MdOutlineEmail size={32} />
</span>
<div className="flex flex-col gap-1">
<h3 className="pt-5 font-semibold text-[17px]">E-Mail</h3>
alhidayahkircon#gmail.com
</div>
</div>
</div>
</section>
</LandingLayout>
);
};
export default Kontak;
I put the Alert on the Navbar:
import { Link } from 'react-router-dom';
import Logo from '../UI/Logo';
import { FiMenu, FiPhone } from 'react-icons/fi';
import Button from '../UI/Button';
import Dropdown from '../UI/Dropdown';
import { BiDonateHeart } from 'react-icons/bi';
import { AiOutlineHistory } from 'react-icons/ai';
import Alert from '../UI/Alert';
const Navbar = (props) => {
return (
<header className="sticky top-0 z-10 bg-white border-b-2 border-slate-200">
<Alert />
<div className="container-custom flex justify-between items-center">
<div className="w-36 pl-2 md:pl-0">
<Link to="/">
<Logo />
</Link>
</div>
<ul className="hidden lg:flex font-medium">
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Link to={'/'}>Beranda</Link>
</li>
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Link to={'/kegiatan'}>Kegiatan</Link>
</li>
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Dropdown label="Donasi">
<li className="block px-4 py-2 hover:bg-gray-100">
<Link className="flex items-center" to="/donasi">
<span className="mr-3">
<BiDonateHeart />
</span>
Formulir Donasi
</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link className="flex items-center" to="/cek-donasi">
<span className="mr-3">
<AiOutlineHistory />
</span>
<p>Cek Donasi</p>
</Link>
</li>
</Dropdown>
</li>
<li className="mr-1 hover:bg-gray-100 py-2 px-4 rounded-md">
<Dropdown label="Tentang Kami">
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/profil-lembaga">Profil Lembaga</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/visi-misi">Visi & Misi</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/galeri">
<p>Galeri</p>
</Link>
</li>
<li className="block px-4 py-2 hover:bg-gray-100">
<Link to="/kontak">Hubungi Kami</Link>
</li>
</Dropdown>
</li>
</ul>
<div className="hidden lg:grid grid-cols-2 gap-1">
<a
className="flex items-center mr-1 hover:bg-gray-100 py-2 px-4 rounded-md"
href="tel:+0222334645"
>
<FiPhone size={'24'} />
<span className="ml-2">022 2334645</span>
</a>
<Button
className="flex justify-center items-center"
options={{
type: 'link',
href: '/donasi',
}}
>
Donasi
<span className="ml-1">
<BiDonateHeart />
</span>
</Button>
</div>
<div className="lg:hidden" onClick={props.offcanvasToggle}>
<FiMenu size={32} />
</div>
</div>
</header>
);
};
export default Navbar;
and here's what the UI should look like:
First of all, I thank you for being petrified
I hope I can use the best way to implement Alert without Warning like this.
This occurs because your updates to state are cascading. Your first set is causing the DOM to update. The update isn't yet complete, so your second (or third) set is also trying to update the DOM, and causing conflict.
I have found that when I have situations where I'm updating multiple state variables simultaneously it is better to use useReducer instead of useState. You can then setup a dispatch method that updates your multiple bits of state simultaneously, and even provide multiple methods that do different updates conditionally. This way state updates are concurrent, and avoid the errors you are seeing.
I am creating a simple Todo List using React, Next.js, and TailwindCSS. For some reasons, I got this error: TypeError: Cannot read property '_context' of undefined.
This error occurs in TodoForm.js. I have defined showModal in index.js using TodoContext.Provider, but why does this error occur?
TodoContext.js
import { createContext } from "react";
const TodoContext = createContext(null);
export default TodoContext;
index.js
import { useState } from "react";
import Modal from "../components/Modal";
import TodoForm from "../components/TodoForm";
import TodoList from "../components/TodoList";
import TodoContext from "./TodoContext";
export default function Home() {
const [open, setOpen] = useState(true);
const [alertType, setAlertType] = useState("success");
const [alertMessage, setAlertMessage] = useState("");
const showModal = (type, msg) => {
setAlertType(type);
setAlertMessage(msg);
setOpen(true);
};
return (
<TodoContext.Provider value={{ showModal }}>
<div className="flex flex-col min-h-screen py-2 w-full items-center">
<TodoForm />
<Modal setOpen={setOpen} alertMessage={alertMessage} open={open} />
<TodoList />
</div>
</TodoContext.Provider>
);
}
TodoForm.js
import { addDoc, collection, serverTimestamp } from "#firebase/firestore";
import { useContext, useState } from "react";
import { db } from "../firebase";
const TodoForm = () => {
const [todo, setTodo] = useState({ title: "", detail: "" });
const { showModal } = useContext();
const onSubmit = async () => {
const collectionRef = collection(db, "todos");
const docRef = await addDoc(collectionRef, {
...todo,
timestamp: serverTimestamp(),
});
setTodo({ title: "", detail: "" });
showModal(
"bg-blue-200",
`Todo with id ${docRef.id} is added successfully! `
);
};
return (
<div className="flex w-3/6 flex-col justify-center mt-6">
<pre>{JSON.stringify(todo)}</pre>
<input
id="title"
type="text"
placeholder="Title"
value={todo.title}
onChange={(e) => setTodo({ ...todo, title: e.target.value })}
/>
<input
id="detail"
type="text"
placeholder="Detail"
value={todo.detail}
onChange={(e) => setTodo({ ...todo, detail: e.target.value })}
/>
<button
type="button"
onClick={onSubmit}
>
ADD A NEW TODO
</button>
</div>
);
};
export default TodoForm;
Modal.js
import { Fragment, useRef, useState } from "react";
import { Dialog, Transition } from "#headlessui/react";
import { ExclamationIcon } from "#heroicons/react/outline";
export default function Modal({ setOpenClick, alertMessage, open }) {
const cancelButtonRef = useRef(null);
return (
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="fixed z-10 inset-0 overflow-y-auto"
initialFocus={cancelButtonRef}
onClose={setOpenClick}
>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true"
>
​
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<ExclamationIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="mt-2">
<p className="text-sm text-gray-500">{alertMessage}</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button
type="button"
className="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setOpenClick(false)}
>
Deactivate
</button>
<button
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => setOpenClick(false)}
ref={cancelButtonRef}
>
Cancel
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition.Root>
);
}
Would you let me know what I am missing here?
This is the image of showing error when using const showModal = useContext(TodoContext) on TodoForm.js
You are trying to destruct the context from the object
const { showModal } = useContext();
But the initial value is null
const TodoContext = createContext(null);
One solution is to give an initial value:
const TodoContext = createContext({showModal:()=>{}});
At Times This Issue Is As A Result Of Importing Something That Does Not Exist. Ensure That The Exported And Imported Names Are Correct.
On the homeScreen when you click a list of products, it goes the productScreen page with 1 product and its details.
When I added the onClick handler and onChange handler to the button and qty select in the productScreen.js, it causes it so that every time you go to productScreen (/products/:id) it will automatically redirect to /cart.
productScreen.js
import Rating from '../components/Rating'
import { useParams, Link } from 'react-router-dom'
import { useSelector, useDispatch} from 'react-redux'
import { useEffect, useState } from 'react'
import { productDetailCreator } from '../state/actions/productActions'
import { addCartAction } from '../state/actions/cartActions'
const ProductScreen = (props) => {
const { id } = useParams()
const dispatch = useDispatch()
const { loading, error, product } = useSelector(state => state.productDetail)
// Load Product
useEffect(() => {
dispatch(productDetailCreator(id))
}, [dispatch, id])
const [qty, setQty] = useState(0)
// Add to Cart Function
const addToCartHandler = (id, qty) => {
dispatch(addCartAction(id, qty))
props.history.push('/cart')
}
return (
<div className="max-w-5xl mx-auto my-36">
<div className=" mt-8 p-2">
<Link to="/" className="flex items-center text-gray-400 font-light">
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3 mr-1 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
</svg>
Back
</Link>
</div>
{ loading? (
<h3>Loading...</h3>
) : error ? (
<h3>{error}</h3>
) : (
<div className="flex flex-col sm:flex-row items-center my-7 px-8 py-2">
<div className="p-5 sm:w-2/5">
<img className="" src={product.image} alt={product.name} />
</div>
<div className="flex flex-col sm:w-2/5 p-5">
<span>{product.name}</span>
<hr className="my-4"/>
<Rating product={product} />
<hr className="my-4"/>
<span>Price: ${product.price}</span>
<hr className="my-4"/>
<span>Description: {product.description}</span>
</div>
<div className="flex flex-col sm:w-1/5 p-5">
<span className="text-center">Price: ${product.price}</span>
<span className="text-center mt-1">Status: {product.countInStock > 0 ? 'In Stock' : 'Out of Stock'}</span>
<div className="px-4 qty-select">
<label htmlFor="qty">Qty</label>
<select onChange={(e) => setQty(e.target.value)} name="qty" value="1" >
{ [...Array(10).keys()].map(x => (
<option value={x+1} key={x+1}>{x+1}</option>
))}
</select>
</div>
<button onClick={addToCartHandler(product._id, qty)} disabled={product.countInStock === 0} className="px-2 py-4 bg-pink-400 hover:bg-pink-300 hover:shadow-md text-white shadow-sm rounded-sm text-center my-4">Add to Cart</button>
</div>
</div>
)
}
</div>
)
}
export default ProductScreen
What's extremely weird is, if you click the image on the home page it will go to cart. If you click the product name on the home page, it will ADD the item to cart and go to cart.
There is no difference in the link references for either image or product name. I have no idea what's causing this issue?
Homescreen > product.js
import Rating from './Rating'
import {Link} from 'react-router-dom'
const Product = ({product}) => {
return (
<div className="card rounded shadow flex md:flex-col items-center overflow-hidden ">
<Link to={`/products/${product._id}`}>
<img className="mx-4 my-1 h-60 md:h-50 py-2 object-contain" src={product.image} alt={product.name} />
</Link>
<div className="py-4 px-4 mx-4 md:mx-2">
<Link to={`/products/${product._id}`} className="block h-28 font-light">{product.name}</Link>
<span className="block text-lg mb-4 font-medium">${product.price.toFixed()}</span>
<Rating product={product} />
<button className="px-6 py-4 my-4 min-w-full bg-pink-400 hover:bg-pink-300 hover:shadow-md text-white shadow-sm rounded-sm block">Add to Cart</button>
</div>
</div>
)
}
export default Product
All I know is if I get rid of the onClick on the Add to Cart button on productScreen.js, the redirect problem stops.
The way you set the onclick onClick={addToCartHandler(product._id, qty)} is calling the method immediately.
To prevent that you have to wrap it in an arrow function:
onclick={() => addToCartHandler(product._id, qty)}
Here you can see exactly your situation in the official docs
Here is some more info on sending params to the handler
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.