I am working on an app that fetches current weather for a specified location.
I want to display current time but i have to use "slice()" method to get rid of the first part of the string that looks like this: "localtime: '2022-05-12 13:39' ".
The problem is with useEffect() and states in the context file.
import axios from "axios";
import { createContext, useEffect, useState } from "react";
const Context = createContext({
weather: {},
place: {},
});
export const ContextProvider = ({ children }) => {
const [weather, setWeather] = useState({});
const [place, setPlace] = useState({});
useEffect(() => {
const getWeatherData = async () => {
try {
const request = await axios.get(
"http://api.weatherstack.com/current"
);
const currentWeather = request.data.current;
const currentPlace = request.data.location;
setWeather(currentWeather);
setPlace(currentPlace);
} catch (err) {
console.log(err);
}
};
getWeatherData();
}, []);
console.log(weather);
console.log(place);
const context = {
weather,
place,
};
return <Context.Provider value={context}>{children}</Context.Provider>;
};
export default Context;
When i console.log my states (lines 33 & 34 in context file) i get this result:
result
So on the first render they're undefined but then useEffect runs and update them.
I assume that this is why my "splice()" method does not work properly.
import styled from "styled-components";
import IconWind from "../icons/icon-wind";
import IconWindDirection from "../icons/icon-wind-direction";
// import IconHumidity from "../icons/icon-humidity";
import { useContext } from "react";
import Context from "../../store/context";
const WeatherDisplay = () => {
const current = useContext(Context);
const currentDay = new Date(current.place.localtime).toLocaleString("en-us", {
weekday: "long",
});
const currentTime = current.place.localtime.slice(10);
return (
<Wrapper>
<div>
<PrimaryInfo>
<h1>{current.place.name},</h1>
<h2>{current.weather.temperature}°</h2>
<p>
{currentDay}, {currentTime}
</p>
</PrimaryInfo>
<AdditionalInfo>
<p>
<IconWind /> Wind Speed: {current.weather.wind_speed}
</p>
<p>
<IconWindDirection /> Wind Direction: {current.weather.wind_dir}
</p>
<p>Atmospheric pressure: {current.weather.pressure}</p>
<p>Humidity: {current.weather.humidity}</p>
</AdditionalInfo>
</div>
</Wrapper>
);
};
const Wrapper = styled.section`
width: 50%;
border-radius: 10px 0 0 10px;
& > div {
height: 100%;
padding-left: 4rem;
display: flex;
justify-content: center;
flex-direction: column;
}
`;
const PrimaryInfo = styled.section`
margin-bottom: 10rem;
h1 {
font-size: 5rem;
}
h2 {
font-size: 4rem;
margin: 1rem 0rem;
}
p {
font-size: 2.4rem;
}
`;
const AdditionalInfo = styled.div`
p {
margin: 1rem 0rem;
font-size: 1.2rem;
display: flex;
align-items: center;
}
`;
export default WeatherDisplay;
splice logs
I tried to check if they already have content but still didnt work:
import styled from "styled-components";
import IconWind from "../icons/icon-wind";
import IconWindDirection from "../icons/icon-wind-direction";
// import IconHumidity from "../icons/icon-humidity";
import { useContext } from "react";
import Context from "../../store/context";
const WeatherDisplay = () => {
const current = useContext(Context);
let currentDay;
let currentTime;
if (current.place.length > 0 && current.weather.length > 0) {
currentDay = new Date(current.place.localtime).toLocaleString("en-us", {
weekday: "long",
});
currentTime = current.place.localtime.slice(10);
}
console.log( currentDay);
console.log( currentTime);
return (
<Wrapper>
<div>
<PrimaryInfo>
<h1>{current.place.name},</h1>
<h2>{current.weather.temperature}°</h2>
<p>
{currentDay}, {currentTime}
</p>
</PrimaryInfo>
<AdditionalInfo>
<p>
<IconWind /> Wind Speed: {current.weather.wind_speed}
</p>
<p>
<IconWindDirection /> Wind Direction: {current.weather.wind_dir}
</p>
<p>Atmospheric pressure: {current.weather.pressure}</p>
<p>Humidity: {current.weather.humidity}</p>
</AdditionalInfo>
</div>
</Wrapper>
);
};
const Wrapper = styled.section`
width: 50%;
border-radius: 10px 0 0 10px;
& > div {
height: 100%;
padding-left: 4rem;
display: flex;
justify-content: center;
flex-direction: column;
}
`;
const PrimaryInfo = styled.section`
margin-bottom: 10rem;
h1 {
font-size: 5rem;
}
h2 {
font-size: 4rem;
margin: 1rem 0rem;
}
p {
font-size: 2.4rem;
}
`;
const AdditionalInfo = styled.div`
p {
margin: 1rem 0rem;
font-size: 1.2rem;
display: flex;
align-items: center;
}
`;
export default WeatherDisplay;
It just displays currentDay and currentTime as undefined.
What am I doing wrong?
Related
I'm building a tic-tac-toe app. I haven't added functionality between the X's and O's, right now, I'm stuck on rending ONE image via the onClick. I've set the state to make the onClick - at least I think - and I've written out my conditional, but its rendering X images for each square. How do I render an image for only ONE square instead of them all? Here is my code:
Xs.js
import React from 'react'
import styled from 'styled-components'
import X1 from './images/X1.jpg'
import X2 from './images/X2.jpg'
import X3 from './images/X3.jpg'
const Image = styled.img`
width: 175px;
height: 175px;
`
const Xs = () => {
const X = [X1, X2, X3]
const randomXImg = Math.floor(Math.random() * X.length)
return (
<Image src={X[randomXImg]} />
)
}
export default Xs
Cell.js - this is just a div to show for the actual square
import React from 'react'
import styled from 'styled-components'
const CellBlock = styled.div`
display: flex;
align-items: center;
justify-content;
border: 1px solid white;
width: 200px;
height: 200px;
background-color: white;
`
const Container = styled.div`
display: flex;
flex-wrap: nowrap;
flex-direction: column;
align-items: center;
box-sizing: border-box;
`
const Board = styled.div`
display: grid;
grid-template-columns: 210px 210px 202px;
grid-template-rows: 210px 210px 202px;
background-color: darkgreen;
`
const Cell = ({ onClick, isTurn, children }) => {
let squares = []
squares = Array.from(Array(9).fill(''))
return (
<Container>
<Board>
{
squares.map((box, id) => (
<CellBlock
key={id}
id={id}
onClick={onClick}
isTurn={isTurn}
>
{children}
</CellBlock>
))
}
</Board>
</Container>
)
}
export default Cell
Here is the gameboard which is the parent component of both Xs.js and Cell.js
Gameboard.js
import React, { useState } from 'react'
import Cell from './Cell'
import Xs from './Xs'
const Gameboard = () => {
const [ player, setPlayer ] = useState('X')
const [ isNotOccupied, setIsNotOccupied ] = useState(false)
return (
<>
<Cell
onClick={() => setIsNotOccupied(!isNotOccupied)}
isTurn={player}
>
{ isNotOccupied && player === 'X' && <Xs />}
</Cell>
</>
)
}
export default Gameboard
Any help would be greatly appreciated.
I have trying to figure out why my component is not working as expected.
The below code creates a list of word cards and each card will play audio when clicked.
There is also a search function to filter out the cards.
However, I find that these two functions do not work together. After searching, sometimes, I could not get the audio working. The audio part works fine without any searching.
I am getting the error
Uncaught TypeError: myAudio.current is null
handlePlayAudio WordsList.js:45
So I am guessing when I type in the search, something is causing myAudio.current to become null. The trouble is, sometimes it works but sometimes it doen't!
Does anyone know what is going on and how to fix it?
WordsList.js
import React, { useState, useRef } from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
// import { Link } from "gatsby"
// import slugify from "slugify"
import styled from "styled-components"
const WordsList = ({ words = [] }) => {
const [searchField, setSearchField] = useState("")
const myAudio = useRef("")
const filteredWords = words.filter(word => {
return (
word.english.toLowerCase().includes(searchField) ||
word.japanese.toLowerCase().includes(searchField) ||
word.romaji.toLowerCase().includes(searchField)
)
})
const handleSearchChange = event => {
const searchField = event.target.value.toLowerCase()
setSearchField(searchField)
}
return (
<Wrapper>
<div className="search-container">
<input
className="search-box"
type="search"
placeholder="search english, japanese, or romaji"
onChange={handleSearchChange}
/>
</div>
<div className="wrapper">
{filteredWords.map(word => {
const { id, english, japanese, romaji, image, audio } = word
const pathToImage = getImage(image)
const audioUrl = audio.file.url
const handlePlayAudio = () => {
myAudio.current.src = `http:${audioUrl}`
myAudio.current.play()
}
return (
<div
className="card"
onClick={handlePlayAudio}
onKeyDown={handlePlayAudio}
key={id}
>
<audio ref={myAudio} src={`http:${audio.file.url}`} />
{console.log(audio)}
{/*<Link key={id} to={`/${slug}`}>*/}
<GatsbyImage image={pathToImage} className="img" alt={english} />
<p>
<b>{english}</b> | {japanese} | {romaji}
</p>
</div>
)
})}
</div>
</Wrapper>
)
}
const Wrapper = styled.section`
.wrapper {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.card {
color: #333;
}
.card:hover {
opacity: 0.9;
cursor: pointer;
}
.img {
width: 100%;
}
p {
padding-top: 0.6rem;
font-size: 0.9rem;
}
.search-container {
margin: 0 0 1.6rem 0;
text-align: center;
}
input {
width: 100%;
padding: 0.3rem 0.5rem;
border: 1px solid #999;
border-radius: 0;
::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #bbb;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: #bbb;
}
::-ms-input-placeholder {
/* Microsoft Edge */
color: #bbb;
}
text-transform: none;
}
`
export default WordsList
Thanks,
Andy
I seem to have come up with an answer.
I moved the audio tag out from inside the map
onclick get the audio url out
Use that url value to update and play the audio tag
import React, { useState, useRef } from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
// import { Link } from "gatsby"
// import slugify from "slugify"
import styled from "styled-components"
const WordsList = ({ words = [] }) => {
const [searchField, setSearchField] = useState("")
const myAudio = useRef("")
const filteredWords = words.filter(word => {
return (
word.english.toLowerCase().includes(searchField) ||
word.japanese.toLowerCase().includes(searchField) ||
word.romaji.toLowerCase().includes(searchField)
)
})
const handleSearchChange = event => {
const searchField = event.target.value.toLowerCase()
setSearchField(searchField)
}
const handleAudio = url => {
myAudio.current.src = url
myAudio.current.play()
}
// const handleTest = test => console.log(test)
return (
<Wrapper>
<audio ref={myAudio} src={""} />
<div className="search-container">
<input
className="search-box"
type="search"
placeholder="search english, japanese, or romaji"
onChange={handleSearchChange}
/>
</div>
<div className="wrapper">
{filteredWords.map(word => {
const { id, english, japanese, romaji, image, audio } = word
const pathToImage = getImage(image)
const audioUrl = audio.file.url
return (
<div
className="card"
onClick={() => handleAudio(audio.file.url)}
key={id}
>
{/*<Link key={id} to={`/${slug}`}>*/}
<GatsbyImage image={pathToImage} className="img" alt={english} />
<p>
<b>{english}</b> | {japanese} | {romaji}
</p>
</div>
)
})}
</div>
</Wrapper>
)
}
const Wrapper = styled.section`
.wrapper {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.card {
color: #333;
}
.card:hover {
opacity: 0.9;
cursor: pointer;
}
.img {
width: 100%;
}
p {
padding-top: 0.6rem;
font-size: 0.9rem;
}
.search-container {
margin: 0 0 1.6rem 0;
text-align: center;
}
input {
width: 100%;
padding: 0.3rem 0.5rem;
border: 1px solid #999;
border-radius: 0;
::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: #bbb;
opacity: 1; /* Firefox */
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: #bbb;
}
::-ms-input-placeholder {
/* Microsoft Edge */
color: #bbb;
}
text-transform: none;
}
`
export default WordsList
Does anyone know how to map a function to react-spring-3d-carousel? This is the code I have but nothing seems to be working. My data structure for portfolioItems is fine and ImageMedia renders one card only instead of mapping each card to its respective place.
It seems I am only able to render one car successfully, but not sure what's wrong here. Maybe if someone can look at the mapping function they'll be able to see where I'm screwing up.
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import Carousel from "react-spring-3d-carousel";
import { config } from "react-spring";
function Rotate(props) {
const table = props.cards.map((element, index) => {
return { ...element, onClick: () => setGoToSlide(index) };
});
const [offsetRadius, setOffsetRadius] = useState(0);
const [showArrows, setShowArrows] = useState(false);
const [goToSlide, setGoToSlide] = useState(null);
const [cards] = useState(table);
useEffect(() => {
setOffsetRadius(props.offset);
setShowArrows(props.showArrows);
}, [props.offset, props.showArrows]);
return (
<CarouselWrapper>
<Carousel
slides={cards}
currentSlide={goToSlide}
offsetRadius={offsetRadius}
showNavigation={showArrows}
animationConfig={config.gentle}
/>
</CarouselWrapper>
);
}
export default Rotate;
const CarouselWrapper = styled.div`
width: (${(props) => props.width});
height: (${(props) => props.height});
margin: (${(props) => props.margin});
`;
import React from "react";
import styled from "styled-components";
import Rotate from "./props/Rotate";
import portfolioItems from "./data/portfolioItems";
import ImageMediaCard from "./ImageMediaCard";
import { v4 as uuidv4 } from "uuid";
function Portfolio() {
let cards = [
{
key: uuidv4(),
content: <ImageMediaCard alt="1" />,
},
{
key: uuidv4(),
content: <ImageMediaCard alt="2" />,
},
{
key: uuidv4(),
content: <ImageMediaCard alt="3" />,
},
{
key: uuidv4(),
content: <ImageMediaCard alt="4" />,
},
];
return (
<PortfolioWrapper id="portfolio">
<PortfolioHeading>Portfolio</PortfolioHeading>
<Rotate
cards={
{key: uuidv(),
content: {portfolioItems.map((portfolioItem) => (
<ImageMediaCard
key={portfolioItem.id}
image={portfolioItem.image}
alt={portfolioItem.alt}
title={portfolioItem.title}
description={portfolioItem.description}
website={portfolioItem.website}
source={portfolioItem.source}
/>
))}}}
height="500px"
width="80%"
margin="10px"
offset={2}
showArrows={false}
/>
</PortfolioWrapper>
);
}
export default Portfolio;
const PortfolioWrapper = styled.div`
max-width: 100vw;
padding-bottom: 120px;
background-color: lightgreen;
height: 900px;
#media (max-width: 768px) {
display: block;
display: inline-block;
}
`;
const PortfolioHeading = styled.h2`
font-size: 60px;
display: flex;
justify-content: center;
`;
const RowWrap = styled.div`
padding-top: 20px;
`;
const Row = styled.div`
display: flex;
justify-content: space-around;
padding-bottom: 30px;
margin-left: 30px;
margin-right: 30px;
#media (max-width: 768px) {
flex-direction: column;
}
`;
const Cards = styled(Card)`
/* display: flex; */
/* justify-content: space-around; */
`;
Any help would be greatly appreciated!
This may sound strange, maybe I completely get it wrong in the first place. But as I read some articles and react docs related to get the children and identify specific child via React.Component.map() however when I try this with my custom components, the child returns a stringify function as type. (I know that react does the stringify thing to prevent script injection). But I basically need to identify specific children that pass into the component and place them in the correct positions in another custom component. (materia_ui style).
<Card>
<CardTitle>
</CardTitle>
<CardContent>
</CardContent>
</Card>
The problem is I can't map passed children since the type has a string.
my environment uses
"react": "^17.0.2",
"#types/react": "^17.0.0",
"react-dom": "^17.0.2",
"#types/react-dom": "^17.0.0",
"typescript": "^4.1.2"
and this is what I have so far
type ExpandableCardProps = {
children: ReactElement<any> | ReactElement<any>[],
}
const ExpandableCard = ({children}: ExpandableCardProps) => {
React.Children.map(children, (child) => {
concole.log(child); // just can't map the child elements as described in some articales
})
// note that I need to identify the correct child to be render in correct position
render (
<div>
<div className="title-container">
// I want to render <ExpandableTitle> here
</div>
<div className="content-container">
// I want to render <ExpandableContent> here
</div>
<div className="content-other">
// may be some other elements here
</div>
</div>
);
}
export default ExpandableCardProps;
type CommonType = {
children: ReactNode;
}
export const ExpandableTitle ({children}:CommonType) => {
<div>
{children}
</div>
}
export const ExpandableContent ({children}:CommonType) => {
<div>
{children}
</div>
}
// usage
<ExpandableCard>
<ExpandableTitle>
/*{ some jsx here }*/
</ExpandableTitle>
<ExpandableContent>
/*{ some jsx here }*/
</ExpandableContent>
</ExpandableCard>
Here's what it looks like in the console
Here's an article I was referring to and which explained most closely what I need, but Can't use the pattern it explained since the type stringify thing, wonder it's with the React version or maybe as I mentioned earlier it's completely misunderstood by myself. I need some insight into this. How can I achieve something like this?
This seems to be working for me:
const ExpandableCard = ({children}) => {
const childArray = React.Children.toArray(children);
const expandableTitleIndex = childArray.findIndex(x => x.props.__TYPE === 'ExpandableTitle');
const expandableContentIndex = childArray.findIndex(x => x.props.__TYPE === 'ExpandableContent');
const additionalChildren = childArray.filter((_, index) => (index !== expandableTitleIndex && index !== expandableContentIndex));
return [childArray[expandableTitleIndex], childArray[expandableContentIndex], ...additionalChildren];
};
const App = () => {
return (
<ExpandableCard>
<div>Child 0 (div)</div>
<ExpandableContent>Child 1 (ExpandableContent)</ExpandableContent>
<ExpandableTitle>Child 2 (ExpandableTitle)</ExpandableTitle>
<div>Child 3 (div)</div>
</ExpandableCard>
);
};
const ExpandableTitle = ({children}) => (
<div>
{children}
</div>
);
ExpandableTitle.defaultProps = {
__TYPE: 'ExpandableTitle',
};
const ExpandableContent = ({children}) => (
<div>
{children}
</div>
);
ExpandableContent.defaultProps = {
__TYPE: 'ExpandableContent',
};
ReactDOM.render(<App />, document.querySelector("#app"));
Live on jsFiddle
After a few workarounds with the Neal Burns answer, I concluded with a typescript compatible solution.
I Will post it here since for someone it may be come in handy someday.
import React, { Children, ReactElement, ReactNode, useEffect, useRef, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './expandableCard.scss';
import { v4 as uuidv4 } from 'uuid'
const types = {
EXPANDABLE_CARD_HEADER: 'expandableCardTitle',
EXPANDABLE_CARD_CONTENT: 'expandableCardContent',
EXPANDABLE_CARD_FOOTER: 'expandableCardFooter',
EXPANDABLE_ITEMS: 'expandableItems',
}
type ExpandableCardProps = {
id?: string;
select?: boolean;
onSelect?: (id: string) => void;
children: ReactElement<ExpandableCardContentProps> | ReactElement<ExpandableCardContentProps>[];
}
const ExpandableCard = ({ id = uuidv4(), select = false, children, onSelect = () => { } }: ExpandableCardProps) => {
const transitionRef = useRef(null);
const [selected, setSelected] = useState(select);
const [expand, setExpand] = useState(false);
const headerElement = useRef<any>(null);
const contentElement = useRef<any>(null);
const expandableFooter = useRef<any>(null);
const expandableItems = useRef<any>(null);
const handleSelected = () => {
setSelected(!selected);
}
useEffect(() => {
if (selected) {
onSelect(id);
}
}, [id, onSelect, selected])
const handleExpand = () => {
setExpand(!expand);
}
Children.forEach(children, (child) => {
switch (child.props.__TYPE) {
case types.EXPANDABLE_CARD_HEADER:
headerElement.current = child;
break;
case types.EXPANDABLE_CARD_CONTENT:
contentElement.current = child;
break;
case types.EXPANDABLE_ITEMS:
expandableItems.current = child;
break;
case types.EXPANDABLE_CARD_FOOTER:
expandableFooter.current = child;
break;
default:
return <div></div>;
}
});
return (
<div className={`expandable-card ${selected ? 'expandable-card-selected' : ''}`}>
<div className={`expandable-card--content ${expand ? 'expandable-card--content-active' : ''}`}>
<div className="expandable-card--expand-button">
<button type="button" onClick={handleExpand}>expand</button>
</div>
{headerElement.current &&
<div className="expandable-card--header">
{headerElement.current}
</div>
}
{contentElement.current}
<div className="d-flex align-items-center mt-3">
<button
type="button"
className={`btn expandable-card--button ${selected ? 'expandable-card--button-active' : ''}`}
onClick={handleSelected}>
{selected && !}
</button>
{expandableFooter.current}
</div>
</div>
<CSSTransition
nodeRef={transitionRef}
in={expand}
timeout={500}
classNames={`expandable-card--drawer`}
mountOnEnter
unmountOnExit>
<div ref={transitionRef} className="expandable-card--drawer">
{expandableItems.current}
</div>
</CSSTransition>
</div >
);
}
type ExpandableCardContentProps = {
children: ReactNode,
__TYPE: string;
}
export const ExpandableCardHeader = ({ children }: ExpandableCardContentProps) => {
return (
<>
{children}
</>
);
}
ExpandableCardHeader.defaultProps = {
__TYPE: types.EXPANDABLE_CARD_HEADER,
}
export const ExpandableCardContent = ({ children }: ExpandableCardContentProps) => (
<>
{children}
</>
);
ExpandableCardContent.defaultProps = {
__TYPE: types.EXPANDABLE_CARD_CONTENT,
}
export const ExpandableCardFooter = ({ children }: ExpandableCardContentProps) => (
<>
{children}
</>
);
ExpandableCardFooter.defaultProps = {
__TYPE: types.EXPANDABLE_CARD_FOOTER,
}
export const ExpandableItems = ({ children }: ExpandableCardContentProps) => (
<>
{children}
</>
);
ExpandableItems.defaultProps = {
__TYPE: types.EXPANDABLE_ITEMS,
}
export default ExpandableCard;
Please note that this is the complete expandable component with animations in it
I'll put up the SCSS code also with this to be complete
.expandable-card {
display: flex;
flex-direction: column;
box-shadow: 0 0px 25px 0px rgba(0, 0, 0, 0.2);
width: 100%;
background-color: #fff;
border-radius: 14px;
position: relative;
&--expand-button {
position: absolute;
top: 10px;
right: 15px;
}
&-selected {
border-bottom: 15px solid yellow;
border-radius: 14px;
}
&--content {
padding: 18px 15px;
border-radius: 14px 14px 0 0;
transition: all 500ms ease-out;
&-active {
z-index: 1;
box-shadow: 0 7px 7px 0 rgba(0, 0, 0, 0.2);
}
}
&--drawer {
display: flex;
flex-direction: column;
width: 100%;
max-height: 0;
background-color: #fff;
padding: 18px 20px;
border-radius: 0 0 14px 14px;
overflow-x: hidden;
overflow-y: auto;
transition: all 500ms ease-out;
/* .classes for help dropdown animations */
&-enter-active {
max-height: 320px;
padding: 18px 20px;
}
&-enter-done {
max-height: 320px;
padding: 18px 20px;
}
&-exit-active {
max-height: 0;
padding: 0 20px;
}
&-exit-done {
max-height: 0;
padding: 0 20px;
}
}
&--header {
display: flex;
align-items: center;
}
&--button {
min-width: 43px;
height: 43px;
background: transparent;
border: 2px solid aqua;
box-sizing: border-box;
border-radius: 10px;
&:focus {
box-shadow: none;
}
&-active {
background-color: blue;
border: none;
}
}
}
I'm creating a DropDown List box and each item in the list has a remove (X) button to remove the item from the list. How is it possible to show the remove button "only" when the item is hovered over?
The current code shows the clear button each each item but I only want it to show when the item is hovered over
Sorry, here is the code
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const ListWrapper = styled.div`
position: absolute;
width: 16rem;
z-index: 1;
background: white;
&:hover {
cursor: pointer;
}
`;
const ListMenu = styled.div`
position: absolute;
width: 100%;
z-index: 1;
background: white;
overflow-x: hidden;
`;
const ListMenuHeader = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-end;
`;
const DropdownText = Text.Link.extend`
padding-top: 3rem;
`;
const DropdownButton = styled.div`
padding: 1 rem 0.75rem;
`;
const ListMenuItem = styled.div`
display: flex;
background-color: grey)};
color: grey};
>[name~=icon] {
right: 0rem;
border-radius: 0;
background: none;
align-items: right;
justify-content: right;
&:hover {
background-color: grey)};
}
&:focus {
outline: none;
}
`;
class ListListMenu extends React.Component {
static propTypes = {
id: PropTypes.string.isRequired,
text: PropTypes.node.isRequired,
items: PropTypes.arrayOf(PropTypes.any).isRequired,
component: PropTypes.func.isRequired,
selectedItem: PropTypes.any,
getItemProps: PropTypes.func.isRequired,
highlightedIndex: PropTypes.number,
closeListMenu: PropTypes.func.isRequired,
};
static defaultProps = {
selectedItem: null,
highlightedIndex: null,
}
onClearClick = (items,item1) => (item) => {
const index = items.indexOf(item1);
if (index > -1) {
items.splice(index, 1);
}
}
render() {
const {
id, text, items, component, selectedItem, getItemProps,
highlightedIndex, closeListMenu,
} = this.props;
return (
<ListWrapper id={id} >
<ListMenuHeader onClick={closeListMenu}>
<DropdownText>{text}</DropdownText>
<DropdownButton
id={`${id}-button`}
>
<Icon type="caret-up" appearance="neutral" />
</DropdownButton>
</ListMenuHeader>
<ListMenu>
{selectedItems.map((item, index) => (
<ListMenuItem
{...getItemProps({
item,
isActive: highlightedIndex === index,
isSelected: _.isEqual(selectedItem, item),
})}
key={index}
>
{React.createElement(component, { item })}
<Button // CLEAR BUTTON
name={item}
id={item}
icon="remove"
onClick={this.onClearClick(items, item)}
circle
display="flat"
appearance="disabled"
id="clear-search-button"
/>
</ListMenuItem>
))}
</ListMenu>
</ListWrapper>
);
}
}
export default ListListMenu;
Here is one way you could probably just have that "x" appear on hover.
Instead of looking for a "hover" event, what about looking for an "onmouseenter" event combined with "onmouseleave"?
Like so...
class Example extends React.Component {
onHover() {
this.refs.deleteX.style.display = "block";
}
onExit() {
this.refs.deleteX.style.display = "none";
}
render() {
return (
<div>
<input onmouseenter={ this.onHover } onmouseleave={ this.onExit } />
<p ref="deleteX">x</p>
</div>
)
}
}
Kind of like this post