printing out items from an array within an array of objects - javascript

I am trying to create some pills for a card display. I have an array of objects which I am mapping over to produce them on the page. Within the objects I have an array called tech which has something like tech: ['python', 'react.js'] etc.
something like this:
const data = [
{
imgUrl: "https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80",
description: 'oifonefpempfewpvpewmpew',
title: "Some project name",
tech: ["python", "react.js"],
},
{
imgUrl: "https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80",
description: 'oifonefpempfewpvpewmpew',
title: "Some project name",
tech: ["python", "react.js"],
},
]
I have mapped over that array like so.
<div className="">
{data.map(tech => (
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">
{tech.tech}
</span>
))}
</div>
Which it prints out the items, but it doesnt split them up into seperate pills just prints the array in one pill.
I am using Gatsby for my project
How can i split them up?

You need to have another .map();
<div className="">
{data.map(el => (
el.tech.map(currTech => (
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">
{currTech}
</span>
))
))}
</div>

From your code, you can see that you're just outputting the array at {tech.tech}:
Try this, assuming you already created the component as Pill and data hold the array of project objects; I am also assuming this is a project component and the Pill component lives in it, then:
<div className="">
{data.map(project => (
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">
{project.tech.map(t => <Pill tech={t} />)}
</span>
))}
</div>

Related

How to map an array inside JSON object using typescript?

I'm trying to get an array from a JSON object and then, map it in order to get the data.
Here is the data returned from an API:
{
project: [
{
_createdAt: '2022-12-15T16:45:57Z',
_id: 'cb39338d-4e6d-4c28-9a79-499afca392e6',
_rev: '974wCYB6EQ3xW9LGNZhQvU',
_type: 'project',
_updatedAt: '2022-12-27T04:55:54Z',
image: [Object],
linkToBuild: 'https://github.com/Arotiana137-51/charitty_to_portfolio_jsp',
summary: "This is a project I've done for learning purpose at the University. I've learned the basics of web programing with Java Server Pages ,Servelet ,....", technologies: [Array],
title: 'Charity '
}
]
}
And I wish to map this array in order to get elements from it inside this JSX element :
import React from "react";
import {motion} from 'framer-motion';
import { Project } from "../typing";
import { urlFor } from "../sanity";
type Props = {
projects:Project[];
};
function Projects({projects}: Props) {
console.log(projects);
return (
<motion.div
initial={{opacity:0}}
whileInView={{opacity:1}}
transition={{duration:1.5}}
className="h-screen relative flex overflow-hidden flex-col text-left md:flex-row max-w-full justify-evenly mx-auto items-center z-0">
<h3 className="absolute top-24 uppercase tracking-widest text-teal-400 text-2xl ">
Projects
</h3>
<div className="relative w-full flex overflow-x-scroll overflow-y-hidden snap-x snap-mandatory z-20">
{/*----------- HERE I TRY TO ACCESS THE ARRAY AND MAP IT DIRECTLY, LIKE IN JS-----------------------------------*/}
{ projects.project.map((project,i) => (
<div className="w-screen flex-shrink-0 snap-center flex flex-col space-y-5 items-center justify-center p-20 md:p-44 h-screen">
<motion.img
initial ={{
y: -300,
opacity:0
}}
transition={{duration: 1.2}}
whileInView = {{opacity:1 , y:0 }}
viewport={{ once: true}}
src={urlFor(project.image).url()}
alt="#"/>
<div className=" space-y-10 px-0 md:px-10 max-w-6xl ">
<h4 className="text-4xl font-semibold text-center">
Case study {i+1} of{projects.length}: <span className="underline decoration-teal-700"> {project?.title}</span>
</h4>
<div className="flex items-center space-x-2 justify-center">
{
project?.technologies.map((technology)=>(
<img
className="h-10 w-10"
key={technology._id}
src={urlFor(technology?.image).url()}
alt=""
/>
))
}
</div>
<p>{project?.summary}</p>
</div>
</div>
))}
</div>
<div className="w-full absolute top-[30%] bg-teal-800 left-0 h-[500px] -skew-y-12">
</div>
</motion.div>
);
}
export default Projects;
This normally works on js but my compiler return:
Server Error
TypeError: Cannot read properties of undefined (reading 'map')
because it still considers the prop as an object, even if I try to access the array at the code above, how can i solve it?
You are trying to do project?.technologies.map, but as i see at the JSON above , there is simply no "technologies" key.

Accessing a variable set in one component file in another file React js

