Change this react class based component to a function one with hooks - reactjs

I'm trying to add a similar feature (an interactive map with popups) to a react application I'm working on. I'm trying to change this into a function component with hooks but have been having some problems (specifically with handleResults and addMarker). I'm relatively new to coding, sorry if this seems really trivial.
import React, { Component } from 'react';
import ReactMapGl, {Marker, Popup} from 'react-map-gl'
import Geocoder from 'react-map-gl-geocoder'
import 'mapbox-gl/dist/mapbox-gl.css';
import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css'
const mapboxToken = 'Your API Key'
class Map3 extends Component {
constructor() {
super()
this.state = {
viewport: {
width: '100vw',
height: '100vh',
latitude: 49.28,
longitude: -123.12,
zoom: 14
},
currMarker: null,
markers: [],
selectedMarker: null
}
this.handleViewportChange = this.handleViewportChange.bind(this)
this.handleGeocoderViewportChange = this.handleGeocoderViewportChange.bind(this)
this.handleResult = this.handleResult.bind(this)
this.addMarker = this.addMarker.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleMarkerClick = this.handleMarkerClick.bind(this)
}
mapRef = React.createRef()
handleViewportChange(viewport) {
this.setState(prevState => ({
viewport: {...prevState.viewport, ...viewport}
}))
}
handleGeocoderViewportChange(viewport) {
const geocoderDefaultOverrides = {transitionDuration: 1000}
return this.handleViewportChange({
...viewport,
...geocoderDefaultOverrides
})
}
handleResult (result) {
this.setState({
currMarker: {
name: result.result.place_name,
latitude: result.result.center[1],
longitude: result.result.center[0]
}
})
}
addMarker() {
const {currMarker} = this.state
this.setState(prevState => ({
markers: [...prevState.markers, currMarker],
currMarker: null
}))
}
handleMarkerClick(marker) {
this.setState({
selectedMarker: marker
})
}
handleClose = () => {
this.setState({
selectedMarker: null
})
}
render() {
const {viewport, markers, selectedMarker} = this.state
return (
<ReactMapGl
ref={this.mapRef}
{...viewport}
onViewportChange={viewport => this.setState({viewport})}
mapboxApiAccessToken={mapboxToken}
mapStyle="mapbox://styles/mapbox/streets-v10"
>
<button className="add-btn" onClick={this.addMarker}>Add</button>
{markers.map((marker, idx) => {
return (
<Marker
key={idx}
latitude={marker.latitude}
longitude={marker.longitude}
onClick={() => this.handleMarkerClick(marker)}
>
<img src="pin.png" alt="marker"/>
</Marker>
)
})
}
<Geocoder
onSelected={this.handleResult}
mapRef={this.mapRef}
placeholder="Search here!"
onViewportChange={this.handleGeocoderViewportChange}
onResult={this.handleResult}
mapboxApiAccessToken={mapboxToken}
position="top-right"
/>
{this.state.selectedMarker &&
<Popup
mapRef={this.mapRef}
latitude={selectedMarker.latitude}
longitude={selectedMarker.longitude}
closeButton={true}
closeOnClick={false}
onClose={this.handleClose}
>
<h3>{selectedMarker.name}</h3>
</Popup>
}
</ReactMapGl>
)
}
}
export default Map3;

Related

InvalidValueError: setMap: not an instance of Map; and not an instance of StreetViewPanorama #react-google-maps/api

I am using #react-google-maps/api and loading the script from cdn to my public directory index.html file.
I get InvalidValueError: setMap: not an instance of Map; and not an instance of StreetViewPanorama and the markers do not appear on my map. Weird thing is that it is totally random to get this error. Sometimes it works without any problem. That is what I do not understand in the first place.
import React, { useEffect } from 'react';
import { GoogleMap, Marker } from '#react-google-maps/api';
import mapStyles from './mapStyles';
//google map height 100
import './MapDisplay.css';
import { connect } from 'react-redux';
const mapContainerStyle = {
height: '100%',
width: '100%',
};
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
};
const MapDisplay = ({
userLocation,
mouseHoverIdR,
selectedInfoR,
searchedResults,
unAuthNoSearchResults,
selectedLocationInfoWindowS,
}) => {
const onClickMarker = (e) => {
selectedLocationInfoWindowS({
selectedInfoR: e,
type: 'infoWindowLocations',
});
};
const onClickMap = () => {
selectedLocationInfoWindowS({
type: '',
selectedInfoR: {
_id: '',
},
});
};
return (
<div id='map'>
<GoogleMap
id='map_canvas'
mapContainerStyle={mapContainerStyle}
zoom={12}
center={userLocation}
options={options}
onClick={() => onClickMap()}
>
{!unAuthNoSearchResults &&
searchedResults.map((searchedResult) => {
if (
mouseHoverIdR === searchedResult._id ||
selectedInfoR._id === searchedResult._id
) {
var a = window.google.maps.Animation.BOUNCE;
}
return (
<Marker
position={searchedResult.coordinates}
key={searchedResult._id}
clickable
onClick={() => onClickMarker(searchedResult)}
animation={a}
id={'hello'}
/>
);
})}
</GoogleMap>
</div>
);
};
How can I fix the error that I get when I try to display the marker on the map?

