Trying to learn React and following this tutorial:https://youtu.be/GDa8kZLNhJ4?t=3547
There you have a App.js component that makes Travel Advisor API call that populates the data object:
import React, { useState, useEffect } from "react";
import { CssBaseline, Grid } from "#material-ui/core";
import { getPlacesData } from "./api";
import Header from "./components/Header/Header";
import List from "./components/List/List";
import Map from "./components/Map/Map";
import { PlaceSharp } from "#material-ui/icons";
const App = () => {
const [places, setPlaces] = useState([]);
const [coordinates, setCoordinates] = useState({});
const [bounds, setBounds] = useState(null);
useEffect(() => {
getPlacesData().then((data) => {
console.log(data) // data is there
setPlaces(data);
});
}, []);
return (
<>
<CssBaseline />
<Header />
<Grid container spacing={3} style={{ width: "100%" }}>
<Grid item xs={12} md={4}>
<List />
</Grid>
<Grid item xs={12} md={8}>
<Map
setCoordinates={setCoordinates}
setBounds={setBounds}
coordinates={coordinates}
/>
</Grid>
</Grid>
</>
);
};
export default App;
The following props are passed to Map component:
<Map
setCoordinates={setCoordinates}
setBounds={setBounds}
coordinates={coordinates}
/>
In Map component it gets passed to GoogleMapReact component:
import React from 'react'
import GoogleMapReact from 'google-map-react'
import {Paper, Typography, useMediaQuery} from '#material-ui/core'
import LocationOnOutlinedIcon from '#material-ui/icons/LocationOnOutlined'
import Rating from "#material-ui/lab"
import useStyles from './styles'
const Map = ({setCoordinates, setBounds, coordinates}) => {
const classes = useStyles()
const isMobile = useMediaQuery('(min-width: 600px)')
//console.log(coordinates)
//const coordinates= {lat: 0, lng: 0}
return (
<div className={classes.mapContainer}>
<GoogleMapReact
bootstrapURLKeys={{ key: 'xxxxxxxxxxxxxxxxxxxx'}}
defaultCenter ={coordinates}
center = {coordinates}
defaultZoom = {14}
margin = {[50, 50, 50, 50]}
options = {''}
onChange = {(e) => {
console.log(e) // this is empty but it should have data
setCoordinates({lat: e.center.lat, lng: e.center.lng});
}}
onChildClick = {''}
>
</GoogleMapReact>
</div>
)
}
export default Map
For some reason coordinates prop is not populated in onChange as seen in the video.
I double check the code and cannot find what is stopping it from getting the data.
The API call returns a bunch of restaurants like this:
So it is fetching the data. Only props {coordinates} not getting filled.
Can you see where can be the issue?
There are two pieces of state that handle some state. Those are places and coordinates. Once the App component is loaded, it tries to fetch places and update its state, triggering a re rendering. So far, so good.
The Map Component receives as prop the value of coordinates. coordinates never changes in the flow of the snippet that you posted. Maybe you want to fetch some coordinates from another endpoint? Or maybe from the places data, map through it and set a new state?. Same applies for bounds.
What it looks like it is missing is a call to setCoordinates and setBounds with the new values.
Related
I'm trying to make a react app that loads a google map.
but somehow the map doesn't show and not an error was reported in console logs....
import React from 'react'
import GoogleMapReact from 'google-map-react'
import {Paper, Typography, useMediaQuery} from '#material-ui/core'
import LocationOnOutlinedIcon from '#material-ui/icons/LocationOnOutlined'
import Rating from '#material-ui/lab'
import useStyles from './styles'
const Map = () => {
const classes = useStyles()
const isMobile = useMediaQuery('(min-width:600px)')
const coordinates = { lat:0, lng: 0}
return (
<div className={classes.mapContainer}>
<GoogleMapReact
bootstrapURLKeys={{ key: 'AIzaSyBrSAzFufdmJBVojpd7idemPVGp8HskFKY' }}
defaultCenter={coordinates}
center={coordinates}
defaultZoom={14}
margin={[50,50,50,50]}
>
</GoogleMapReact>
</div>
)
}
export default Map
ㄴ this is Map.js file
import Header from "./components/Header/Header";
import {List} from "./components/List/List";
import Map from "./components/Map/Map";
import {CssBaseline, Grid} from '#material-ui/core';
function App() {
return (
<div className="App">
<CssBaseline></CssBaseline>
<Header/>
<Grid container spacing={3} style={{ width: '100%'}}>
<Grid item xs={12} md={4}>
<List/>
</Grid>
<Grid item xs={12} md={8}>
<Map/>
</Grid>
</Grid>
</div>
);
}
export default App;
ㄴthis is App.js file
enter image description here
What's causing the error(if that is an error)?
j
How can I make the map show up in my app?
I am using react-perfect-scrollbar to show images list.
Inside the perfect scroll bar, I am going to lazy load images. But it won't work.
import React, { useState, useCallback, useEffect } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar';
import useIsMountedRef from 'src/hooks/useIsMountedRef';
import LazyLoad from 'react-lazyload';
import {
Box,
Button,
Link,
List,
ListItem,
ListItemIcon,
ListItemText,
CircularProgress,
Typography,
makeStyles
} from '#material-ui/core';
const isMountedRef = useIsMountedRef();
const [images, setImages] = useState([]);
const getImages = useCallback(async () => {
try {
setLoading(true);
const response = await axios.get(`${backendUrl}/images/get/images`);
if (isMountedRef.current) {
setImages(response.data.projectImages);
}
} catch (err) {
console.error(err);
} finally {
if (isMountedRef.current) {
setLoading(false);
}
}
}, [isMountedRef]);
<PerfectScrollbar options={{ suppressScrollX: true }}>
<List className={classes.list}>
{images.map((image, i) => (
<ListItem
divider={i < images.length - 1}
key={i}
className={classes.listItem}
>
<ListItemIcon>
<LazyLoad height={90} key={i} overflow>
<img
src={`${awsS3Url}/${image.Key}`}
className={classes.listImage}
onClick={() => onSelect(`${awsS3Url}/${image.Key}`)}
/>
</LazyLoad>
</ListItemIcon>
<ListItemText
primary={GetFilename(image.Key)}
primaryTypographyProps={{ variant: 'h5' }}
secondary={bytesToSize(image.Size)}
className={classes.listItemText}
/>
<MoreButton
handleArchive={() => handleRemoveOne(image)}
/>
</ListItem>
))}
</List>
</PerfectScrollbar>
Some of images(the first view without scrolling) are showing.
When I scroll, the images won't load, only the list contents without images are showing.
What did I code wrong?
I had the same problem. The issue is that react-lazyload is trying to find a container that has its overflow property set to scroll or auto, but perfect-scrollbar sets its overflow property to hidden and handles the scrolling manually instead. So we have to tell react-lazyload manually which container it should monitor for scroll events.
This can, by documentation, be done in two ways; by passing an HTMLElement or a query selector string. Alas, there seems to be a bug in the library that causes the property to be ignored if it is not a string (https://github.com/twobin/react-lazyload/blob/055405125d0313014f0951cffc78345297f10a08/lib/index.js#L261) so currently the only way is to pass a query selector string.
But when I tried to pass a query selector string targeting the perfect-scrollbars container, it seems that the container might not always be there yet when react-lazyload attaches its event listeners, so we must check that the container is actually there before we initialize the LazyLoad-container.
So the relevant code is:
import React, { ReactElement, useRef, useState, useEffect } from 'react';
import PerfectScrollbar from 'react-perfect-scrollbar'
import LazyLoad from 'react-lazyload';
export default (): ReactElement => {
// Get a reference to the wrapper element so we know when it is created
const scrollbarWrapperRef = useRef(null);
// Initialize a state setter to notify the view when the scrollParent becomes available
const [scrollParent, setScrollParent] = useState<HTMLElement|null>(null);
// Adjust this selector to your liking
const scrollParentSelector = '#scrollbar-wrapper .scrollbar-container';
// Here we make sure that the PerfectScrollbar container is actually available before we let the content and the LazyLoads be created.
useEffect(() => {
const scrollParentElement = document.querySelector(scrollParentSelector);
if (scrollParentElement) {
setScrollParent(scrollParentElement);
}
}, [scrollbarWrapperRef.current]);
// The relevant DOM
return (
<div ref={scrollbarWrapperRef} id="scrollbar-wrapper">
<PerfectScrollbar>
{ scrollParent &&
<List>
{images.map((image, i) => (
<LazyLoad scrollContainer={scrollParentSelector}>
<img src="..." />
</LazyLoad>
))}
</List>
}
</PerfectScrollbar>
</div>
);
}
Developed with react and typescript.
Now the card is shown or hidden when you click on the div tag.
I want to hide the Card when it is displayed, even if another place other than the div tag is pressed.
import React, { FunctionComponent, useState } from 'react';
import { Card } from 'components/atoms/Card';
import { Display } from 'components/atoms/Display';
const Test: FunctionComponent = () => {
const [isDisplay, setIsDisplay] = useState(false);
const onClick = () => {
setIsDisplay(!isDisplay);
};
return (
<>
<div onClick={onClick} style={{ width: '100px', height: '100px' }}>
display Card
</div>
<Display enabled={isDisplay}>
<Card width={100} height={100}></Card>
</Display>
</>
);
};
export default Test;
Try this in your onClick method. It looks like you need to access the current state's value and update it.
setIsDisplay(state => !state);
It's explained here in the React docs.
https://reactjs.org/docs/hooks-reference.html#functional-updates
Is there a way to setState of one state with the state of another state? For example below in _updateData()...on click the geojson state gets the state of newGeojson? The goal is to be able to change the data={geojson} on click of the button (not showing the Geojson files in the example but let's say they exist in GeoJSON and newGeojson. I pull JSON from a firebase then convert it to GeoJSON in componentDidMount to create the geojson states). Thanks for the help. Hopefully, I am approaching this the right way.
import React from 'react';
import ReactMapGL, {Source, Layer} from 'react-map-gl';
class Map extends React.Component {
state = {
geojson: null,
newGeojson: null,
};
_updateData() {
// In here set the state of geojson to the state of newGeojson so the data={geojson} updates on click, something like setState{geojson} = newGeojson
}
render() {
const {geojson} = this.state;
return (
<ReactMapGL latitude={37.78} longitude={-122.41} zoom={8}>
<Source id="my-data" type="geojson" data={geojson}>
<Layer
id="point"
type="circle"
paint={{
'circle-radius': 10,
'circle-color': '#007cbf'
}} />
</Source>
<button onClick={this._updateData()}>Update</button>
</ReactMapGL>
);
}
}
Like that:
import React, { useState } from 'react';
import ReactMapGL, {Source, Layer} from 'react-map-gl';
const Map = () => {
const [geojson, setGeojson] = useState(null)
const updateData = () => {
// for example
fetch('url').then(resp => resp.json()).then(resp => {
setGeojson(resp.data)
})
}
return (
<ReactMapGL latitude={37.78} longitude={-122.41} zoom={8}>
<Source id="my-data" type="geojson" data={geojson}>
<Layer
id="point"
type="circle"
paint={{
'circle-radius': 10,
'circle-color': '#007cbf'
}} />
</Source>
<button onClick={updateData}>Update</button>
</ReactMapGL>
);
}
It's using hooks. For old syntax you probably want this.setState({geojson: newGeojson}) (https://reactjs.org/docs/react-component.html#setstate)
I am using material UI with React. I have a modal component and a ButtonAppBar. Inside the ButtonAppBar there is a shopping cart icon that I added. I am still a bit new to React and would like to know the best way to display the modal when the shopping cart is clicked. Thanks in advance.
So here is my App.js:
import React, { Component } from 'react';
import { BrowserRouter } from 'react-router-dom';
import { Route } from 'react-router-dom';
import './App.css';
import ShopHome from './containers/ShopHome';
import ButtonAppBar from './components/ButtonAppBar';
import SimpleModalWrapped from './containers/ShoppingCartModal';
class App extends Component {
handle
render() {
return (
<BrowserRouter>
<div>
<ButtonAppBar />
<SimpleModalWrapped />
<Route exact path="/" component={ShopHome} />
</div>
</BrowserRouter>
);
}
}
export default App;
Here is my ButtonAppBar:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import MenuIcon from '#material-ui/icons/Menu';
import SvgIcon from '#material-ui/core/SvgIcon';
import ShoppingCart from '#material-ui/icons/ShoppingCart';
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
appBar: {
marginBottom: 50,
}
};
function ButtonAppBar(props) {
const { classes } = props;
return (
<div className={classes.root}>
<AppBar style={styles.appBar} position="static">
<Toolbar>
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" className={classes.grow}>
Velo-Velo
</Typography>
<Button color="inherit">Checkout</Button>
<SvgIcon>
<ShoppingCart />
</SvgIcon>
</Toolbar>
</AppBar>
</div>
);
}
ButtonAppBar.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(ButtonAppBar);
Here is the modal:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import Typography from '#material-ui/core/Typography';
import Modal from '#material-ui/core/Modal';
import Button from '#material-ui/core/Button';
function rand() {
return Math.round(Math.random() * 20) - 10;
}
function getModalStyle() {
const top = 50 + rand();
const left = 50 + rand();
return {
top: `${top}%`,
left: `${left}%`,
transform: `translate(-${top}%, -${left}%)`,
};
}
const styles = theme => ({
paper: {
position: 'absolute',
width: theme.spacing.unit * 50,
backgroundColor: theme.palette.background.paper,
boxShadow: theme.shadows[5],
padding: theme.spacing.unit * 4,
},
});
class SimpleModal extends React.Component {
state = {
open: false,
};
handleOpen = () => {
console.log('clicked')
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
render() {
const { classes } = this.props;
return (
<div>
{/* <Typography gutterBottom>Click to get the full Modal experience!</Typography>
<Button onClick={this.handleOpen}>Open Modal</Button> */}
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.state.open}
onClose={this.handleClose}
>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="h6" id="modal-title">
Text in a modal
</Typography>
<Typography variant="subtitle1" id="simple-modal-description">
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</Typography>
<SimpleModalWrapped />
</div>
</Modal>
</div>
);
}
}
SimpleModal.propTypes = {
classes: PropTypes.object.isRequired,
};
// We need an intermediary variable for handling the recursive nesting.
const SimpleModalWrapped = withStyles(styles)(SimpleModal);
export default SimpleModalWrapped;
Your best bet is to use something like Redux. As a lazier strategy have a Higher Order Component house both components. The higher order component can have the function which needs to run when you click on the icon and it changes something in the state of the higher order Component.
Say you have:
import React, { Component } from 'react'
import ComponentWithIcon from './icon-holder'
import ModalComponent from './modal'
class OuterContainer extends Component {
state = { modalOpen: false }
setModal = openOrClose => this.setState({modalOpen: openOrClose})
render(){
const {setModal, state: { modalOpen } } = this
return (
<>
<ComponentWithIcon
handleModalClick={setModal}
/>
<ModalComponent
isOpen={modalOpen}
handleModalClick={setModal}
/>
</>
)
}
}
As a result rather than the Modal component determining whether it is open (having it in its own state) which means that only it will know about whether it is presently open that will be determined by the OuterContainer... The OuterContainer is also the one which will handleTheClickEvent which clicking on the icon causes, so instead of running its own function, the icon will run the OuterComponent's function to setState...
This functionality (the clickHandler) and the state is passed to each of those child components through their props. Since the modal needs to be able to close that too can be passed in from the OuterComponent and it can be kept with the same state.modalOpen and possibly the same passed in action (clickHandler) which is toggle (or if you need for some reason then you can have an openModal/closeModal defined by the OuterComponent and pass both of those callbacks to be run by the modal.
Using redux (with react-redux) you have a single <Provider> component which is the higher order components to all of its children. The provider keeps in state a general store which is generated through redux's createStore() this store then gets actions which manipulate the central store and because it is at the top level it is able to inject its current state (or specific parts of it) to any component that is held within the Provider (which is every single component). Each of those components are able to send messages to that provider using dispatch() they then dispatch actions to the store and that results in the store being replaced with a new store that has things updated inside of it (such as whether the modal is open).
import React from 'react'
const MyModal = ({isOpen, handleModal }) => (
<div className={`modal ${isOpen ? 'is-showing' : 'is-hidden'}`}>
<div
className='toolbar'
>
<CloseButton onClick={() => handleModal(false)} />
</div>
<div>
{/* Modal content */}
</div>
</>
)