I am quite new to react and am trying to create a sports fixture application where when you click on one of the fixtures in the list it takes you to a new page with more things you can do for that fixture.
On my home page I render the list of popular fixtures which I have set in a JSON called popular.json. I have this coded so if I add or remove a fixture from popular.json then this will automatically be rendered by having my component set up as below
import fixtures from './variables/popular.json'
export const setFixture = (id, home, homeLogo, away, awayLogo, date, time, venue) => {
const match = { id: id, teamA: home, teamALogo: homeLogo, teamB: away, teamBLogo: awayLogo, date: date, time: time, stadium: venue }
}
export default function Example() {
return (
<div >
<div className="max-w-4xl mx-auto px-4 py-1 sm:px-6 sm:pt-20 sm:pb-24 lg:max-w-7xl lg:pt-10 lg:px-8">
<h2 className="text-4xl font-bold text-white tracking-tight">
Popular
</h2>
<ul
time="list"
className="grid grid-cols-1 py-10 gap-6 sm:grid-cols-2 lg:grid-cols-3"
>
{fixtures.map((fixture) => (
<li key={fixture.id} className="col-span-1 rounded-lg bg-white bg-opacity-70 hover:bg-gray-100 shadow">
<a href="/game" onClick={setFixture(fixture.home,fixture.homeLogo,fixture.away,fixture.awayLogo,fixture.date,fixture.time,fixture.venue)}>
<div>
<div className="-mt-px flex">
<div className="flex w-0 flex-1">
<div className="mx-auto py-5 flex-shrink-0 flex justify-center">
<img
className="h-16 w-16"
src={fixture.homeLogo}
alt="Logo"
/>
</div>
</div>
<div className="w-1"></div>
<div className="-ml-px flex w-0 flex-1">
<div className="mx-auto py-5 flex-shrink-0 flex items-center justify-center">
<img
className="h-16 w-16"
src={fixture.awayLogo}
alt="Logo"
/>
</div>
</div>
</div>
<div className="-mt-px items-center flex">
<div className="flex w-0 flex-1">
<div className="relative -mr-px inline-flex w-0 flex-1 items-center justify-center rounded-bl-lg border border-transparent py-4 text-3xl font-bold text-gray-600 hover:text-purple-600">
<span className="justify-center">{fixture.home}</span>
</div>
</div>
<div className="flex w-1">
<div className="justify-center flex w-0 flex-1">
<span className="text-4xl font-bold text-center text-gray-600 hover:text-purple-600 tracking-tight">
V
</span>
</div>
</div>
<div className="-ml-px flex w-0 flex-1">
<div className="relative inline-flex w-0 flex-1 items-center justify-center rounded-br-lg border border-transparent py-4 text-3xl font-bold text-gray-600 hover:text-purple-600">
<span className="justify-center">{fixture.away}</span>
...
and so on and it looks like this render of fixtures
When you click on one of these games it redirects to the /game page where I want to build add more stuff you can see about each game.
I want to know which game has been selected by the setFixture function which is called onClick of one of the games, and then I want to pass in the match variable to my new component (which is in a different .jsx file) so that I can render the game that has been selected on the new page.
I have tried to import the match variable from this file and the setFixture function in the new component file but cannot set my variables for the teams, date, venue etc. from this import and am just seeing many errors.
Can anyone help me with how this can be done? Recognizing which game has been selected and loading this into another file?
Thank you so much!
The usual approach to problems in React is to make use of Components. A component represents an object, and is provided a set of properties to allow that instance of the object to be represented.
So, even before going to your "next page" think about the Components that you could have on this "page" - each of the Fixtures you are representing can be a Component that takes properties that will help render the teams in that match. This will then lead to less repetition in each of your components - the Example() component will now render (for example) 6 Fixture() components.
Also, React is usually a Single Page Application. Whilst you're getting your basic application working you don't need to consider Routing just yet. Also, pages aren't "loaded"; components are re-rendered when Component properties ("props" for short) are updated. So, in the same page above you could have a Game() component that initially doesn't have any team props sent to it and behaves by not rendering until it has teams set.
You could use something like https://stackblitz.com/ to showcase what you have now, and it'd be easier to show how your code can be simplified.

How to collapse Siblings with Headless UI

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.

React: How to set up Edit Function in CRUD app to switch out whole components while keeping id

Hey Guys I'm pretty new to React and I'm running into a bit of a pickle here.
I'm making a simple CRUD website creator and I've got the add and delete function created and they work great! but I'm having trouble with the edit function.
I've based this on the this tutorial which works with String data-type
https://www.digitalocean.com/community/tutorials/react-crud-context-hooks
So in my mind this is how it should should work
I've got use state as passing an object with a couple of properties
const [selectedSection, setSelectedSection] = useState({
id: null,
section: {},
});
I've set the id to the current component I'm editing with
const currentSectionId = route.match.params.id;
in my useEffect I'm carrying over the current id with while setting the new compenent skipping the id in the current section
useEffect(() => {
const sectionId = currentSectionId;
const selectedSection = sections.find(
(currentSectionTraversal) => currentSectionTraversal.id === parseInt(sectionId)
);
setSelectedSection(selectedSection);},[currentSectionId, sections]);
const onSubmit = (e) => {
e.preventDefault();
editSection(selectedSection);
history.push("/");
console.log("selectedSection id",selectedSection.section, selectedSection.id)
};
and the button function to spread the selectedSection and change the only the requested value in the button.
const handleOnChange = (userKey, newValue) => setSelectedSection({ ...selectedSection, [userKey]: newValue });
in my render code I've got my button set up like
<button href="/" className="bg-green-400 w-mt hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
value={selectedSection.section}
onChange={() => handleOnChange("section", QuickLinks)}
type="submit"
>Add Section</button>
Now I've tried different things like changing the data-type in the useState Object, having the setSelectedSection in use effect to change the SelectedSection.section
but in the console what I'm noticing is the button is not carrying the data over.
I've imported my compenent as Quicklinks but I'm not sure why it's not passing the component into the selectedSection.section
here is the entire code of the editSection
import React, { useState, useContext, useEffect} from "react";
import { useHistory, Link } from "react-router-dom";
import { GlobalContext } from "./GlobalState";
import QuickLinks from '../components/sections/quick_links/quickLinks';
import QuickLinksPreview from './images/quick-link.jpg';
export const EditSection = (route) => {
let history = useHistory();
const { sections, editSection } = useContext(GlobalContext);
const [selectedSection, setSelectedSection] = useState({
id: null,
section: {},
});
const currentSectionId = route.match.params.id;
useEffect(() => {
const sectionId = currentSectionId;
const selectedSection = sections.find(
(currentSectionTraversal) => currentSectionTraversal.id === parseInt(sectionId)
);
setSelectedSection(selectedSection);
}, [currentSectionId, sections]);
const onSubmit = (e) => {
e.preventDefault();
editSection(selectedSection);
history.push("/");
console.log("selectedSection id",selectedSection.section, selectedSection.id)
};
const handleOnChange = (userKey, newValue) => setSelectedSection({ ...selectedSection, [userKey]: newValue });
if (!selectedSection || !selectedSection.id) {
return <div>Invalid Employee ID.</div>;
}
return (
<React.Fragment>
<div className="w-full container mt-20 mx-auto">
<form onSubmit={onSubmit}>
<table>
<tbody>
{/* ----------------------------Item List Start COPY------------------------ */}
<tr>
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-40 w-50 shadow">
<img className="h-40 w-full " src={QuickLinksPreview} alt="" />
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">Quick Links</div>
<div className="text-sm text-gray-500">Multi Card Quick Links<br></br>for Description and links</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-white-800"> Beta </span>
{/* Component Development Status
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-black-800"> Constrution </span>
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"> Active </span>
<span className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-black-800"> Testing </span>
*/}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">S03-S5</td>
{/* Pricing Levels as Structure
S = Labels that it is sections
02 = is the Template Id for Developers
S = Standard Price
3 = Price level
*/}
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<button href="/" className="bg-green-400 w-mt hover:bg-green-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
value={selectedSection.section}
onChange={() => handleOnChange("section", QuickLinks)}
type="submit"
>Add Section</button>
</td>
</tr>
{console.log("Selected section", selectedSection.section)}
{/* ----------------------------Item List END COPY------------------------ */}
</tbody>
</table>
<div className="flex items-center justify-between">
<div className="block mt-5 bg-red-400 w-full hover:bg-red-500 text-white font-bold py-2 px-4 rounded focus:text-gray-600 focus:shadow-outline">
<Link to="/">Cancel</Link>
</div>
</div>
</form>
</div>
</React.Fragment>
);
};
Try using onClick instead of onChange for the button. I think onChange is for <input> tags not buttons. Also href is for links, not buttons. Unless this button is in a form type=submit isn't necessary. Also an arrow function isn't required for onClick. onChange={() => handleOnChange("section", QuickLinks)} -> onClick={handleOnChange("section", QuickLinks)}.

React - Hide parent's child element on blur event

I'm working on a custom input component. so when user clicks on the input element I want to show a dropdown that will contain default ten records and if user types some keyword in the input the dropdown will be updated with the results matching the keyword.
when user clicks on the input I want to show the dropdown and If they click out the container I want to hide the dropdown.
so I've added an onBlur event to the container and now if I click somewhere else it hides the dropdown but If I try to select a record It's also hidding the dropdown which is incorrect.
How can I hide the dropdown when the clicks outside of the input container? any suggestions?
export const ListBoxInput = ({ label, data, keyName }) => {
const [selected, setSelected] = useState({ key: null, value: null });
const [showDropdown, setShowDropdown] = useState(false);
return (
<div
className='flex items-center space-x-3'
onBlur={() => setShowDropdown(false)}
>
<label className='block text-sm font-medium text-gray-700'>{label}</label>
<div className='relative group'>
<input
type='text'
value={selected.key}
onFocus={() => setShowDropdown(true)}
onChange={(e) => setSelected({ key: e.target.value, value: null })}
disabled={!data.length}
placeholder={!data.length ? 'No data...' : ''}
className='w-full py-2 pl-3 pr-10 mt-1 text-left bg-white border border-gray-300 rounded-md shadow-sm disabled:opacity-50 focus:outline-none focus:ring-1 focus:ring-brand focus:border-brand sm:text-sm'
/>
{showDropdown && (
<div className='absolute z-10 w-full py-1 mt-3 overflow-auto text-base bg-white rounded-md shadow-lg max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none '>
{data.map((r) => (
<button
key={r.id}
type='button'
onClick={() => {
setSelected({ key: r[keyName], value: r.id });
setShowDropdown(false);
}}
className='block w-full py-2 pl-3 text-left hover:bg-gray-100 pr-9'
>
{r[keyName]}
</button>
))}
</div>
)}
</div>
</div>
);
};

Categories