Re render component React table

I am trying to re render a component. I have a refresh button and I want to clean all filters and sorting values when clicked.
The thing is that I can not make a re render, not even with forceUpdate(), it is doing NOTHING and I don't know why. Also, I tried with setState(), and nothing. What I want to happen is what happens when I change the page, it re renders the component. Please can anybody could help me? What am I doing wrong?
import React, { Component } from "react";
import DeleteComponent from "../components/DeleteComponent"
import ReactTable from 'react-table';
import { Link, withRouter } from 'react-router-dom';
import axios from "axios";
import { getJwt } from '../helpers/jwt'
import eye from '../img/eye.png'
import bin from '../img/bin.png'
import writing from '../img/writing.png'
class CustomReactTable extends Component {
constructor(props) {
super(props)
this.state = {
data: [],
showDelete: false,
item: null,
pages: null,
totalItems: null,
loading: false,
state: {},
}
}
fetchData = (state) => {
this.setState({ state: state })
const jwt = getJwt()
if (!jwt) {
this.props.history.push('/login')
}
let config = {
headers: { 'Authorization': `Bearer ${jwt}` },
params: {
page: state.page,
pageSize: state.pageSize,
sorted: state.sorted,
filtered: state.filtered
}
}
this.setState({ loading: true })
axios.get(`http://localhost:3001/api/v1${this.props.location.pathname}`, config)
.then(response => {
console.log(response)
this.setState({
data: response.data.result,
loading: false
})
})
axios.get(`http://localhost:3001/api/v1${this.props.location.pathname}/count-documents`, config)
.then(response => {
this.setState({
totalItems: response.data.result,
pages: Math.ceil(response.data.result / state.pageSize)
})
})
}
loadOptions = () => {
this.props.columns.push({
Header: "",
Cell: (row) => [
// Find a better way to add unique key
<Link to={`${this.props.location.pathname}/${row.original._id}/show`} key={row.original._id} params={{ id: row.original._id }}><button className="btn-xs btn-outline-light"><img style={{ width: '1em' }} src={eye} /></button></Link>,
<Link to={`${this.props.location.pathname}/${row.original._id}/edit`} key={row.original._id + 'a'}><button className="btn-xs btn-outline-light"><img style={{ width: '1em' }} src={writing} /></button></Link>,
<button key={row.original._id + 'b'} className="btn-xs btn-outline-light" onClick={() => { this.onClickDeleteButton(row.original._id) }}><img style={{ width: '1em' }} src={bin} /></button>
]
})
}
loadFunctionalities = () => {
return (
<div className='functionalities-react-table'>
<span className='functionalities-add-item-table'>
<Link to={`${this.props.location.pathname}/add`}><button className="btn-sm btn-outline-success">Add new {this.props.modelName}</button></Link>
</span>
<span className='functionalities-refresh-table'>
<button className="btn-sm btn-outline-dark">Refresh table</button>
</span>
</div>
)
}
onClickDeleteButton = (id) => {
this.setState({ showDelete: true, item: id })
}
onCancelDeleteClick = () => {
this.setState({ showDelete: false })
}
componentDidMount() {
this.loadOptions()
}
reloadData = () => {
this.fetchData(this.state.state)
}
render() {
return (
<div className='main-content'>
{this.state.showDelete && (
<DeleteComponent reloadData={this.reloadData} onCancelDeleteClick={this.onCancelDeleteClick} item={this.state.item} />
)}
<h3>{`${this.props.modelName} (${this.state.totalItems})`}</h3>
{this.loadFunctionalities()}
<ReactTable
data={this.state.data}
columns={this.props.columns}
manual
onFetchData={this.fetchData}
defaultPageSize={10}
pages={this.state.pages}
style={{ fontSize: '0.9em' }}
>
</ReactTable>
<div className="total-records-tag">{this.props.modelName}: {this.state.totalItems}</div>
</div >
)
}
}
export default withRouter(CustomReactTable);

How to render only 5 items in react autosuggest?

