How do I setState in my react-leaflet component - javascript

I cannot update state in my componentDidMount using setState. I have an axios call that handles the asynchronous call to my api and loads an anonymous function which should update state when the user clicks on the geoJSON layer. For some reason the redux store is not being updated on click and remains the initial state of an empty string. My code is below
import React from 'react';
import L from 'leaflet';
import {apiKey} from './apiKey';
import { connect } from 'react-redux';
import axios from 'axios';
import {districtStyle, districtHighlightStyle, mapStyle} from './styles';
class Map extends React.Component {
componentDidMount() {
let districtsJSON = null;
this.map = L.map('map').setView([31.15, -99.90], 6);
L.tileLayer(`https://{s}.tile.thunderforest.com/pioneer/{z}/{x}/{y}.png?apikey=${apiKey}`, {
attribution: 'Pioneer Basemap by Thunderforest, a project by Gravitystorm Limited.'
}).addTo(this.map);
const districtsUrl = 'http://localhost:8000/gerrymander/api/districts/';
axios.get(districtsUrl).then(
response => {
districtsJSON = response.data;
const txDistricts = L.geoJson(districtsJSON, {
style: districtStyle,
onEachFeature: (feature, layer) => {
layer.on({
click: (e) => {
if (lastClickedLayer !== e.target) {
txDistricts.setStyle(districtStyle);
}
e.target.setStyle(districtHighlightStyle);
let lastClickedLayer = e.target;
// Set state to the target layer's clicked district
this.setState({ district: e.target.feature.properties.namelsad })
}
});
}
}).addTo(this.map);
}
)
console.log(this.props.district);
}
render() {
return <div id="map" style={mapStyle}></div>
}
}
const mapStateToProps = state => {
return {
district: state.district,
}
}
export default connect(mapStateToProps)(Map);

Related

React-Leaflet LocateControl component - keeps duplicating on each refresh

I'm using Locate Control in React Leaflet, but the Locate Control buttons are always duplicated, and sometimes I get 3 or 4 of them (see image below). I'm running the function through a useEffect with empty dependency to only fire it once, but no matter. I can target the class with display: none, but then both disappear. I feel like this might be an issue with Locate Control library? Really not sure. Open to any help or ideas.
import { useEffect } from "react"
import { useMap } from "react-leaflet"
import Locate from "leaflet.locatecontrol"
import "leaflet.locatecontrol/dist/L.Control.Locate.min.css"
const AddLocate = () => {
const map = useMap()
useEffect(() => {
const locateOptions = {
position: "bottomleft",
flyTo: true,
}
const locateControl = new Locate(locateOptions)
locateControl.addTo(map)
}, [])
return null
}
export default AddLocate;
Looks like you use a package made for leaflet. Which should for the most parts be okay. However the way you add the control is not really the react-leaflet way, where we want to add add components rather than add "stuff" directly to the map.
Below you can see how easy it is to implement a location component that you simply just can add as component within your MapContainer.
import { ActionIcon } from "#mantine/core";
import React, { useState } from "react";
import { useMapEvents } from "react-leaflet";
import { CurrentLocation } from "tabler-icons-react";
import LeafletControl from "./LeafletControl";
interface LeafletMyPositionProps {
zoom?: number;
}
const LeafletMyPosition: React.FC<LeafletMyPositionProps> = ({ zoom = 17 }) => {
const [loading, setLoading] = useState<boolean>(false);
const map = useMapEvents({
locationfound(e) {
map.flyTo(e.latlng, zoom);
setLoading(false);
},
});
return (
<LeafletControl position={"bottomright"}>
<ActionIcon
onClick={() => {
setLoading(true);
map.locate();
}}
loading={loading}
variant={"transparent"}
>
<CurrentLocation />
</ActionIcon>
</LeafletControl>
);
};
export default LeafletMyPosition;
And for LeafletControl I just have this reusable component:
import L from "leaflet";
import React, { useEffect, useRef } from "react";
const ControlClasses = {
bottomleft: "leaflet-bottom leaflet-left",
bottomright: "leaflet-bottom leaflet-right",
topleft: "leaflet-top leaflet-left",
topright: "leaflet-top leaflet-right",
};
type ControlPosition = keyof typeof ControlClasses;
export interface LeafLetControlProps {
position?: ControlPosition;
children?: React.ReactNode;
}
const LeafletControl: React.FC<LeafLetControlProps> = ({
position,
children,
}) => {
const divRef = useRef(null);
useEffect(() => {
if (divRef.current) {
L.DomEvent.disableClickPropagation(divRef.current);
L.DomEvent.disableScrollPropagation(divRef.current);
}
});
return (
<div ref={divRef} className={position && ControlClasses[position]}>
<div className={"leaflet-control"}>{children}</div>
</div>
);
};
export default LeafletControl;
I would do some debugging to that useEffect to see if it's only happening once. It's possible the entire component is mounted multiple times.

