How to Show or Hide a div using useState in NextJS? - reactjs

I’m new to React but trying to build a responsive website with a “desktop header” and “mobile header” that is shown when the user clicks on a menu-icon-toggle and closes when the user clicks on Close-icon.
I’m obviously doing it wrong but can’t seem to figure out what the problem is, I believe that NextJS doesn’t know what to open or close.
**Note: I´m using TailwindCSS and this is a component that will be rendered on the index page
My code looks something like this (simplified, without all the content):
import React, { useState } from 'react'
import Image from 'next/Image'
function header() {
const \[mobile__Header, setMobile__Header\] = useState(false)
const showMobile__Header = () =\> setMobile__Header(!mobile__Header)
return (\<div\>
{/* mobile header */}
<div className='absolute flex flex-col w-screen h-screen place-content-between bg-white text-black p-5 z-50'>
<div className='flex items-center justify-between'>
{/* Left Logo */}
<div className='cursor-pointer'>
</div>
{/* close icon */}
<div className='cursor-pointer' onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</div>
{/* nav links */}
<div className='flex'>
<div className='flex flex-col text-xl space-y-3'>
</div>
</div>
{/* Social links and languaje changer */}
<div className='flex justify-between font-light'>
<div>
<a className="link" href="">EN</a>
</div>
<div className='flex flex-col'>
</div>
</div>
</div>
{/* desktop header */}
<header className="flex w- px-10 py-1 justify-between">
<div className="flex">
{/* Left Logos */}
<div className="flex md:hidden cursor-pointer">
</div>
<div className="hidden md:flex cursor-pointer">
</div>
</div>
<div className="flex items-center">
{/* Menu icon toggle */}
<div className='flex md:hidden cursor-pointer' onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</div>
{/* Right Nav Links and language changer */}
<div className="space-x-4 px-5 py-5 hidden md:flex ">
</div>
</div>
</header>
</div>
)}
export default header

