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
Related
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 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.
So i'm trying to access the id of my ypdate, and the example code i fould uses Match, I don't think the newer version of React likes it. I think I need to implement useParams(), however I new to coding and i'm not sure how to. Here is my code with Match params
import { useEffect } from "react";
import { Redirect } from "react-router-dom";
import { PlusCircleIcon, BookOpenIcon } from "#heroicons/react/solid";
import { useFormik } from "formik";
import * as Yup from "yup";
import { useDispatch, useSelector } from "react-redux";
import {
fetchCategoryAction,
updateCategoriesAction,
deleteCategoriesAction,
} from "../../redux/slices/category/categorySlice";
//Form schema
const formSchema = Yup.object({
title: Yup.string().required("Title is required"),
});
const UpdateCategory = ({
match: {
params: { id },
},
}) => {
const dispatch = useDispatch();
//fetch single category
useEffect(() => {
dispatch(fetchCategoryAction(id));
}, []);
//get data from store
const state = useSelector(state => state?.category);
const { loading, appErr, serverErr, category, isEdited, isDeleted } = state;
//formik
const formik = useFormik({
enableReinitialize: true,
initialValues: {
title: category?.title,
},
onSubmit: values => {
//build up the date for update
//dispath the action
dispatch(updateCategoriesAction({ title: values.title, id }));
},
validationSchema: formSchema,
});
//redirect
if (isEdited || isDeleted) return <Redirect to="/category-list" />;
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<BookOpenIcon className="mx-auto h-12 w-auto" />
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Update Category
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
<p className="font-medium text-indigo-600 hover:text-indigo-500">
These are the categories user will select when creating a post
</p>
{/* Display err */}
<div>
{appErr || serverErr ? (
<h2 className="text-red-500 text-center text-lg">
{serverErr} {appErr}
</h2>
) : null}
</div>
</p>
</div>
{/* Form */}
<form onSubmit={formik.handleSubmit} className="mt-8 space-y-6">
<input type="hidden" name="remember" defaultValue="true" />
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="email-address" className="sr-only">
Name
</label>
{/* Title */}
<input
value={formik.values.title}
onChange={formik.handleChange("title")}
onBlur={formik.handleBlur("title")}
type="text"
autoComplete="text"
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 text-center focus:z-10 sm:text-sm"
placeholder="New Category"
/>
<div className="text-red-400 mb-2">
{formik.touched.title && formik.errors.title}
</div>
</div>
</div>
<div>
<div>
{/* Submit */}
{loading ? (
<button
disabled
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-gray-600 "
>
<span className="absolute left-0 inset-y-0 flex items-center pl-3">
<PlusCircleIcon
className="h-5 w-5 text-yellow-500 group-hover:text-indigo-400"
aria-hidden="true"
/>
</span>
Loading please wait...
</button>
) : (
<>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-yellow-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<span className="absolute left-0 inset-y-0 flex items-center pl-3">
<PlusCircleIcon
className="h-5 w-5 text-yellow-500 group-hover:text-indigo-400"
aria-hidden="true"
/>
</span>
Update Category
</button>
<button
onClick={() => dispatch(deleteCategoriesAction(id))}
type="submit"
className="group mt-2 relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Delete Category
</button>
</>
)}
</div>
</div>
</form>
</div>
</div>
);
};
export default UpdateCategory;
Nothing renders to the screen with it in there I take it out and I do, however the functionality i need for updating isn't there. Any help would be greatly appreciated.
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.
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.