I'm trying to make a map that will show the name of the country when you hover your cursor. For this I am using react-simple-map with react-tooltip:
function OnHoverMap() {
const [content, setContent] = useState("");
return (
<div>
<Map setTooltipContent={setContent} />
<ReactTooltip>{content}</ReactTooltip>
</div>
)
}
const Map = ({ setTooltipContent }) => {
return (
<>
<ComposableMap data-tip="" projectionConfig={{ scale: 200 }}>
<ZoomableGroup>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map(geo => (
<Geography
key={geo.rsmKey}
geography={geo}
onMouseEnter={() => {
const { NAME } = geo.properties;
setTooltipContent(`${NAME}`);
}}
onMouseLeave={() => {
setTooltipContent("");
}}
style={{
default: {
fill: "#D6D6DA",
outline: "none"
},
hover: {
fill: "#F53",
outline: "none"
},
pressed: {
fill: "#E42",
outline: "none"
}
}}
/>
))
}
</Geographies>
</ZoomableGroup>
</ComposableMap>
</>
);
};
This works fine, but there is a problem when zooming and moving the map - the focus shifts to the overall map (ComposableMap), and the panel with country name appear above the map, not over the country:
On react-simple-maps website mentions, what is not possible to set data-tip on Geography directly.
Is there a way to solve this problem? Does it help if I make ComposableMap non-focusable?
Instead of adding "data-tip" to ComposableMap, add it to the outer div which is holding your Map and ReactTooltip.
Related
I have a scenario where I have 2 different components ( buttons and an info container). On each button click I am trying to display each matched info container. I am able to achieve the desired functionality in my buttons, but when I pass state back to my other component I am only able to display the matched index. My desired result is if I clicked a button in my nav and it has an active class all my "info container" should remain visible until the "active" class is toggled/removed.
JS:
...
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ onChange, id, styles, discription }) => {
const [toggleThisButton, setToggleThisButton] = useState(false);
const handleClick = (index) => {
setToggleThisButton((prev) => !prev);
onChange(index);
};
return (
<>
<Avatar
className={toggleThisButton ? styles.orange : ""}
onClick={() => handleClick(id)}
>
{id}
</Avatar>
{JSON.stringify(toggleThisButton)}
{/* {toggleThisButton && <div className={styles.info}>{discription}</div> } */}
</>
);
};
const ToggleContainer = ({ discription, className }) => {
return <div className={className}> Content {discription}</div>;
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [value, setValue] = useState(false);
const handleChange = (newValue) => {
setValue(newValue);
console.log("newValue===", newValue);
};
return (
<>
<div className={classes.wrapper}>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem
id={id}
styles={classes}
discription={d}
onChange={handleChange}
/>
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
{data.map((d, id) => {
return (
<ToggleContainer
className={
value === id
? clsx(classes.elWrapper, "active")
: classes.elWrapper
}
key={id}
styles={classes}
discription="Hello"
/>
);
})}
</div>
</>
);
}
Codesanbox:
https://codesandbox.io/s/pedantic-dream-vnbgym?file=/src/App.js:0-2499
Codesandbox : https://codesandbox.io/s/72166087-zu4ev7?file=/src/App.js
You can store the selected tabs in a state. That way you don't need to render 3 (or more) <ToggleContainer>. In <ToggleContainer> pass the selected tabs as props and render the selected tabs content in <ToggleContainer>.
import React, { useState } from "react";
import "./styles.css";
import { makeStyles } from "#material-ui/core/styles";
import Avatar from "#material-ui/core/Avatar";
import { deepOrange } from "#material-ui/core/colors";
import clsx from "clsx";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ onChange, id, styles, discription }) => {
const [toggleThisButton, setToggleThisButton] = useState(false);
const handleClick = (index) => {
onChange(discription, !toggleThisButton);
setToggleThisButton((prev) => !prev);
};
return (
<>
<Avatar
className={toggleThisButton ? styles.orange : ""}
onClick={() => handleClick(id)}
>
{id}
</Avatar>
{JSON.stringify(toggleThisButton)}
{/* {toggleThisButton && <div className={styles.info}>{discription}</div> } */}
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
// action : False -> Remove, True -> Add
const handleChange = (val, action) => {
let newVal = [];
if (action) {
// If toggle on, add content in selected state
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log(newVal);
setSelected(newVal);
};
return (
<>
<div className={classes.wrapper}>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem
id={id}
styles={classes}
discription={d}
onChange={handleChange}
/>
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</>
);
}
I want to make transition view in my React Native project. In Picture component I always get error: undefined is not an object (evaluating 'info.images[0]'). When I console.log info.images[0] or info.images[0].image I got normal link for picture not undefined.
My FavouritePlaces.tsx component:
const FavouritePlaces = ({navigation}: HomeNavigationProps<"FavouritePlaces">) => {
const[markers, setMarkers] = useState([]);
useEffect(() => {
const getFavourites = async () => {
let keys = []
try {
keys = await AsyncStorage.getAllKeys()
} catch (e) {
// read key error
}
let values
try {
values = await AsyncStorage.multiGet(keys)
setMarkers(values)
console.log('ValuesFromAsyncStorage', values)
} catch(e) {
// read error
}
}
getFavourites();
}, [])
const transition = (
<Transition.Together>
<Transition.Out type='fade' />
<Transition.In type='fade' />
</Transition.Together>
);
const list = useRef<TransitioningView>(null);
const theme = useTheme()
const width = (wWidth - theme.spacing.m * 3) / 2;
const [footerHeight, setFooterHeight] = useState(0);
return (
<Box flex={1} backgroundColor="background">
<Header
title="Избранные места"
left={{icon: 'menu', onPress: () => navigation.openDrawer()}}
right={{icon: 'shopping-bag', onPress: () => true}}
/>
<Box flex={1}>
<ScrollView contentContainerStyle={{
paddingHorizontal: theme.spacing.m,
paddingBottom: footerHeight
}}>
<Transitioning.View ref={list} transition={transition}>
{markers ?
<Box flexDirection='row'>
<Box marginRight='s'>
{markers
.filter((_, i) => i % 2 !== 0).map((currentMarker) => <Picture key={currentMarker}
place={currentMarker}
width={width}/>)}
</Box>
<Box>
{markers
.filter((_, i) => i % 2 === 0).map((currentMarker) => <Picture key={currentMarker}
place={currentMarker}
width={width}/>)}
</Box>
</Box> : undefined}
</Transitioning.View>
</ScrollView>
<TopCurve footerHeight={footerHeight}/>
<Box position='absolute' bottom={0} left={0} right={0} onLayout={({
nativeEvent: {
layout: {height},
}
}) => setFooterHeight(height)}>
<Footer label="Удалить из избранного" onPress={() => {
list.current?.animateNextTransition();
// setPlaces(places.filter((place => !place.selected)))
// console.log(defaultPictures)
}}/>
</Box>
</Box>
</Box>
)
}
export default FavouritePlaces
Picture component:
const Picture = ({
place,
width
}: PictureProps) => {
const info = JSON.parse(place[1])
console.log('currentInfo', info.images[0].image)
const [selected, setSelected] = useState(false);
return (
<BorderlessTap onPress={() => {
setSelected(prev => !prev);
place.selected = !place.selected;
}}>
{/*<Text>{place.description}</Text>*/}
{info.images[0].image ?
<ImageBackground
style={{backgroundColor: 'black', borderRadius: '3%', marginBottom: '3%', alignItems: 'flex-end', padding: '3%', width, height: 80}}
source={{ uri: info.images[0].image }}>
{selected && (
<RoundedIcon
backgroundColor="primary"
color="background"
size={24} name="check"
/>
)}
</ImageBackground> : undefined}
</BorderlessTap>
)
}
export default Picture
You are rendering the Picture component before getFavourites has populated markers.
Your logic markers ? ... : undefined is always going to be truthy because [] evaluates to true. You should do something like markers.length ? ... : undefined.
React/NextJS newbie issue any help is much appreciated. I have a header component built on React/NextJS and Material. This return statement works like a charm.
return (
<header>
<HideOnScroll {...props}>
<AppBar className={header}>
{mobileView ? displayMobile() : displayDesktop()}
</AppBar>
</HideOnScroll>
</header>
);
But as soon as I add a basic div, as shown below, I get "TypeError: children.props is undefined". I'm just trying to add a full width div with a height of 10px to the top of the header for some trim but can't even get a div to render. I've tried adding {...props} to the div but still no luck.
return (
<header>
<HideOnScroll {...props}>
<div>test</div>
<AppBar className={header}>
{mobileView ? displayMobile() : displayDesktop()}
</AppBar>
</HideOnScroll>
</header>
);
Full source code below:
import {
AppBar,
Toolbar,
Typography,
makeStyles,
Button,
IconButton,
Drawer,
Link,
MenuItem,
Slide,
useScrollTrigger
} from "#material-ui/core";
import MenuIcon from "#material-ui/icons/Menu";
import React, { useState, useEffect } from "react";
import NextLink from "next/link";
const headersData = [
{
label: "Home",
href: "/",
},
{
label: "Blog",
href: "/blog/ryan-test",
},
];
const useStyles = makeStyles(() => ({
header: {
backgroundColor: "#400CCC",
paddingRight: "79px",
paddingLeft: "118px",
"#media (max-width: 900px)": {
paddingLeft: 0,
},
},
logo: {
fontFamily: "Work Sans, sans-serif",
fontWeight: 600,
color: "#FFFEFE",
textAlign: "left",
},
menuButton: {
fontFamily: "Open Sans, sans-serif",
fontWeight: 700,
size: "18px",
marginLeft: "38px",
},
toolbar: {
display: "flex",
justifyContent: "space-between",
},
drawerContainer: {
padding: "20px 30px",
},
topTrim: {
height: "10px",
width: "100%",
backgroundColor: "red",
}
}));
export default function Header(props) {
const { header, logo, menuButton, toolbar, drawerContainer, topTrim } = useStyles();
const [state, setState] = useState({
mobileView: false,
drawerOpen: false,
});
const { mobileView, drawerOpen } = state;
const TopTrim = () => {
return <div>Hello</div>;
}
useEffect(() => {
const setResponsiveness = () => {
return window.innerWidth < 900
? setState((prevState) => ({ ...prevState, mobileView: true }))
: setState((prevState) => ({ ...prevState, mobileView: false }));
};
setResponsiveness();
window.addEventListener("resize", () => setResponsiveness());
}, []);
const HideOnScroll = (props) => {
const { children } = props;
const trigger = useScrollTrigger();
return (
<Slide appear={false} direction="down" in={!trigger}>
{children}
</Slide>
);
}
const displayDesktop = () => {
return (
<Toolbar className={toolbar}>
{femmecubatorLogo}
<div>{getMenuButtons()}</div>
</Toolbar>
);
};
const displayMobile = () => {
const handleDrawerOpen = () =>
setState((prevState) => ({ ...prevState, drawerOpen: true }));
const handleDrawerClose = () =>
setState((prevState) => ({ ...prevState, drawerOpen: false }));
return (
<Toolbar>
<IconButton
{...{
edge: "start",
color: "inherit",
"aria-label": "menu",
"aria-haspopup": "true",
onClick: handleDrawerOpen
}}
>
<MenuIcon />
</IconButton>
<Drawer
{...{
anchor: "left",
open: drawerOpen,
onClose: handleDrawerClose,
}}
>
<div key="test1" className={drawerContainer}>{getDrawerChoices()}</div>
</Drawer>
<div key="test2">{femmecubatorLogo}</div>
</Toolbar>
);
};
const getDrawerChoices = () => {
return headersData.map(({ label, href }) => {
return (
<NextLink href={href} key={label} passHref>
<MenuItem>{label}</MenuItem>
</NextLink>
);
});
};
const femmecubatorLogo = (
<Typography variant="h6" component="h1" className={logo}>
Femmecubator
</Typography>
);
const getMenuButtons = () => {
return headersData.map(({ label, href }) => {
return (
<NextLink href={href} key={label} passHref>
<Button
{...{
key: label,
color: "inherit",
to: href,
className: menuButton,
}}
>
{label}
</Button>
</NextLink>
);
});
};
return (
<header>
<HideOnScroll {...props}>
<div>test</div>
<AppBar className={header}>
{mobileView ? displayMobile() : displayDesktop()}
</AppBar>
</HideOnScroll>
</header>
);
}
Pass it like this :
<HideOnScroll {...props}>
<>
<div>test</div>
<AppBar className={header}>
{mobileView ? displayMobile() : displayDesktop()}
</AppBar>
</>
</HideOnScroll>
Ultimately, I ended up in moving the div to inside of the AppBar. I now have to adjust AppBar padding and spacing of anything living inside of AppBar to compensate for the div also living there but it is working.
return (
<header>
<HideOnScroll {...props}>
<AppBar className={header}>
<div className={topTrim}>This is a test</div>
{mobileView ? displayMobile() : displayDesktop()}
</AppBar>
</HideOnScroll>
</header>
);
I have a motion value that is updated whenever a component is being hovered. I thought that framer-motion automatically took care of animating the value to its new state but apparently it doesn't. How can I transition the new values of my motion value? It is also important to note that I'm aware of the whileHover prop that exists but I don't want to use it here.
This example component illustrates my situation:
const Component = () => {
const scale = useMotionValue(defaultScale);
return (
<motion.img
style={{ scale }}
onMouseEnter={ () => scale.set(1.35) }
onMouseLeave={ () => scale.set(defaultScale) }
/>
)
}
Have you tried useSpring?
const Component = () => {
const scale = useSpring(1);
return (
<motion.div
style={{ scale, background: "tomato", width: "100px", height: "100px" }}
onMouseEnter={() => scale.set(1.35)}
onMouseLeave={() => scale.set(1)}
/>
);
};
Docs: https://www.framer.com/api/motion/motionvalue/#usespring
Or you can also use useAnimation to create custom controls and transitions:
const Component2 = () => {
const controls = useAnimation();
return (
<motion.div
style={{ background: "blue", width: "100px", height: "100px" }}
animate={controls}
onMouseEnter={() => controls.start({ scale: 1.35 })}
onMouseLeave={() => controls.start({ scale: 1 })}
/>
);
};
Docs: https://www.framer.com/api/motion/animation/#animation-controls
Codesandbox with both examples: https://codesandbox.io/s/httpsstackoverflowcomquestions64077992-j63qv?file=/src/App.js
I am having a parent component and a child component. I want to call a function from my child in my parent component. I am using ref and it works fine, but just for my last element in my array...
Parent:
onClick = () => {
this.child.handleClose(); // do stuff
};
var items = [];
tracks.filter(searchingFor(term)).map(function (title, i) {
items.push(
<div>
<ItemViewAll
onRef={(ref) => (this.child = ref)}
triggerOpen={this.handleOpenParent.bind(this)}
triggerClose={this.handleCloseParent.bind(this)}
key={title.id}
title={title.title}
/>
</div>
);
}, this);
ItemViewAll is imported from my Child component, where the needed functions are located.
Child:
componentDidMount() {
this.props.onRef(this)
}
componentWillUnmount() {
this.props.onRef(undefined)
}
handleClose = () => {
this.setState({ open: false });
this.props.triggerClose();
}
It seems like that the ref works only for the last element in my items[]...
Do you have any suggestions on how to make it work for every element in my array?
Here is my full Parent Compoenent, could be heldpful. Still have no clue how to set the ref...
Parent (complete):
const { scaleDown } = transitions;
function searchingFor(term) {
return function (x) {
return (
x.title.toLowerCase().includes(term.toLowerCase()) ||
x.body.toLowerCase().includes(term.toLowerCase())
);
};
}
class ViewAll extends React.Component {
constructor(props) {
super(props);
this.state = {
term: "",
mounted: false,
tracks: [],
hasMoreItems: true,
};
this.searchHandler = this.searchHandler.bind(this);
this.focus = this.focus.bind(this);
}
loadContent() {
var requestUrl = this.props.url;
fetch(requestUrl)
.then((response) => {
return response.json();
})
.then((tracks) => {
this.setState({ tracks: this.state.tracks.concat(tracks) });
})
.catch((err) => {
console.log("There has been an error");
});
}
componentDidMount() {
var requestUrl = this.props.url;
fetch(requestUrl)
.then((response) => {
return response.json();
})
.then((data) => {
this.setState({ tracks: data });
})
.catch((err) => {
console.log("There has been an error");
});
window.scrollTo(0, 0);
this.focus();
}
handleOpenParent() {
this.setState({ show: true });
}
handleCloseParent() {
this.setState({ show: false });
}
searchHandler(event) {
this.setState({ term: event.target.value });
}
focus() {
this.textInput.focus();
}
onClick = () => {
this.child.handleClose(); // do stuff
};
render() {
const { term, data, tracks } = this.state;
const loader = <div className="loader"></div>;
var items = [];
tracks.filter(searchingFor(term)).map(function (title, i) {
items.push(
<div>
<MuiThemeProvider>
<Paper
style={{
borderRadius: "2em",
background:
"linear-gradient(to right, #82f2da 30%, white 100%)",
}}
zDepth={1}
>
<ItemViewAll
onRef={(ref) => (this.child = ref)}
triggerOpen={this.handleOpenParent.bind(this)}
triggerClose={this.handleCloseParent.bind(this)}
key={title.id}
title={title.title}
score={title.vote_average}
overview={title.body}
backdrop={title.image}
description={title.description}
messenger={title.messenger}
twitter={title.twitter}
discord={title.discord}
slack={title.slack}
kik={title.kik}
telegram={title.telegram}
/>
</Paper>
</MuiThemeProvider>
</div>
);
}, this);
return (
<div>
<header className="Header">
{this.state.show && (
<div
style={{ height: 50, width: 50, background: "red" }}
onClick={this.onClick}
></div>
)}
</header>
<div>
<Media query="(max-width: 599px)">
{(matches) =>
matches ? (
<div style={{ marginTop: 100 }}>
<div
style={{
width: "90%",
marginLeft: "auto",
marginRight: "auto",
marginBottom: 50,
}}
>
<MuiThemeProvider>
<TextField
hintText="Welcher Bot darf es sein?"
type="Text"
onChange={this.searchHandler}
value={term}
fullWidth={true}
underlineFocusStyle={{
borderColor: "#82f2da",
borderWidth: 3,
}}
underlineStyle={{
borderColor: "#82f2da",
borderWidth: 1.5,
top: "40px",
}}
hintStyle={{ fontSize: 30, fontFamily: "Anton" }}
inputStyle={{ fontSize: 30, fontFamily: "Anton" }}
ref={(input) => {
this.textInput = input;
}}
style={{ caretColor: "#82f2da" }}
/>
</MuiThemeProvider>
</div>
</div>
) : (
<div style={{ marginTop: 130 }}>
<div
style={{
width: "80%",
marginLeft: "auto",
marginRight: "auto",
marginBottom: 70,
}}
>
<MuiThemeProvider>
<TextField
hintText="Welcher Bot darf es sein?"
type="Text"
onChange={this.searchHandler}
value={term}
fullWidth={true}
underlineFocusStyle={{
borderColor: "#82f2da",
borderWidth: 3,
}}
underlineStyle={{
borderColor: "#82f2da",
borderWidth: 1.5,
top: "50px",
}}
hintStyle={{ fontSize: 40, fontFamily: "Anton" }}
inputStyle={{ fontSize: 40, fontFamily: "Anton" }}
ref={(input) => {
this.textInput = input;
}}
style={{ caretColor: "#82f2da" }}
/>
</MuiThemeProvider>
</div>
</div>
)
}
</Media>
<Media query="(max-width: 599px)">
{(matches) =>
matches ? (
<InfiniteScroll
pageStart={1}
loadMore={this.loadContent.bind(this)}
hasMore={this.state.hasMoreItems}
loader={loader}
initialLoad={false}
>
<StackGrid
columnWidth={180}
gutterHeight={10}
gutterWidth={10}
duration={1500}
monitorImagesLoaded={true}
easing={easings.quadInOut}
appear={scaleDown.appear}
appeared={scaleDown.appeared}
enter={scaleDown.enter}
entered={scaleDown.entered}
leaved={scaleDown.leaved}
>
{items}
</StackGrid>
</InfiniteScroll>
) : (
<InfiniteScroll
pageStart={10}
loadMore={this.loadContent.bind(this)}
hasMore={this.state.hasMoreItems}
loader={loader}
initialLoad={true}
>
<StackGrid
columnWidth={180}
gutterHeight={80}
gutterWidth={80}
duration={1500}
monitorImagesLoaded={true}
easing={easings.quadInOut}
appear={scaleDown.appear}
appeared={scaleDown.appeared}
enter={scaleDown.enter}
entered={scaleDown.entered}
leaved={scaleDown.leaved}
>
{items}
</StackGrid>
</InfiniteScroll>
)
}
</Media>
</div>
</div>
);
}
}
export default ViewAll;
The onClick function schould be called when clicked on the div in my header. But for all elements in my array and not just for the last one.... The onClick function calls again the handleClose() in my Child.
I believe the reason you always get the last element instance, it is because you are iterating an array and for every item, you render a new and overwrite the this.child variable with a new reference of .
But even though, you should not create a ref for each item of element. If you want to get the onClick event, you have to implement this event on the component . If the component is from a third party library, check the documentation to see how to handle events. Usually, it is with onClick event.
hard to say for sure without seeing more of parent component code (i think you may have cut something from that accidentally), so it's just a guess, but you are probably overwriting your ref while mapping over array, hence it's working only for last element. you can find some solutions for that here.