Mobx observer component not re-rendering on state change

I am using Mobx in my react app for global state. It has been working fantastic and exactly as expected until now. I am storing the mouse position in an SVG element in the store and hoping to utilize this state in a number of components. I can tell the state is changing correctly by logging inside the store, but for some reason my CrossHairs component is not re-rendering despite being wrapped in an observer HOC and featuring dereferenced store values. Any help would be much appreciated.
Store:
import PropTypes from 'prop-types';
import React, { createContext, useContext } from 'react';
import { webSocket } from 'rxjs/webSocket';
import { makeAutoObservable } from 'mobx';
import { PriceDataStore } from './_priceDataStore';
import { groupByFunction } from './utils/rxjs/groupByFunction';
import { TradePlanStore } from './_tradePlanStore';
export class WebsocketStore {
websocketSubject = webSocket('ws://localhost:8080/socket');
chartSubject;
priceDataStore;
tradePlanStore;
mouseCoordinates;
constructor() {
makeAutoObservable(this);
this.priceDataStore = new PriceDataStore(this);
this.tradePlanStore = new TradePlanStore(this);
this.chartSubject = groupByFunction(
'messageType',
'getChart',
this.websocketSubject
);
this.mouseCoordinates = {
x: 0,
y: 0,
};
}
sendWebsocketMessage = (msg) => {
this.websocketSubject.next(msg);
};
updateCoordinates = (coordinates) => {
this.mouseCoordinates = { x: coordinates[0], y: coordinates[1] };
};
}
const WebsocketContext = createContext(null);
export const useWebsocketStore = () => useContext(WebsocketContext);
export const WebSocketProvider = ({ store, children }) => {
return (
<WebsocketContext.Provider value={store}>
{children}
</WebsocketContext.Provider>
);
};
WebSocketProvider.propTypes = {
children: PropTypes.any,
store: PropTypes.any,
};
Place where state is being changed, this seems to be working:
const websocketStore = useWebsocketStore();
useEffect(() => {
select(zoomRef.current)
.on('mouseenter', () => {
setShowCrossHairs(true);
})
.on('mousemove', (event) => {
const coordinates = pointer(event);
websocketStore.updateCoordinates(coordinates);
})
.on('mouseleave', () => {
setShowCrossHairs(false);
});
}, [websocketStore.updateCoordinates]);
Component that is not triggered to re-render on mouseCoordinates state change in store despite being wrapped in observer and dereferencing mouseCoordinates values.
import PropTypes from 'prop-types';
import React, { useEffect } from 'react';
import { select, pointer } from 'd3';
import { colors } from '../../../../../../colorVars';
import { useWebsocketStore } from '../../../../../../stores/websocketStore/websocketStore';
import { observer } from 'mobx-react-lite';
const ChartCrossHairs = observer(({
size,
bottomPadding,
showCrossHairs,
}) => {
const websocketStore = useWebsocketStore();
const x = websocketStore.mouseCoordinates.x;
const y = websocketStore.mouseCoordinates.y;
return (
<g>
{showCrossHairs && (
<>
<line
strokeDasharray="1, 1"
x1={0}
x2={size}
y1={y}
y2={y}
stroke={colors.white}
/>
<line
strokeDasharray="1, 1"
y1={0}
y2={size + bottomPadding}
x1={x}
x2={x}
stroke={colors.white}
/>
</>
)}
</g>
);
});