I'am using react autosuggest npm package to get the json data and display it. I want to display only 5 items. How to do it?
Form.js
import React from 'react'
import Autosuggest from 'react-autosuggest';
import cities from 'cities.json';
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
);
};
const getSuggestionValue = suggestion => suggestion.name;
const renderSuggestion = suggestion => (
<div>
{console.log('suggestion', suggestion)}
{suggestion.name}
</div>
);
class Form extends React.Component {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<br/>
</div>
)
}
}
export default Form;
I want to render only 5 items, otherwise, computer hangs while loading huge data. Is there any other autocomplete react npm package, since I want only cities and country list. i.e when city is inputted, automatically the city name must be suggested with its relevant country.Any solution or suggestion highly appreciated. Thanks in advance
i modified you're getSuggestions() method a little i guess this should work for you.
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,5);
};
Use the Slice method with start index and last Index
suggestions={suggestions.slice(0, 5)}
import {
React
,Avatar
,axiosbase
} from '../../import-files';
import Autosuggest from 'react-autosuggest';
import './autosuggest.css';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Paper from '#material-ui/core/Paper';
import MenuItem from '#material-ui/core/MenuItem';
let suggestions = [ { label: 'Afghanistan' } ];
function renderInputComponent(inputProps) {
const { classes, inputRef = () => {}, ref, ...other } = inputProps;
return (
<TextField
className={classes.textField}
fullWidth
variant="outlined"
InputProps={{
inputRef: node => {
ref(node);
inputRef(node);
},
classes: {
input: classes.input,
},
}}
{...other}
/>
);
}
function renderSuggestion(suggestion, { query, isHighlighted }) {
return (
<MenuItem selected={isHighlighted} component="div">
<div>
<strong key={String(suggestion.id)} style={{ fontWeight: 300 }}>
<span className="sugg-option">
<span className="icon-wrap">
<Avatar src={suggestion.Poster}></Avatar>
</span>
<span className="name">
{suggestion.Title}
</span>
</span>
</strong>
</div>
</MenuItem>
);
}
function initSuggestions(value) {
suggestions = value;
}
function getSuggestionValue(suggestion) {
return suggestion.Title;
}
function onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) {
console.log('HandleSuggestion() '+suggestionValue);
}
const styles = theme => ({
root: {
height: 50,
flexGrow: 1,
},
container: {
position: 'relative',
},
suggestionsContainerOpen: {
position: 'absolute',
zIndex: 998,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
overflowY: 'scroll',
maxHeight:'376%'
},
suggestion: {
display: 'block',
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
divider: {
height: theme.spacing.unit * 2,
},
});
class IntegrationAutosuggest extends React.Component {
state = {
single: '',
popper: '',
suggestions: [],
};
componentDidMount() {
initSuggestions(suggestions);
}
// Filter logic
getSuggestions = async (value) => {
const inputValue = value.trim().toLowerCase();
var _filter = JSON.stringify({
filter : inputValue,
});
return await axiosbase.post(`${apiCall}`, _filter);
};
handleSuggestionsFetchRequested = ({ value }) => {
this.getSuggestions(value)
.then(data => {
if (data.Error) {
this.setState({
suggestions: []
});
} else {
const responseData = [];
data.data.itemsList.map((item, i) => {
let File = {
id: item.idEnc,
Title: item.englishFullName +' '+item.arabicFullName,
englishFullName: item.englishFullName,
arabicFullName: item.arabicFullName,
Poster: item.photoPath,
}
responseData.push(File);
});
this.setState({
suggestions: responseData
});
}
})
};
handleSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
handleChange = name => (event, { newValue }) => {
this.setState({
[name]: newValue,
});
if(event.type=='click'){
if(typeof this.props.handleOrderUserFirstNameChange === "function"){
this.props.handleOrderUserFirstNameChange(newValue);
}
this.state.suggestions.filter(f=>f.Title===newValue).map((item, i) => {
//id
//Title
// Poster
if(typeof this.props.handleUserIDChange === "function"){
this.props.handleUserIDChange(item.id);
}
});
}
};
render() {
const { classes } = this.props;
// console.log('Re-render!!');
// console.log(this.props);
// console.log(this.state.suggestions);
const autosuggestProps = {
renderInputComponent,
suggestions: this.state.suggestions,
onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested,
onSuggestionsClearRequested: this.handleSuggestionsClearRequested,
onSuggestionSelected: this.props.onSelect,
getSuggestionValue,
renderSuggestion,
};
return (
<div className={classes.root}>
<Autosuggest
{...autosuggestProps}
inputProps={{
classes,
placeholder: this.props.placeHolder,
value: this.state.single,
onChange: this.handleChange('single'),
}}
theme={{
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
/>
<div className={classes.divider} />
</div>
);
}
}
export default withStyles(styles)(IntegrationAutosuggest);

Google Maps not re-rendering in reactjs

I am trying to show different locations of stores that exists in a database in the based on user search, but at first i need to center the map using user's current latitude and longitude.
I did set the lat and lng to 0 in the state and updated the state values when the component is mounted.
The problem is, the map component does not re-render when this state value is updated.
import React, { Component } from 'react'
import { Map, GoogleApiWrapper, InfoWindow, Marker } from 'google-maps-react';
import { GeneConsumer } from '../../store';
const mapStyles = {
width: '100% !important',
height: '100% !important'
};
class LocationMap extends Component {
constructor(props) {
super(props);
this.state = {
currentLatLng: {
lat: 0,
lng: 0
},
showingInfoWindow: false, //Hides or the shows the infoWindow
activeMarker: {}, //Shows the active marker upon click
selectedPlace: {} //Shows the infoWindow to the selected place upon a marker
}
}
componentDidMount() {
navigator.geolocation.getCurrentPosition(
(position) => {
this.setState({
currentLatLng: {
lat: position.coords.latitude,
lng: position.coords.longitude
}
});
},
(error) => {
this.setState({
currentLatLng: {
lat: 6.4494007,
lng: 3.4498444
}
});
}
)
}
render() {
console.log(this.state.currentLatLng.lat)
console.log(this.state.currentLatLng.lng)
return(
<GeneConsumer>
{value => {
const {locations} = value;
const displayMarkers = () => {
if(!locations){
return null;
}
return locations.map((location, index) => {
return <Marker key={index} id={index} position={{
lat: location.latitude,
lng: location.longitude
}}
onClick={() => onMarkerClick(location)}
name = {location.name}
/>
})
}
const onMarkerClick = (props, marker, e) =>
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
});
const onClose = props => {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
});
}
};
return (
<Map
google={this.props.google}
zoom={10}
style={mapStyles}
initialCenter={{ lat: this.state.currentLatLng.lat, lng: this.state.currentLatLng.lng}}
>
{displayMarkers()}
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
onClose={onClose()}
>
<div>
<h4>{this.state.selectedPlace.name}</h4>
</div>
</InfoWindow>
</Map>
);
}}
</GeneConsumer>
);
}
}
export default GoogleApiWrapper({
apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
})(LocationMap);
This is by design, updating initialCenter prop does not cause map to re-render:
initalCenter: Takes an object containing latitude and longitude
coordinates. Sets the maps center upon loading.
To re-center the map use center prop along with initialCenter prop. The following example demonstrates how to re-center the map on map click event:
class MapExample extends Component {
state = {
currentCenter: this.props.center //default center
};
handleMapClick = (ref, map, ev) => {
this.setState({ currentCenter: ev.latLng });
};
render() {
return (
<Map
google={this.props.google}
className={"map"}
initialCenter={this.props.center}
center={this.state.currentCenter}
zoom={this.props.zoom}
onClick={this.handleMapClick}
>
<Marker position={this.state.currentCenter} />
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: "--YOUR-KEY-GOES-HERE--"
})(MapExample);

How to add coordinates to google-maps-react on a user click event?

I'm trying to add a marker on based on a user clicking on the map for the google-maps-react npm. Currently the code below will generate markers and add them to the this.state = { markers:[ ] } and I would like to map them out on the map component. However, the position:event.latLng, will not register the lat and lng and the marker will only be created and inserted into the state with the key: Date.now() and defaultAnimation: 2. Below the code:
import React, { Component } from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
export class MapContainer2 extends Component {
constructor(props){
super(props);
this.state={
lat:null,
lng:null,
markers:[]
}
}
componentDidMount(){
navigator.geolocation.getCurrentPosition(position=>
this.setState({
lat:position.coords.latitude,
lng:position.coords.longitude,
}));
}
mapClicked = (event) =>{
const { markers } = this.state;
this.setState({
markers:[
{
position:event.latLng,
key: Date.now(),
defaultAnimation: 2,
},
...markers
]
})
}
render() {
if (!this.props.loaded) {
return <div>Loading...</div>
}
const style = {
width: '100%',
height: '100vh'
}
return (
<Map
google={this.props.google}
zoom={11}
style={style}
initialCenter={{
lat: this.state.lat,
lng: this.state.lng
}}
center={{
lat: this.state.lat,
lng: this.state.lng
}}
onClick={this.mapClicked}
>
<Marker
title={'Geolocation'}
position={{
lat:this.state.lat,
lng:this.state.lng,
}}
/>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: ('AIzaSyCZ7rgMN34kWkGvr8Pzkf_8nkT7W6gowBA')
})(MapContainer2)
I resolved it by updating the function mapClicked with the following:
mapClicked = (mapProps, map, event) => {
const { markers } = this.state;
const lat = event.latLng.lat();
const lng = event.latLng.lng();
let url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=myapikey`
axios.get(url).then(response => {
this.setState({
googleReverseGeolocation:response.data.results[0].formatted_address,
markers:[{position:{lat:event.latLng.lat(),lng:event.latLng.lng()}}, ...markers],
latClick:lat,
lngClick:lng
});
this.props.onMapClickChange(lat, lng, response.data.results[0].formatted_address);
});
}

Resources