I'm using recharts/ react to visualize some simple data, and running into a wall. I want to show the line + tooltip on ALL the graphs whenever a user hovers over any of the graphs. Been trying to use state or dispatch but running into a wall.
I've attached code snippets for my chart and dashboard files with the attempt at using dispatcher. I dont't know where in chart.js to actually call showTooltip. The functionality I want is to show the tooltips for all charts whenever a user hovers over any single chart. If one tooltip = active, I want all tooltips=active. Any guidance would be super helpful!
chart.js snippet
export default function Chart(props) {
const {state, dispatch} = useContext(AppContext);
const showTooltip = (newValue) => {
dispatch({ type: 'HOVER', data: newValue,});
};
const theme = createMuiTheme({
palette: {
primary: {
main: '#041f35'
},
secondary: {
main: '#5dc5e7'
}
}
});
return (
<React.Fragment>
<MuiThemeProvider theme={theme}>
<Title>{props.title}</Title>
<ResponsiveContainer width="100%" height="100%">
<LineChart
width={500}
height={300}
data={data}
margin={{
top: 5,
right: 5,
left: -35,
bottom: 5,
}}
>
<XAxis dataKey="time" />
<YAxis axisLine={false} tickLine={false}/>
<Tooltip />
<CartesianGrid vertical={false} stroke="#d3d3d3"/>
<Line type="monotone" dataKey="mktplace1" stroke={theme.palette.primary.main} activeDot={{ r: 8 }} />
<Line type="monotone" dataKey="mktplace2" stroke={theme.palette.secondary.main} />
</LineChart>
</ResponsiveContainer>
</MuiThemeProvider>
</React.Fragment>
);
}
dashboard.js file snippet
export const AppContext = React.createContext();
// Set up Initial State
const initialState = {
active: new Boolean(false),
};
function reducer(state, action) {
switch (action.type) {
case 'HOVER':
return {
active: new Boolean(true)
};
default:
return initialState;
}
}
export default function Dashboard() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className={classes.root}>
<CssBaseline />
<main className={classes.content}>
<Container maxWidth="lg" className={classes.container}>
<Grid container spacing={2}>
{/* Chart */}
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
<Grid item xs={12} sm={12} md={6} lg={6} xl={6}>
<Paper className={fixedHeightPaper}>
<AppContext.Provider value={{ state, dispatch }}>
<Chart title="Sales by Marketplace"/>
</AppContext.Provider>
</Paper>
</Grid>
</Grid>
</Container>
</main>
</div>
);
}
I know this was a long time ago, but there is a "syncId" property to put on your chart. All charts with the same syncId will show tooltips at the same time.
Related
I have a popup dialog where I get a bunch of values from the user and then get a response after making an API request. I put an inline conditional rendering on the dialog box as it should only render once chart data is updated from the response. However, the dialog never appears even if console.log shows the data is updated. I tried to use useEffect() with many functions but it did not work. Any idea how to refresh the data again?
Edit: Added only relevant code
const [barGraphData, setBarGraphData] = useState([]);
const funcSetBarGraphData = (newBarGraphData) => {
setBarGraphData(newBarGraphData);
};
const sendChartData = async () => {
let bar_response = await axios.post(
"http://localhost:8080/h2h-backend/bardata",
bar_data,
{headers: {'Content-Type': 'application/json'}}
).then(res=>{
const resData = res.data;
const resSubstring = "[" + resData.substring(
resData.indexOf("[") + 1,
resData.indexOf("]")
) + "]";
const resJson = JSON.parse(resSubstring);
console.log(typeof resJson, resJson);
funcSetBarGraphData(barGraphData);
}).catch(err=>{
console.log(err);
});
chartClickOpen();
};
Returning popup dialog with charts when button is clicked:
<StyledBottomButton onClick={sendChartData}>Submit</StyledBottomButton>
{barGraphData.length > 0 && <Dialog
fullScreen
open={openChart}
onClose={chartClickClose}
TransitionComponent={Transition}
>
<AppBar sx={{ position: 'relative' }}>
<Toolbar>
<Typography sx={{ ml: 2, flex: 1 }} variant="h6" component="div">
Analytics View
</Typography>
<IconButton
edge="start"
color="inherit"
onClick={chartClickClose}
aria-label="close"
>
<CloseIcon />
</IconButton>
</Toolbar>
</AppBar>
<Grid container spacing={2}>
<Grid item xs={8} sx={{ pt: 2 }}>
<BarChart width={730} height={250} data={barGraphData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="business_name" />
<YAxis />
<Tooltip />
<Legend />
<Bar dataKey="num_of_customers" fill="#8884d8" />
<Bar dataKey="sum_total_amount" fill="#82ca9d" />
</BarChart>
{/* <Bar options={set_bar.bar_options} data={set_bar.bar_data} redraw={true}/> */}
</Grid>
<Grid item xs={4} sx={{ pt: 2 }}>
{/* <Pie data={data2} /> */}
</Grid>
</Grid>
</Dialog>}
<StyledBottomButton onClick={handleClose}>Cancel</StyledBottomButton>
I am working on a Frontend Mentor project and I am trying to pass the hover status from the parent component (App) to the child component (Listing).
You can see that I have created a state object called hover inside the App component and passed it to the Listing component but when the hover object updates the css style is not applied as it should be on the Typography element inside the Listing component. Or at least there isn't a re-render if it does.
App.js
let [hover, updateHover] = useState(false);
updateHover = () => {
if(hover === false){hover = true; console.log(hover); return(0);}
else{hover = false; console.log(hover); return;}
}
return (
<ThemeProvider theme={ theme }>
<div style={{backgroundColor:'hsl(180, 52%, 96%)',}}>
<div style={{backgroundImage:`url(${headerImage})`, backgroundSize:'cover', height:'10rem'}}></div>
<Box display="flex" justifyContent="center" alignItems="center">
<Box style={{width:'70%', marginTop:'5rem'}}>
<Card style={styles.listing} onMouseEnter={updateHover} onMouseLeave={updateHover}>
<CardActionArea href="#" className="listingHover">
<Listing id="one" hover={hover} />
</CardActionArea>
</Card>
</Box>
</Box>
</div>
</ThemeProvider>
);
}
export default App;
Listing.js
function Listing(props) {
let id = props.id
let hover = props.hover
return (
<React.Fragment>
<Box className="listing" display="flex" sx={{backgroundColor:'#fff', width:'100%', height:'7.3rem'}}>
<Box>
<Typography variant="h4"><Link className={hover ? 'jobTitle': null} href="#" color="secondary" underline="none">{jobs[id].name}</Link></Typography>
</Box>
</Box>
</Box>
</React.Fragment>
)
}
export default Listing
I think your problem here is that you re declared updateHover.
You should change the name and have something like
const [hover, setHover] = useState(false);
const updateHover = () => {
if (!hover) {
console.log(hover);
setHover(true);
return(0)
} else {
setHover(false)
return;
}
}
as an aside why are you returning from the function? do you need the console.log? a cleaner option would be
const [hover, setHover] = useState(false);
const updateHover = () => setHover(!hover) // flips the current value i.e same as if (hover === true) setHover(false) else setHover(true);
return (
<ThemeProvider theme={ theme }>
<div style={{backgroundColor:'hsl(180, 52%, 96%)',}}>
<div style={{backgroundImage:`url(${headerImage})`, backgroundSize:'cover', height:'10rem'}}></div>
<Box display="flex" justifyContent="center" alignItems="center">
<Box style={{width:'70%', marginTop:'5rem'}}>
<Card style={styles.listing} onMouseEnter={updateHover} onMouseLeave={updateHover}>
<CardActionArea href="#" className="listingHover">
<Listing id="one" hover={hover} />
</CardActionArea>
</Card>
</Box>
</Box>
</div>
</ThemeProvider>
);
}
You are wrong
change state in the React.
Try like this.
const [hover, setHover] = useState(false);
updateHover = () => {
setHover(!hover)
}
or
const [hover, setHover] = useState(false);
return (
<ThemeProvider theme={ theme }>
<div style={{backgroundColor:'hsl(180, 52%, 96%)',}}>
<div style={{backgroundImage:`url(${headerImage})`, backgroundSize:'cover', height:'10rem'}}></div>
<Box display="flex" justifyContent="center" alignItems="center">
<Box style={{width:'70%', marginTop:'5rem'}}>
<Card style={styles.listing} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}>
<CardActionArea href="#" className="listingHover">
<Listing id="one" hover={hover} />
</CardActionArea>
</Card>
</Box>
</Box>
</div>
</ThemeProvider>
);
how do I set the number of cards per row? I need 4 cards to be in one row of mine.
I have been trying for hours.. can't figure out what i do wrong..
I was getting all sorts of compilation errors when i played around with this.
thanks!
:)
import { React } from 'react';
import { Card, Button, CardGroup } from 'react-bootstrap';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
const MyDishes = props => {
const { dishes } = props;
return (
<CardGroup style={{display: 'flex', flexDirection: 'row'}}>
{dishes.length > 0 &&
dishes.map(d => (
<Row xs={1} md={2} className="g-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col>
<Card key={d.id} style={{ width: '100em' }} style={{flex: 1}}>
<Card.Img variant="top" src={d.attributes.picture} />
<Card.Body>
<Card.Title>{d.attributes.name}</Card.Title>
<Card.Text>Possibly some text here</Card.Text>
<Link to={`/dishes/${d.id}`}>
<Button variant="primary">Full Recipe</Button>
</Link>
</Card.Body>
</Card>
</Col>
))}
</Row>
</CardGroup>
);
};
const mapStateToProps = state => {
return {
dishes: state.myDishes
};
};
export default connect(mapStateToProps)(MyDishes);
Above bootstrap 5
<Row className="row-cols-4">
...
</Row>
or any version
<Row className="row-cols-4">
{Array.from({ length: 4 }).map((_, idx) => (
<Col className="col-3">
...
</Col>
</Row>
I am trying to insert a google maps map in a React application. I would rather not use a non-official library (the ones that I have found lack documentation) and I have already managed inserting the map.
My problem is that the map is re-rendered every time the state of the parent component changes; although the values that change are completely irrelevant from what the map needs.
After a bit of research (I am new to React) I came across the React.memo() HOC which is supposed to prevent re-renders of child components when their props are unchanged. For some reason however I cannot get it to work correctly. Event when I insert the map inside a component with no props, any change in the parent state results in a re-render of the map.
Here is the parent component:
const CompanyDepotsPopup = () => {
const classes = useStyles();
const dispatch = useDispatch();
const open = useSelector((state) => selectIsDepotsPopupOpen(state));
const company = useSelector((state) => selectSelectedCompany(state));
const depotsStatus = useSelector((state) => selectDepotsStatus(state));
const {t} = useTranslation();
const [value, setValue] = useState(0);
const [phone, setPhone] = useState("");
const handleChange = (event, newValue) => {
setValue(newValue);
};
const closeModal = () => {
dispatch(toggleDepotsPopup({}));
}
useEffect(() => {
if (company) {
dispatch(depotsListed({companyId: company.id}));
}
}, [company])
if (!company) return <></>;
if (depotsStatus === "loading") {
return <CenteredLoader/>
}
function TabPanel(props) {
const {children, value, index} = props;
return (
<div
hidden={value !== index}
style={{height: "100%"}}
>
{value === index && (
<Box boxShadow={3} mt={1} ml={2} mr={2} height={"100%"}>
{children}
</Box>
)}
</div>
);
}
return (
<Dialog fullWidth={true} open={open}
aria-labelledby="company-dialog-popup">
<DialogTitle >
{company.name}
</DialogTitle>
<DialogContent style={{padding: 0, margin: 0}}>
<Divider/>
<Box mr={0} ml={0} mt={0} p={0} height="95%">
<div >
<AppBar position="static">
<Tabs value={value} onChange={handleChange} aria-label="depots tabs" centered>
<Tab label={t("Company's depots list")}/>
<Tab label={t("Add new depot")}/>
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
<DepotsList/>
</TabPanel>
<TabPanel value={value} index={1}>
<Paper>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} md={12} lg={12}>
<TextField
onChange={(event) => setPhone(event.target.value)}
id="phone"
label={t("Phone")}
type="text"
fullWidth
value={phone}
/>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={12}>
<div style={{height: "250px", display: "flex", "flexDirection": "column"}}>
<MyMap
id="myMap"
/>
</div>
</Grid>
<Grid item xs={12} sm={12} md={12} lg={12} align={"center"}>
<Button variant={"outlined"}>
{t("Save")}
</Button>
</Grid>
</Grid>
</Paper>
</TabPanel>
</div>
</Box>
</DialogContent>
<DialogActions style={{marginTop: "20px"}}>
<Button
variant={"outlined"}
onClick={closeModal}
color="secondary"
>
Done
</Button>
</DialogActions>
</Dialog>
)}
And here is the Map component:
import React, {useEffect} from "react";
const Map = ({id}) => {
const onScriptLoad = () => {
const map = new window.google.maps.Map(
document.getElementById(id),
{
center: {lat: 41.0082, lng: 28.9784},
zoom: 8
}
);
const marker = new window.google.maps.Marker({
position: {lat: 41.0082, lng: 28.9784},
map: map,
title: 'Hello Istanbul!'
});
}
useEffect(() => {
if (!window.google) {
const s = document.createElement("script");
s.type = "text/javascript";
s.src = "https://maps.google.com/maps/api/js?key=''"
const x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
s.addEventListener('load', e => {
onScriptLoad();
})
} else {
onScriptLoad();
}
}, []);
return (
<div style={{width: "100%", height: "100%"}} id={id}/>
);
}
const MyMap = React.memo(Map);
export default MyMap;
Every time setPhone is called when the user types the phone and the state changes, the map is re-rendered. Could someone explain to me why the React.memo does not work and how should I proceed in order to avoid re-rendering the map?
I think my guts feeling is this component
function TabPanel(props) {
const {children, value, index} = props;
return (
<div
hidden={value !== index}
style={{height: "100%"}}
>
{value === index && (
<Box boxShadow={3} mt={1} ml={2} mr={2} height={"100%"}>
{children}
</Box>
)}
</div>
);
}
This is defined inside a component, therefore the instance of this component keeps changing after any state change. In order to prevent it, move it outside of the component, like this
function TabPanel()
function CompanyDepotsPopup()
Instead of
function CompanyDepotsPopup() {
function TabPanel()
}
The reason is also your TabPanel wraps everything else.
I am getting the data from backend & I am passing the data to Product.js. But Cards are not coming only search bar is coming. I am able to see the data in console using console.log(this.state.products);. Imports are there.
Here is my Products.js file content.
import Product from "./Product";
class Products extends Component {
constructor() {
super();
this.state = {
products: [],
searchString: ""
};
}
componentDidMount() {
axios.get("http://localhost:9022/products/getAll").then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
}
render() {
return (
<div>
{this.state.products ? (
<div>
<TextField
style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange}
/>
<Grid container spacing={24} style={{ padding: 24 }}>
{this.state.products.map(currentProduct => (
<Grid item xs={12} sm={6} lg={4} xl={3}>
<Product products={currentProduct} />
</Grid>
))}
</Grid>
</div>
) : (
"No products found"
)}
</div>
);
}
}
export default Products;
Here is my Product.js file content.
const Product = props => {
return (
<div>
{props.product ? (
<Card>
<CardContent>
<Typography gutterBottom variant="headline" component="h2">
{props.product.fields.title}
</Typography>
<Typography component="p">{props.product.fields.id}</Typography>
</CardContent>
</Card>
) : null}
</div>
);
};
export default Product;
Its a typo. Its props.producs but not props.product. You are passing products as a prop to Product component but accessing as props.product so you need to access it using props.products. Try the below corrected code
const Product = (props) => {
return(
<div>
{ props.products ? (
<Card>
<CardContent>
<Typography gutterBottom variant="headline" component="h2">
{props.products.title}
</Typography>
<Typography component="p">
{props.products.id}
</Typography>
</CardContent>
</Card>
): null }
</div>
)
}
export default Product;
Also when you do .map or .forEach or for loop you should add unique id as key to the parent jsx element inside loop. iN your code you need to add unique like below
If you have unique id from each currentProduct then do this
{ this.state.products.map(currentProduct => (
<Grid key={currentProduct.id} item xs={12} sm={6} lg={4} xl={3}>
<Product products={currentProduct} />
</Grid>
))}
otherwise add index as key like below
{ this.state.products.map((currentProduct, index) => (
<Grid key={`Key_${index}`} item xs={12} sm={6} lg={4} xl={3}>
<Product products={currentProduct} />
</Grid>
))}