Passing function via props from Parent to Child component in React?

I am having an issue where I'm trying to pass a function(updateEvents) via props from my App.js file to a NumberOfEvents.js file. I passed the same function to another component with no issues. However, when I try on the NumberOfEvents file, I get the following error:
Error image
Please help!!!
Here is the Parent:
import React, { Component } from 'react';
import EventList from './EventList';
import CitySearch from './CitySearch';
import NumberOfEvents from './NumberOfEvents';
import { extractLocations, getEvents } from './api';
import './nprogress.css';
import './App.css';
class App extends Component {
state = {
events: [],
locations: [],
numberOfEvents: 32
}
componentDidMount() {
this.mounted = true;
getEvents().then((events) => {
if (this.mounted) {
this.setState({
events: events.slice(0, this.state.numberOfEvents),
locations: extractLocations(events)
});
}
});
}
componentWillUnmount() {
this.mounted = false;
}
updateEvents = (location, eventCount) => {
this.mounted = true;
getEvents().then((events) => {
const locationEvents = (location === 'all')
? events
: events.filter((event) => event.location === location);
this.setState({
events: locationEvents,
numberOfEvents: eventCount,
});
});
};
render() {
return (
<div className="App">
<CitySearch
locations={this.state.locations} updateEvents={this.updateEvents} />
<EventList
events={this.state.events} />
<NumberOfEvents
numberOfEvents={this.state.numberOfEvents}
updateEvents={this.updateEvents} />
</div>
);
}
}
export default App;
And here is the Child:
import React, { Component } from 'react';
class NumberOfEvents extends Component {
state = {
numberOfEvents: 32
}
handleChange = (event) => {
const value = event.target.value;
this.setState({
numberOfEvents: value,
});
this.props.updateEvents('', value);
};
render() {
return (
<input
className="number"
value={this.state.numberOfEvents}
onChange={this.handleChange} />
)
}
}
export default NumberOfEvents;
Im not sure this will help ...In Your Parent Component , inside return statement when passing the updateEvents Prop, try passing it as arrow function like this ....
updateEvents={ () => this.updateEvents() } />
try adding a constructor to the child component
constructor(props) {
super(props);
this.state = {
numberOfEvents: 32
}
}

React Mobx can't display observable contents, very simple app