You have many HTML/CSS issues like positioning and element structure.
The free tailwindui example is a solid example to reference. It has nice transitions and accessibility baked in, which I removed for the example. It also uses headlessui and heroicons, both were built by the TW team. The TW menu components handle the state internally, so you will not be able to see the logic in their example.
The below responsive example is based on the above-referenced version but without external dependencies.
import { useState } from "react";
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
const toggleMenu = () => setOpen(!isOpen);
return (
<header className="relative bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6">
<div className="flex justify-between items-center border-b-2 border-gray-100 py-6 md:justify-start md:space-x-10">
<div className="flex justify-start lg:w-0 lg:flex-1">
<a href="#">
<span className="h-8 w-auto sm:h-10">LOGO</span>
</a>
</div>
<div className="-mr-2 -my-2 md:hidden">
<button
onClick={toggleMenu}
className="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
>
Open
</button>
</div>
<nav className="hidden md:flex space-x-10">
<a href="#" className="text-base font-medium text-gray-500 hover:text-gray-900">
About
</a>
</nav>
</div>
</div>
{isOpen && (
<div className="absolute top-0 inset-x-0 p-2 transition transform origin-top-right md:hidden">
<div className="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 bg-white divide-y-2 divide-gray-50">
<div className="pt-5 pb-6 px-5">
<div className="flex items-center justify-between">
<div>
<span className="h-8 w-auto">LOGO</span>
</div>
<div className="-mr-2">
<button
onClick={toggleMenu}
className="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500"
>
X
</button>
</div>
</div>
<div className="mt-6">
<nav className="grid gap-y-8">
<a href="#" className="p-3 flex items-center rounded-md hover:bg-gray-50">
About
</a>
</nav>
</div>
</div>
</div>
</div>
)}
</header>
);
};
You will also likely need to handle the closing of the menu on route change.
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
const Navbar = () => {
const [isOpen, setOpen] = useState(false);
const toggleMenu = () => setOpen(!isOpen);
const router = useRouter();
useEffect(() => {
const closeMenu = () => isOpen && setOpen(false);
router.events.on("routeChangeStart", closeMenu);
return () => {
router.events.off("routeChangeStart", closeMenu);
};
}, [isOpen, router]);
return (
...see above example

Without knowing exactly what you are asking, this should set you down the right path, at least from a logic standpoint.
import React, { useState } from 'react'
import Image from 'next/Image'
function header() {
const [mobile__Header, setMobile__Header] = useState(false)
const showMobile__Header = (e) => {
if (e.target.className.includes('mobile')) {
setMobile__Header(true)
} else if (e.target.className.includes('desktop')){
setMobile__Header(false)
}
}
return (
<div>
<div className='absolute flex flex-col w-screen h-screen place-content-between bg-white text-black p-5 z-50'>
<div className='flex items-center justify-between'>
<div className='cursor-pointer'>
</div>
<div className={mobile__Header === true ? 'cursor-pointer-mobile' : 'remove-display'} onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
</div>
<div className='flex'>
<div className='flex flex-col text-xl space-y-3'>
</div>
</div>
<div className='flex justify-between font-light'>
<div>
<a className="link" href="">EN</a>
</div>
<div className='flex flex-col'>
</div>
</div>
</div>
<header className="flex w- px-10 py-1 justify-between">
<div className="flex">
<div className="flex md:hidden cursor-pointer">
</div>
<div className="hidden md:flex cursor-pointer">
</div>
</div>
<div className="flex items-center">
<div className={mobile__Header === false ? 'cursor-pointer-desktop' : 'remove-display'} onClick={showMobile__Header}>
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</div>
<div className="space-x-4 px-5 py-5 hidden md:flex ">
</div>
</div>
</header>
</div>
)
}
export default header
Basically, make sure to differentiate between your mobile button and your desktop button using your classNames. Then, you set a bolean indicator depending on what is in the className. From there, you either display the correct container, or use a CSS class that simply puts display: none (in this case, I used the name remove-display. All that is done with an inline ternary operator.
Whether you are toggling a button, or toggling a whole container (a parent div that when display: none removes all the content inside), this approach works in both scenarios.
I am still confused as to what you are asking but my solution should get you going. You have an onClick function on two divs and the comments above each of those says are misleading. One says close icon and the other says menu icon toggle. I am not sure what we are toggling.
EDIT: Here is a much cleaner way to do it and it involved no className toggling. This will teach you what you are doing so you can actually toggle what you are trying to toggle:
import React, { useState } from 'react'
function header() {
const [isActivate, setActive] = useState(false)
const handleActivate = (e) => {
!isActivate ? setActivate(true) : setActivate(false)
}
return (
<div>
<div>
<h1 style={{cursor: 'pointer'}} onClick={handleActivate}>
{isActivate === true ? 'OPEN' : false}
</h1>
<h1 style={{cursor: 'pointer'}} onClick={handleActivate}>
{!isActivate ? 'CLOSE' : false}
</h1>
</div>
</div>
)
}
export default header

Related

Drag and drop not working, I'm using react-dnd library

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.

TailwindCSS fade in Element on click

So I'm making this app and I need to fade in the menu when I click the button. I have it rendering on click using state, but I can't get it to fade in / fade out on click. When I edit the opacity value inside Chrome Dev Console the transition works fine, but when I want to change it using state it doesn't.
Any help? Thanks in advance!
import React, { useState } from "react";
import { useRouter } from "next/router";
import { MenuIcon, XIcon } from "#heroicons/react/outline";
function Header() {
const router = useRouter();
const [popCard, setPopCard] = useState("hidden");
const [fade, setFade] = useState(true);
const handleMenuClick = () => {
setPopCard("inline-block");
setFade(true);
};
const handleXClick = () => {
setPopCard("hidden");
setFade(false);
};
return (
<div className="text-center">
<header className="sticky z-50 top-0 shadow-md bg-white border-b p-5">
<div className="flex justify-between items-center">
<h1
className="text-6xl text-red-500 cursor-pointer"
onClick={() => router.push("/")}
>
Velvet
</h1>
<MenuIcon
className="h-8 text-red-500 cursor-pointer"
onClick={handleMenuClick}
/>
</div>
</header>
<div
className={
popCard +
" w-[60%] flex-col border my-10 pb-3 rounded-3xl shadow-lg transition duration-300 ease-in-out " +
`${fade === true ? "opacity-100" : "opacity-0"}`
}
>
<div className="flex justify-end">
<XIcon
className="h-6 text-red-500 cursor-pointer mt-2 mr-2 opacity-70"
onClick={handleXClick}
/>
</div>
<div className="space-y-8 text-3xl text-center mt-5 mb-10 text-red-500">
<h1>Contac</h1>
<h1>About Us</h1>
</div>
</div>
</div>
);
}
export default Header;
codesandbox: Sandbox
Just to be clear, I want the menu card to fade in when I click the menu button, and I want the menu card to fade out when I click the close button.
The solution is, you need to add duration, like this:
`transition-all duration-200 ${fade ? "opacity-100" : "opacity-0"}`
Here is my forked sandbox you had given, I've removed extra inline CSS, so it may become evident.
Here is the complete code:
function Header() {
const [popCard, setPopCard] = useState("hidden");
const [fade, setFade] = useState(false);
const handleMenuClick = () => {
setPopCard("inline-block");
setFade(true);
};
const handleXClick = () => {
setPopCard("hidden");
setFade(false);
};
console.log(fade, "fade");
return (
<div className="text-center">
<header className="sticky z-50 top-0 shadow-md bg-white border-b p-5">
<div className="flex justify-between items-center">
<h1 className="text-6xl text-red-500 cursor-pointer">Velvet</h1>
<button
className="text-3xl border rounded-lg px-5"
onClick={handleMenuClick}
>
Menu
</button>
</div>
</header>
<div className="p-10">
<div
className={`transition-all duration-200 ${
fade ? "opacity-100" : "opacity-0"
}`}
>
<div className="flex justify-end">
<button className="mt-2 mr-2 border p-2" onClick={handleXClick}>
Close
</button>
</div>
<div className="space-y-2 text-3xl text-center mt-5 mb-10 mx-5 text-red-500">
<h1>Kontakt</h1>
<h1>O Velvetu</h1>
</div>
</div>
</div>
</div>
);
}
export default Header;
Sandbox: https://codesandbox.io/s/sweet-swartz-mr3nru?file=/pages/index.js:41-1396

How can I do to show the pic?

I am trying to upload a image but when I try I don't see the image on the web page.
import "./styles.css";
const App = () => {
return (
<div className="grid bg-white rounded-lg shadow-xl w-11/12 md:w-9/12 lg:w-1/2 h-1 w-1">
<div className="grid grid-cols-1 mt-5 mx-7 h-3 w-3">
<label className="uppercase md:text-sm text-xs text-gray-500 text-light font-semibold mb-1">
Upload Photo
</label>
<div className="flex items-center justify-center w-full">
<label className="flex flex-col border-4 border-dashed w-full h-32 hover:bg-gray-100 hover:border-purple-300 group">
<div className="flex flex-col items-center justify-center pt-7">
<svg
className="w-10 h-10 text-purple-400 group-hover:text-purple-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
<p className="lowercase text-sm text-gray-400 group-hover:text-purple-600 pt-1 tracking-wider">
Select a photo
</p>
</div>
<input type="file" className="hidden" />
</label>
</div>
</div>
</div>
);
};
export default App;
Here is my code : Code
I don't undertand how I can do to see the picture after selected the pic.
Could you help me please ?
Thank you very much !
Update:
Your code sandbox working
The short answer is you set the src attribute of the image tag to URL.createObjectURL(photo) where photo is the file that was selected by the form input: event.target.files[0].
Here's my code:
import React, { useState } from 'react'
import logo from '../static/logo.svg'
type FormSubmitFunction = (formdata: React.FormEvent<HTMLFormElement>) => Promise<void>
interface LandingPageProps {
handleSubmit: FormSubmitFunction
}
const LandingPage = ({ handleSubmit }: LandingPageProps) => {
const [photo, setPhoto] = useState<File>()
return (
<div className='flex flex-col items-center max-w-sm mx-auto'>
<div className='w-5/12 mt-3 sm:mt-4'><img src={logo} alt='logo' /></div>
<form className='w-full' onSubmit={handleSubmit}>
<div>
<div className="w-11/12 mx-auto">
<div className={photo ? 'hidden' : 'block'}>
<h1 className='m-6 text-lg font-bold text-center'>Please upload a photo</h1>
<input
id="photoupload"
name="photo"
type="file"
required
placeholder="Upload photo"
accept="image/gif, image/jpeg, image/png"
className="block w-full px-3 py-2 mx-auto mt-2 text-base placeholder-gray-600 bg-white border-2 border-gray-300 shadow-md focus:placeholder-gray-500 focus:bg-white focus:border-gray-600 focus:outline-none focus:ring-0"
onChange={
(event) => {
event.target.files && setPhoto(event.target.files[0])
}
}
/>
</div>
<div className={photo ? 'block' : 'hidden'}>
<h1 className='m-6 text-lg font-bold text-center'>Success! Thanks.</h1>
<img src={photo ? URL.createObjectURL(photo) : undefined} alt={photo ? photo.name : undefined} />
</div>
</div>
</div>
</form>
</div >
)
}
export default LandingPage
OK ... I updated your code sandbox.
Past this into App.js and it works...
import "./styles.css";
import React, { useState } from "react";
const App = () => {
const [photo, setPhoto] = useState();
return (
<div className="grid bg-white rounded-lg shadow-xl w-11/12 md:w-9/12 lg:w-1/2 h-1 w-1">
<div className="grid grid-cols-1 mt-5 mx-7 h-3 w-3">
<label className="uppercase md:text-sm text-xs text-gray-500 text-light font-semibold mb-1">
Upload Photo
</label>
<div className="flex items-center justify-center w-full">
<label className="flex flex-col border-4 border-dashed w-full h-32 hover:bg-gray-100 hover:border-purple-300 group">
<div className="flex flex-col items-center justify-center pt-7">
{photo && (
<img src={URL.createObjectURL(photo)} alt={photo.name} />
)}
{!photo && (
<svg
className="w-10 h-10 text-purple-400 group-hover:text-purple-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
)}
<p className="lowercase text-sm text-gray-400 group-hover:text-purple-600 pt-1 tracking-wider">
Select a photo
</p>
</div>
<input
type="file"
className="hidden"
onChange={(event) => {
event.target.files && setPhoto(event.target.files[0]);
}}
/>
</label>
</div>
</div>
</div>
);
};
export default App;

next.js link object nog showing when passing in href in contentmanagement system

I have to build a blog with next.js, and now I want to display some hyperlinks in the blog via the content management system using rich text.
As seen in the image, the data objects paragraph is showing in the blog and the link (href link) is nog showing.
data objects href not visible
The code looks like this:
const PostDetail = ( { post }) => {
const getContentFragment = (index, text, obj, type) => {
let modifiedText = text;
if (obj) {
if (obj.bold) {
modifiedText = (<b key={index}>{text}</b>);
}
if (obj.italic) {
modifiedText = (<em key={index}>{text}</em>);
}
if (obj.underline) {
modifiedText = (<u key={index}>{text}</u>);
}
}
switch (type) {
case 'heading-one':
return <h1 key={index} className="text-3xl font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h1>;
case 'heading-three':
case 'heading-two':
return <h2 key={index} className="text-2xl font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h2>;
case 'heading-three':
return <h3 key={index} className="text-xl font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h3>;
case 'paragraph':
return <p key={index} className="mb-8">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</p>;
case 'heading-four':
return <h4 key={index} className="text-md font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</h4>;
case 'link':
return <Link key={index} href={href} className="text-md font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</Link>;
case 'script':
return <script key={index} className="text-md font-semibold mb-4">{modifiedText.map((item, i) => <React.Fragment key={i}>{item}</React.Fragment>)}</script>;
case 'image':
return (
<img
key={index}
alt={obj.title}
height={obj.height}
width={obj.width}
src={obj.src}
/>
);
default:
return modifiedText;
}
};
return (
<>
<div className="bg-white shadow-lg rounded-lg lg:p-8 pb-12 mb-8">
<div className="relative overflow-hidden shadow-md mb-6">
<img src={post.featuredImage.url} alt="" className="object-top h-full w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg" />
</div>
<div className="px-4 lg:px-0">
<div className="flex items-center mb-8 w-full">
<div className="hidden md:flex items-center justify-center lg:mb-0 lg:w-auto mr-8 items-center">
<img
alt={post.author.name}
height="30px"
width="30px"
className="align-middle rounded-full"
src={post.author.photo.url}
/>
<p className="inline align-middle text-gray-700 ml-2 font-medium text-lg">{post.author.name}</p>
</div>
<div className="font-medium text-gray-700">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 inline mr-2 text-pink-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<span className="align-middle">{moment(post.createdAt).format('MMM DD, YYYY')}</span>
</div>
</div>
<h1 className="mb-8 text-3xl font-semibold">{post.title}</h1>
{post.content.raw.children.map((typeObj, index) => {
const children = typeObj.children.map((item, itemindex) => getContentFragment(itemindex, item.text, item));
console.log(post.content.raw)
return getContentFragment(index, children, typeObj, typeObj.type);
})}
</div>
</div>
</>
)
}
export default PostDetail
If I'm not mistaken this is from JavaScript Mastery blog tutorial. I had the exact same problem.
Ended up changing PostDetail.jsx to this:
import React from "react";
import moment from "moment";
import { RichText } from "#graphcms/rich-text-react-renderer";
const PostDetail = ({ post }) => {
return (
<>
<div className="bg-white shadow-lg rounded-lg lg:p-8 pb-12 mb-8">
<div className="relative overflow-hidden shadow-md mb-6">
<img
src={post.featuredImage.url}
alt=""
className="object-top h-full w-full object-cover shadow-lg rounded-t-lg lg:rounded-lg"
/>
</div>
<div className="px-4 lg:px-0">
<div className="flex items-center mb-8 w-full">
<div className="hidden md:flex items-center justify-center lg:mb-0 lg:w-auto mr-8 items-center">
<img
alt={post.author.name}
height="30px"
width="30px"
className="align-middle rounded-full"
src={post.author.photo.url}
/>
<p className="inline align-middle text-gray-700 ml-2 font-medium text-lg">
{post.author.name}
</p>
</div>
<div className="font-medium text-gray-700">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6 inline mr-2 text-pink-500"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
<span className="align-middle">
{moment(post.createdAt).format("MMM DD, YYYY")}
</span>
</div>
</div>
<h1 className="mb-8 text-3xl font-semibold">{post.title}</h1>
<div className="mb-8">
{post.excerpt && (
<p
className="text-gray-700 text-center text-lg
font-normal px-4 lg:px-20 mb-8"
></p>
)}
<RichText content={post.content.raw.children} />
</div>
</div>
</div>
</>
);
};
export default PostDetail;
You might run into a problem in Hygraph, where you can't put extra line breaks in your posts. Simply press shift + enter (instead of just enter).

Weird bug in React/Redux component that causes redirect?

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

Resources