I'm trying to use the map function but I can't get it right.
I have a side-bar which I want to show some icons. Here is an example without the map.
const SideBar = () => {
return (
<div className="fixed top-0 left-0 h-screen w-20 m-0 flex flex-col bg-gray-100 text-white shadow-lg">
<SideBarIcon icon={<FaFire size="30" />} />
<SideBarIcon icon={<FaPoo size="30" />} />
</div>
);
};
const SideBarIcon = ({ icon, text = "tooltip 💡"}) => (
<div className="sidebar-icon group">
{icon}
<span class="sidebar-tooltip group-hover:scale-100">{text}</span>
</div>
);
Here is an example with the map function
const SideBar = () => {
const icons = [FaFire, FaPoo];
return (
<div className="fixed top-0 left-0 h-screen w-20 m-0 flex flex-col bg-gray-100 text-white shadow-lg">
{icons.map(function(icon) {
return <SideBarIcon icon={<icon size="30"/>}/>
})}
</div>
);
};
const SideBarIcon = ({ icon, text = "tooltip 💡"}) => (
<div className="sidebar-icon group">
{icon}
<span class="sidebar-tooltip group-hover:scale-100">{text}</span>
</div>
);
Can you tell me what I'm doing wrong?
Thank you for your time!
By simply putting icon inside the tags, it thinks you're rendering an HTML element called icon, therefore it's not rendering the mapped item. It also wouldn't work if you set it as <{icon}/>, because it would be trying to render an empty element.
Luckily, there's an easy fix -- Just capitalize Icon, and React will render it as a JSX Component.
{icons.map(function(Icon) {
return <SideBarIcon icon={<Icon size="30"/>}/>
})}
{icons.map(function (Icon) {
return <SideBarIcon icon={<Icon size="30"/>}/>
})}
Components start with capital letters, just change icon to Icon.
Please do not forget to use key prop for your mapped items.
https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized
const SideBar = () => {
const icons = [<FaFire size="30" />, <FaPoo size="30" />];
return (
<div className="fixed top-0 left-0 h-screen w-20 m-0 flex flex-col bg-gray-100 text-white shadow-lg">
{icons.map(function(icon) {
return <SideBarIcon icon={icon}/>
})}
</div>
);
};
Since you are using icon in lowercase, it is not recognized as a jsx component. To fix this, change it to uppercase.
turn this
{icons.map(function(icon) {
return <SideBarIcon icon={<icon size="30"/>}/>
})}
to this
{icons.map(function(Icon) {
return <SideBarIcon icon={<Icon size="30"/>}/>
})}
Also, if the underlying components allow it, you could use the direct invocation, like so:
return <SideBarIcon icon={icon({size:"30"})/>
Keep in mind, that in most cases that is not what you want, and it might introduce hard-to-fix bugs.
Related
I have a Menu panel next to my sider. I am trying to add a drawer type animation however I can't seem position open/close within the pink column
const album = ["Album1", "Album2", "Album3"];
export const Menu = () => {
const [open, setOpen] = useState(false);
return (
<div className="flex flex-1 flex-col p-3">
<h2 className="text-lg font-medium text-gray-900">Album</h2>
<div className="flex-1">
<div className="flex flex-1 flex-col space-y-3 py-3">
{album.map((name) => (
<button
key={name}
id={name}
className={`flex space-x-2 items-center`}
onClick={() => setOpen(true)}
>
<span>{name}</span>
</button>
))}
</div>
</div>
<aside
onClick={() => setOpen(false)} //temporary
className={`transform top-0 left-0 w-72 bg-blue-400 fixed h-full overflow-auto ease-in-out transition-all duration-1000 z-30 ${
open ? "translate-x-14" : "-translate-x-full"
}`}
>
hello
</aside>
</div>
);
};
Javascript. Just add a eventListener to the 'button' which will handle the actions. In the eventListener, remove or add a css class. For example, by default the position is '-100px right' with css set position to '300px right', also add a transition 'all 300ms ease'. Now you element start with no css, and when user press button add the class.
<div class="text-3xl font-bold underline transition-all duration-300" id="must-change">
Lorem Ipsu
</div>
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold p-3 rounded" id="press-me">
Button
</button>
<script>
const btn = document.querySelector("#press-me")
const mustChange = document.querySelector("#must-change")
btn.addEventListener("click", () => {
mustChange.classList.toggle("font-bold")
})
</script>
I set 'transition all' but you can use a specific transition if you want.
I want to render this popover panel
<Popover.Panel className="absolute z-10 inset-x-0 transform shadow-xl pb-2 w-max">
<div>
<Dropdown subCategory={mainCatArray}/>
</div>
</Popover.Panel>
same as this popover button render in this below code. Because I want to render a separate popover panel with the popover button, please help me to do that. (I cannot render in one categoryObj because I want to render this popover button horizontally, but if I use a single map for the entire popover component popover button renders it vertically.
(Simply What I want to do is I want to render one <Popover.Panel> to one <Popover.Button>)
import { Fragment, useEffect, useState } from "react";
import { Popover, Transition } from "#headlessui/react";
import Dropdown from "./dropdown";
import { categoryObj } from "../../components/item/categoriesobj";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const CatDropDown = () => {
const [mainCatArray, setMainCatArray] = useState([]);
return (
<>
<Popover className="z-0 relative">
{({ open }) => (
<>
<div>
<div className=" z-10 bg-white">
<div className="w-2/3 flex gap-5 px-4 py-6 sm:px-6 lg:px-8 ">
{categoryObj.map((singleMainCategory) => (
<Popover.Button
className={classNames(
open ? "text-gray-900" : "text-gray-900",
"group bg-white rounded-md inline-flex items-center text-sm font-susty focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-white"
)}
>
<span
key={singleMainCategory.id}
onClick={() => {
setMainCatArray(singleMainCategory.subCategory);
}}
>
<span>{singleMainCategory.name}</span>
</span>
</Popover.Button>
))}
</div>
</div>
</div>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 -translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-1"
>
<Popover.Panel className="absolute z-10 inset-x-0 transform shadow-lg pb-2">
<div>
<Dropdown subCategory={mainCatArray}/>
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
</>
);
};
export default CatDropDown;
To make the accordion component with Headless UI, I have used Disclosure component. But I have a problem to control the collapse/expand state for it's siblings.
So, I want to close other siblings when I open one, but Disclosure component is only supporting internal render props, open and close. So, I can't control it outside of the component and can't close others when I open one.
import { Disclosure } from '#headlessui/react'
import { ChevronUpIcon } from '#heroicons/react/solid'
export default function Example() {
return (
<div className="w-full px-4 pt-16">
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2">
<Disclosure>
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<span>What is your refund policy?</span>
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-5 w-5 text-purple-500`}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
If you're unhappy with your purchase for any reason, email us
within 90 days and we'll refund you in full, no questions asked.
</Disclosure.Panel>
</>
)}
</Disclosure>
<Disclosure as="div" className="mt-2">
{({ open }) => (
<>
<Disclosure.Button className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75">
<span>Do you offer technical support?</span>
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-5 w-5 text-purple-500`}
/>
</Disclosure.Button>
<Disclosure.Panel className="px-4 pt-4 pb-2 text-sm text-gray-500">
No.
</Disclosure.Panel>
</>
)}
</Disclosure>
</div>
</div>
)
}
How do we control the close/open state outside of the component?
I don't think so it's possible using HeadlessUI, although you can create your own Disclosure like component.
Lift the state up to the parent component by creating a disclosures state that stores all the information about the disclosures.
Loop over the disclosures using map and render them.
Render a button that toggles the isClose property of the disclosures and also handles the aria attributes.
On button click, toggle the isOpen value of the clicked disclosure and close all the other disclosures.
Checkout the snippet below:
import React, { useState } from "react";
import { ChevronUpIcon } from "#heroicons/react/solid";
export default function Example() {
const [disclosures, setDisclosures] = useState([
{
id: "disclosure-panel-1",
isOpen: false,
buttonText: "What is your refund policy?",
panelText:
"If you're unhappy with your purchase for any reason, email us within 90 days and we'll refund you in full, no questions asked."
},
{
id: "disclosure-panel-2",
isOpen: false,
buttonText: "Do you offer technical support?",
panelText: "No."
}
]);
const handleClick = (id) => {
setDisclosures(
disclosures.map((d) =>
d.id === id ? { ...d, isOpen: !d.isOpen } : { ...d, isOpen: false }
)
);
};
return (
<div className="w-full px-4 pt-16">
<div className="mx-auto w-full max-w-md rounded-2xl bg-white p-2 space-y-2">
{disclosures.map(({ id, isOpen, buttonText, panelText }) => (
<React.Fragment key={id}>
<button
className="flex w-full justify-between rounded-lg bg-purple-100 px-4 py-2 text-left text-sm font-medium text-purple-900 hover:bg-purple-200 focus:outline-none focus-visible:ring focus-visible:ring-purple-500 focus-visible:ring-opacity-75"
onClick={() => handleClick(id)}
aria-expanded={isOpen}
{...(isOpen && { "aria-controls": id })}
>
{buttonText}
<ChevronUpIcon
className={`${
isOpen ? "rotate-180 transform" : ""
} h-5 w-5 text-purple-500`}
/>
</button>
{isOpen && (
<div className="px-4 pt-4 pb-2 text-sm text-gray-500">
{panelText}
</div>
)}
</React.Fragment>
))}
</div>
</div>
);
}
it is possible just you need to add some extra props selectors to the Disclosure.Button Component, in this case, I am adding aria-label='panel' like so...
import { Disclosure } from '#headlessui/react'
function MyDisclosure() {
return (
<Disclosure>
<Disclosure.Button aria-label="panel" className="py-2">
Is team pricing available?
</Disclosure.Button>
<Disclosure.Panel className="text-gray-500">
Yes! You can purchase a license that you can share with your entire
team.
</Disclosure.Panel>
</Disclosure>
)
}
next you need to select the following with "querySelectorAll" like...
<button
type='button'
onClick={() => {
const panels = [...document.querySelectorAll('[aria-expanded=true][aria-label=panel]')]
panels.map((panel) => panel.click())
}}
>
</button>
with this, you just need to change 'aria-expanded' to either 'true' or 'false' to expand or collapse
There's a way to do this with React (assuming you're using #headlessui/react) via useState:
const [disclosureState, setDisclosureState] = useState(0);
function handleDisclosureChange(state: number) {
if (state === disclosureState) {
setDisclosureState(0); // close all of them
} else {
setDisclosureState(state); // open the clicked disclosure
}
}
And in each Disclosure component, just pass an onClick callback to the Disclosure.Button:
<Disclosure.Button onClick={() => handleDisclosureChange(N)} />
Where N is the index of the clicked Disclosure (using 1 as the first Disclosure, since 0 handles all disclosures closed).
Finally, conditionally render the Disclosure.Panel based on the disclosureState:
{
disclosureState === N && (<Disclosure.Panel />)
}
Where N is the index of the clicked Disclosure. Using this method you can open just 1 disclosure at a time, and clicking an open disclosure will close all of them.
I am implementing a modal in my react app but face the weird error, Error: Expected `onClick` listener to be a function, instead got a value of `object` type.. What's confusing here is the fact that I have implemented the modal in another part of the application using the same logic and it doesn't produce such error.
Below are important snippets of the code.
index.js
import React, { useState } from "react";
import ProfileSave from "../../components/modal/profileSave";
const test = () => {
const [saveModal, setSaveModal] = useState(false);
const [save, setSave] = useState(false);
return (
<>
<button className="w-[165px] h-[60px] text-center px-4 py-2 text-sm text-white bg-[#3A76BF] rounded-md font-bold text-[16px]" onClick={saveShowModal}>
Save
</button>
{saveModal && (
<ProfileSave
open={saveModal}
handleClose={() => setSaveModal(!saveModal)}
handleSave={handleSave}
/>
)}
</>
export default test
profileSave.js
import React from "react";
const ProfileSave = (open, handleClose, handleModal) => {
return open ? (
<div className="fixed inset-0 z-10 overflow-y-auto">
<div
className="fixed inset-0 w-full h-full bg-black opacity-40"
onClick={handleClose}
></div>
</div>
) : (
""
);
};
export default ProfileSave;
You neet to extract the props that you component recive
Try this :
import React from "react";
const ProfileSave = ({open, handleClose, handleModal}) => {
return open ? (
<div className="fixed inset-0 z-10 overflow-y-auto">
<div
className="fixed inset-0 w-full h-full bg-black opacity-40"
onClick={handleClose}
></div>
</div>
) : (
""
);
};
export default ProfileSave;
I'm using react with sanity io to create a portfolio page. I would like to create an unordered list according to how many tags I have a on a project. A project is composed of a title, image, description, and tags(this is just an array).
What I have tried is:
{project.tags.map(type => {
return (
<li>{type.type.name}</li>
)
})}
And I keep getting the error: "TypeError: project.forEach is not a function". If I just render
{project.tags}
all of the tags are displayed but then I can't style them hence the need to put them into a list.
Does anyone know how to accomplish this?
import React, { useEffect, useState} from "react";
import sanityClient from "../client.js";
import '../index.css';
export default function Project() {
const [projectData, setProjectData] = useState(null);
useEffect(() => {
sanityClient.fetch(`*[_type == "project"] | order(index) {
title,
mainImage{
asset->{
_id,
url
},
},
description,
projectType,
link,
tags
}`).then((data) => setProjectData(data)).catch(console.error);
}, []);
return (
<div className="antialiased text-gray-800 bg-gray-400">
<div className="container relative flex flex-col px-6 mx-auto space-y-8">
<div className="absolute inset-0 z-0 w-2 h-full bg-white shadow-md left-38 md:mx-auto md:right-0 md:left-0"></div>
{projectData && projectData.map((project, index)=> (
<div className="relative z-10 p-10 ">
<img src={project.mainImage.asset.url} alt={project.mainImage.alt} className="timeline-img" />
<div className={`${index % 2 === 0 ? "timeline-container-left timeline-container" : "timeline-container" }`} >
<p className="font-bold uppercase">{project.title}</p>
<div className={`${index % 2 === 0 ? "timeline-pointer-left timeline-pointer" : "timeline-pointer" }`} aria-hidden="true"></div>
<div className="p-6 bg-white rounded-md shadow-md sm:p-2">
<p className="pt-1">{project.description}</p>
</div>
</div>
</div>
))}
</div>
</div>
)
}
{project.tags.map( type => ( {type} ))} is the answer.