Very simple app, I'm trying to display content from my API using Mobx and Axios, here's my Axios agent.ts:
import { ITutorialUnit } from './../model/unit';
import axios, { AxiosResponse } from "axios";
//set the base URL
axios.defaults.baseURL = "http://localhost:5000/api";
//store our request in a const
const responseBody = (response: AxiosResponse) => response.data;
const requests = {
get: (url: string) => axios.get(url).then(responseBody),
};
//create a const for our activty's feature,all our activities' request are go inside our Activities object
const TutorialUnits = {
list: ():Promise<ITutorialUnit[]> => requests.get("/tutorialunits"),
};
export default{
TutorialUnits
}
then I call this agent.s in a store:
import { ITutorialUnit } from "./../model/unit";
import { action, observable } from "mobx";
import { createContext } from "react";
import agent from "../api/agent";
class UnitStore {
#observable units: ITutorialUnit[] = [];
//observable for loading indicator
#observable loadingInitial = false;
#action loadUnits = async () => {
//start the loading indicator
this.loadingInitial = true;
try {
//we use await to block anything block anything below list() method
const units = await agent.TutorialUnits.list();
units.forEach((unit) => {
this.units.push(unit);
// console.log(units);
});
this.loadingInitial = false;
} catch (error) {
console.log(error);
this.loadingInitial = false;
}
};
}
export default createContext(new UnitStore());
then I call this in my App component:
import React, { Fragment, useContext, useEffect } from "react";
import { Container } from "semantic-ui-react";
import "semantic-ui-css/semantic.min.css";
import NavBar from "../../features/nav/NavBar";
import { ActivityDashboard } from "../../features/Units/dashboard/tutorialUnitDashboard";
import UnitStore from "../stores/unitStore";
import { observer } from "mobx-react-lite";
import { LoadingComponent } from "./LoadingComponent";
const App = () => {
const unitStore = useContext(UnitStore);
useEffect(() => {
unitStore.loadUnits();
//need to specify the dependencies in dependenciy array below
}, [unitStore]);
//we are also observing loading initial below
if (unitStore.loadingInitial) {
return <LoadingComponent content="Loading contents..." />;
}
return (
<Fragment>
<NavBar />
<Container style={{ marginTop: "7em" }}>
<ActivityDashboard />
</Container>
</Fragment>
);
};
export default observer(App);
Finally, I want to use this component to display my content:
import { observer } from "mobx-react-lite";
import React, { Fragment, useContext } from "react";
import { Button, Item, Label, Segment } from "semantic-ui-react";
import UnitStore from "../../../app/stores/unitStore";
const UnitList: React.FC = () => {
const unitStore = useContext(UnitStore);
const { units } = unitStore;
console.log(units)
return (
<Fragment>
{units.map((unit) => (
<h2>{unit.content}</h2>
))}
</Fragment>
);
};
export default observer(UnitList);
I can't see the units..
Where's the problem? My API is working, I tested with Postman.
Thanks!!
If you were using MobX 6 then you now need to use makeObservable method inside constructor to achieve same functionality with decorators as before:
class UnitStore {
#observable units: ITutorialUnit[] = [];
#observable loadingInitial = false;
constructor() {
// Just call it here
makeObservable(this);
}
// other code
}
Although there is new thing that will probably allow you to drop decorators altogether, makeAutoObservable:
class UnitStore {
// Don't need decorators now anywhere
units: ITutorialUnit[] = [];
loadingInitial = false;
constructor() {
// Just call it here
makeAutoObservable(this);
}
// other code
}
More info here: https://mobx.js.org/react-integration.html
the problem seems to be the version, I downgraded my Mobx to 5.10.1 and my mobx-react-lite to 1.4.1 then Boom everything's fine now.

react native componetWillUpdate not working

I am trying to render a component with already existing data from state (provided from redux-persist), the data in is state.login.user (i can see it in console.log in the mapStateToProps function that is being called and returns the dataObject : state.login.user but the dataObject is not being updated and because of that componentWillReceiveProps is not being called.
Can you point me to what im doing wrong?
import React from 'react'
import { connect } from 'react-redux'
import { ScrollView, AppRegistry, Component, Text, Image, View, Button, Modal, TouchableOpacity } from 'react-native'
import { GiftedForm, GiftedFormManager } from 'react-native-gifted-form'
// Styles
import styles from './Styles/MyProfileScreenStyles'
class MyProfileScreen extends React.Component {
constructor (props, context) {
const dataObject = {
profile: {
last_name : undefined,
}
}
super(props, context)
this.state = {
form: {
lastName: dataObject.profile.last_name,
tos: false
}
}
}
handleValueChange (values) {
this.setState({form: values})
}
componentWillReceiveProps (newProps) {
console.tron.log("componend will receive")
console.tron.log(newProps)
if (newProps.dataObject) {
this.setState({
dataObject: newProps.dataObject
})
}
}
render () {
const {lastName, tos, gender} = this.state.form
console.log('render', this.state.form)
return (
<View style={styles.container}>
<GiftedForm
formName='signupForm'
openModal={(route) => { this.props.navigator.push(route) }}
onValueChange={this.handleValueChange.bind(this)}
>
<GiftedForm.TextInputWidget
name='lastName'
title='Last name'
placeholder='Last name'
clearButtonMode='while-editing'
value={lastName}
/>
<GiftedForm.HiddenWidget name='tos' value={tos}/>
</GiftedForm>
</View>
)
}
}
const mapStateToProps = (state) => {
if( state.login.user !== null){
console.tron.log("test map state to props")
return {
dataObject: state.login.user
}
}
return {}
}
export default connect(mapStateToProps)(MyProfileScreen)
componentWillReceiveProps is only called when the props are updated after the component has rendered, before the component is re-rendered. You'll want to set the state inside your constructor as the props should already be there.

Categories

Resources