Working with React-Native and trying to learn ES6 syntax. I had a similar issue yesterday and got the solution. I added
.bind(this)
to my my function calls and the problem was solved. I ran into the same issue again with another function call and I cannot track down what is going on. The error message is the same.
undefined is not a object (evaluating 'this.props.drawer.open')
The function is:
onClickMenu () {
this.props.drawer.open();
}
and it is being called with this:
onPress={this.onClickMenu.bind(this)}
Here is the entire code. If you see something other than this issue that doesn't look right let me know please! *note I have replaced "var" with "let". From what I've read it is proper ES6 syntax to do that everywhere?
'use strict';
const React = require('react-native');
const {
Text,
View,
Component,
StyleSheet,
SwitchAndroid
} = React;
import { Button } from 'react-native-material-design';
import Store from 'react-native-simple-store';
import Underscore from 'underscore';
import RNGMap from 'react-native-gmaps';
import Polyline from 'react-native-gmaps/Polyline';
import Icon from 'react-native-vector-icons/Ionicons';
import SettingsService from './../settings/settings.service';
//import subdivisions from './subdivisions.json';
import commonStyles from './../common/styles';
let accessToken = null;
let userId = null;
let routeId = null;
let subdivisionId = null;
SettingsService.init('Android');
class Map extends Component {
constructor(props) {
super(props)
this.state = {
odometer: 0,
mapWidth: 300,
mapHeight: 300,
enabled: false,
isMoving: false,
currentLocation: undefined,
locationManager: undefined,
paceButtonIcon: 'Start Trip',
navigateButtonIcon: 'navigate',
paceButtonStyle: commonStyles.disabledButton,
// mapbox
center: {
lat: 40.7223,
lng: -73.9878
},
zoom: 10,
markers: []
}
}
componentDidMount() {
Store.get('token').then((token) => {
accessToken = token.access_token;
userId = token.userId;
});
let me = this,
gmap = this.refs.gmap;
this.locationManager = this.props.locationManager;
// location event
this.locationManager.on("location", function(location) {
console.log('- location: ', JSON.stringify(location));
me.setCenter(location);
gmap.addMarker(me._createMarker(location));
me.setState({
odometer: (location.odometer / 1000).toFixed(1)
});
// Add a point to our tracking polyline
if (me.polyline) {
me.polyline.addPoint(location.coords.latitude, location.coords.longitude);
}
});
// http event
this.locationManager.on("http", function(response) {});
// geofence event
this.locationManager.on("geofence", function(geofence) {});
// error event
this.locationManager.on("error", function(error) {
console.log('- ERROR: ', JSON.stringify(error));
});
// motionchange event
this.locationManager.on("motionchange", function(event) {
me.updatePaceButtonStyle();
});
// getGeofences
this.locationManager.getGeofences(function(rs) {
}, function(error) {
console.log("- getGeofences ERROR", error);
});
SettingsService.getValues(function(values) {
values.license = "eddbe81bbd86fa030ea466198e778ac78229454c31100295dae4bfc5c4d0f7e2";
values.orderId = 1;
values.stopTimeout = 0;
//values.url = 'http://192.168.11.120:8080/locations';
me.locationManager.configure(values, function(state) {
me.setState({
enabled: state.enabled
});
if (state.enabled) {
me.initializePolyline();
me.updatePaceButtonStyle()
}
});
});
this.setState({
enabled: false,
isMoving: false
});
}
_createMarker(location) {
return {
title: location.timestamp,
id: location.uuid,
icon: require("image!transparent_circle"),
anchor: [0.5, 0.5],
coordinates: {
lat: location.coords.latitude,
lng: location.coords.longitude
}
};
}
initializePolyline() {
// Create our tracking Polyline
let me = this;
Polyline.create({
width: 12,
points: [],
geodesic: true,
color: '#2677FF'
}, function(polyline) {
me.polyline = polyline;
});
}
onClickMenu () {
this.props.drawer.open();
}
onClickEnable() {
let me = this;
if (!this.state.enabled) {
this.locationManager.start(function() {
me.initializePolyline();
});
} else {
this.locationManager.resetOdometer();
this.locationManager.stop();
this.setState({
markers: [{}],
odometer: 0
});
this.setState({
markers: []
});
if (this.polyline) {
this.polyline.remove(function(result) {
me.polyline = undefined;
});
}
}
this.setState({
enabled: !this.state.enabled
});
this.updatePaceButtonStyle();
}
onClickPace() {
if (!this.state.enabled) {
return;
}
let isMoving = !this.state.isMoving;
this.locationManager.changePace(isMoving);
this.setState({
isMoving: isMoving
});
this.updatePaceButtonStyle();
}
onClickLocate() {
let me = this;
this.locationManager.getCurrentPosition({
timeout: 30
}, function(location) {
me.setCenter(location);
}, function(error) {
console.error('ERROR: getCurrentPosition', error);
me.setState({
navigateButtonIcon: 'navigate'
});
});
}
onRegionChange() {}
setCenter(location) {
this.setState({
navigateButtonIcon: 'navigate',
center: {
lat: location.coords.latitude,
lng: location.coords.longitude
},
zoom: 16
});
}
onLayout() {
let me = this,
gmap = this.refs.gmap;
this.refs.workspace.measure(function(ox, oy, width, height, px, py) {
me.setState({
mapHeight: height,
mapWidth: width
});
});
}
updatePaceButtonStyle() {
let style = commonStyles.disabledButton;
if (this.state.enabled) {
style = (this.state.isMoving) ? commonStyles.redButton : commonStyles.greenButton;
}
this.setState({
paceButtonStyle: style,
paceButtonIcon: (this.state.enabled && this.state.isMoving) ? 'Stop Trip' : 'Start Trip'
});
}
render() {
return (
<View style={commonStyles.container}>
<View style={commonStyles.topToolbar}>
<Icon.Button name="android-options" onPress={this.onClickMenu.bind(this)} backgroundColor="transparent" size={30} color="#000" style={styles.btnMenu} underlayColor={"transparent"} />
<Text style={commonStyles.toolbarTitle}>Background Geolocation</Text>
<SwitchAndroid onValueChange={this.onClickEnable.bind(this)} value={this.state.enabled} />
</View>
<View ref="workspace" style={styles.workspace} onLayout={this.onLayout.bind(this)}>
<RNGMap
ref={'gmap'}
style={{width: this.state.mapWidth, height: this.state.mapHeight}}
markers={this.state.markers}
zoomLevel={this.state.zoom}
onMapChange={(e) => console.log(e)}
onMapError={(e) => console.log('Map error --> ', e)}
center={this.state.center} />
</View>
<View style={commonStyles.bottomToolbar}>
<Icon.Button name={this.state.navigateButtonIcon} onPress={this.onClickLocate.bind(this)} size={25} color="#000" underlayColor="#ccc" backgroundColor="transparent" style={styles.btnNavigate} />
<Text style={{fontWeight: 'bold', fontSize: 18, flex: 1, textAlign: 'center'}}>{this.state.odometer} km</Text>
<Button raised={true}
text={this.state.paceButtonIcon}
onPress={this.onClickPace.bind(this)}
overrides={{backgroundColor:"#e12429",textColor:"#ffffff"}}
style={this.state.paceButtonStyle}></Button>
<Text> </Text>
</View>
</View>
);
}
};
const styles = StyleSheet.create({
workspace: {
flex: 1
}
});
module.exports = Map;
UPDATE:
debugging via adb in the terminal shows the same error
So here is rest of code. to troubleshoot. I added the project files to a plunker. it is a demo project that i am working with. plunker
'use strict';
const React = require('react-native');
const {
Text,
Component,
StyleSheet,
AppRegistry
} = React;
import Map from './map/map';
import Drawer from 'react-native-drawer';
import Settings from './settings/settings.android';
import Icon from 'react-native-vector-icons/Ionicons';
import BackgroundGeolocation from 'react-native-background-geolocation-android';
global.bgGeo = BackgroundGeolocation;
class App extends Component {
onClickMenu() {
this.props.refs.drawer.open();
}
render() {
return (
<Drawer ref="drawer" side="right" acceptPan={false} content={<Settings drawer={this.refs.drawer} locationManager={BackgroundGeolocation} />}>
<Map drawer={this.refs.drawer} locationManager={BackgroundGeolocation} />
</Drawer>
);
}
};
module.exports = App;
UPDATE:
I dont think you can pass through refs to components in such a way,
certainly it would not work in React and I dont think it would work
in such a way in React-Native either.
I'm not clear why you are trying to .open the Drawer from the
Map component as it looks like the Map component can only be
accessed when the Drawer is open, but, if you want to access parent
behaviours from children a good pattern is to pass through functions
for children to execute (you could argue that this is actually bad
and that passing events around is a more robust pattern).
I've never used the library so I'm not totally clear on its usage but
you can pass functions through like this:
class Application extends Component {
closeControlPanel = () => {
this.refs.drawer.close()
};
openControlPanel = () => {
this.refs.drawer.open()
};
render () {
return (
<Drawer
ref="drawer"
content={<ControlPanel />}
>
<Map onMenuClose={ this.closeControlPanel.bind( this ) } />
</Drawer>
)
}
})
In this case this.props.onMenuClose should be attached to an action, which, when executed will trigger the function from the parent and execute the this.refs.drawer.close function.
Related
I have a big problem with React TimeLines Package(https://openbase.com/js/react-timelines)
I want something like this photo:
( having 3 P tags with different ClassNames)
but in default case of this package I cant do it!
I think I should use something like createElement and textContent in JS. but I dont know how!
My Codes:
import React, { Component } from "react";
import Timeline from "react-timelines";
import "react-timelines/lib/css/style.css";
import { START_YEAR, NUM_OF_YEARS, NUM_OF_TRACKS } from "./constant";
import { buildTimebar, buildTrack } from "./builder";
import { fill } from "./utils";
const now = new Date("2021-01-01");
const timebar = buildTimebar();
// eslint-disable-next-line no-alert
const clickElement = (element) =>
alert(`Clicked element\n${JSON.stringify(element, null, 2)}`);
class App extends Component {
constructor(props) {
super(props);
const tracksById = fill(NUM_OF_TRACKS).reduce((acc, i) => {
const track = buildTrack(i + 1);
acc[track.id] = track;
return acc;
}, {});
this.state = {
open: false,
zoom: 2,
// eslint-disable-next-line react/no-unused-state
tracksById,
tracks: Object.values(tracksById),
};
}
handleToggleOpen = () => {
this.setState(({ open }) => ({ open: !open }));
};
handleToggleTrackOpen = (track) => {
this.setState((state) => {
const tracksById = {
...state.tracksById,
[track.id]: {
...track,
isOpen: !track.isOpen,
},
};
return {
tracksById,
tracks: Object.values(tracksById),
};
});
};
render() {
const { open, zoom, tracks } = this.state;
const start = new Date(`${START_YEAR}`);
const end = new Date(`${START_YEAR + NUM_OF_YEARS}`);
return (
<div className="app">
<Timeline
scale={{
start,
end,
zoom,
}}
isOpen={open}
toggleOpen={this.handleToggleOpen}
clickElement={clickElement}
timebar={timebar}
tracks={tracks}
now={now}
enableSticky
scrollToNow
/>
</div>
);
}
}
export default App;
builder.js:
export const buildElement = ({ trackId, start, end, i }) => {
const bgColor = nextColor();
const color = colourIsLight(...hexToRgb(bgColor)) ? "#000000" : "#ffffff";
return {
id: `t-${trackId}-el-${i}`,
title: "Bye Title: Hello Type: String",
start,
end,
style: {
backgroundColor: `#${bgColor}`,
color,
borderRadius: "12px",
width: "auto",
height: "120px",
textTransform: "capitalize",
},
};
};
I am using the following packages for my map:
"leaflet-routing-machine": "^3.2.12",
"leaflet": "^1.7.1",
"react-leaflet": "^2.7.0",
Essentially I have a Routing machine component which I've integrated with my Map and Markers i.e. (upon clicking two points on the map you get the route) you can drag each and the route updates!
However at this point I have a button which resets everything, clears the markers, the associated LAT & LONG. But I just want the route to reset as well.
You can see those previous routes (in beautiful "chartreuse") stay on the map.
Right now I have a function which is supposed to control when the component is visible:
function clearMarkers(){
setIsRoutingDone(false);
}
{isRoutingDone && <Routing isRoutingDone={isRoutingDone} map={map} myMapRef={myMapRef} icon={{startIcon, endIcon}} userLocation={userLocation} coords={{fromLat, fromLon, toLat, toLon}} />}
This is my Routing Machine:
import { MapLayer } from "react-leaflet";
import L from "leaflet";
import "leaflet-routing-machine";
import { withLeaflet } from "react-leaflet";
class Routing extends MapLayer {
constructor(props) {
super(props);
}
createLeafletElement() {
const { map, coords, icon, removeFrom, removeTo } = this.props;
var dStart = L.latLng(coords.fromLat, coords.fromLon);
var dGoal = L.latLng(coords.toLat, coords.toLon);
this.leafletElement = L.Routing.control({
collapsible: true,
lineOptions: {
styles: [{color: 'chartreuse', opacity: 1, weight: 5}]
},
waypoints: [dStart, dGoal],
createMarker: function(i, waypoints, n) {
if (i === 0) {
marker_icon = icon.startIcon;
}
var marker_icon;
if (i === 0) {
marker_icon = icon.startIcon;
}
else if (i == n - 1) {
marker_icon = icon.endIcon
}
var marker = L.marker(i === 0 ? dStart : dGoal,{
draggable: true,
icon: marker_icon
});
return marker;
}
}).addTo(map.leafletElement);
return this.leafletElement.getPlan();
}
updateLeafletElement(props) {
if(this.leafletElement){
if(this.props.isRoutingDone === false){
this.leafletElement.spliceWaypoints(0, 2); // <-- removes your route
}
}
}
}
export default withLeaflet(Routing);
Actually I logged something in the updateLeafletElement function and zzilch.
And this is my map:
import React, { useState, useEffect, useRef } from 'react'
import { Map, Marker } from 'react-leaflet';
import LocateControl from '../LocateControl/LocateControl.jsx';
import MapboxLayer from '../MapboxLayer/MapboxLayer.jsx';
import Routing from '../RoutingMachine/RoutingMachine.jsx'
export default function MyMap({getAddressFromLatLong, hillfinderFormButtonRef, setCurrentLocation, setCurrentDestination}) {
var myMapRef = useRef();
useEffect(() => {
hillfinderFormButtonRef.current = clearMarkers;
return() => {
hillfinderFormButtonRef.current = null;
}
}, []);
function resetHandler (){
return myMapRef.current();
};
function clearMarkers(){
console.log("markerData ", markerData);
setMarkerData(markerData => [], ...markerData);
setFromLat(fromLat => null);
setFromLon(fromLon => null);
setToLat(toLat => null)
setToLon(toLon => null)
setFrom(from => 0);
setTo(to => 0);
setIsRoutingDone(false);
// setRemoveFrom(removeFrom => null)
// setRemoveTo(removeTo => null)
}
function saveMap(map){
setMap(map);
}
function handleOnLocationFound(e){
setUserLocation(e.latlng)
}
function markerClick(e){
e.originalEvent.view.L.DomEvent.stopPropagation(e)
}
return (
<Map animate={animate} center={userLocation} onClick={setMarkers} onLocationFound={handleOnLocationFound} zoom={zoom} ref={saveMap}>
{markerData && markerData.map((element, index) => {
return (
<Marker
key={index}
marker_index={index}
position={element}
draggable={true}
onClick={markerClick}
onDragend={updateMarker}
icon={element.id === 0 ? startIcon : endIcon}
/>
)
})}
<MapboxLayer
accessToken={MAPBOX_ACCESS_TOKEN}
style="mapbox://styles/mapbox/streets-v9"
/>
<LocateControl startDirectly />
{isRoutingDone && <Routing isRoutingDone={isRoutingDone} map={map} myMapRef={myMapRef} icon={{startIcon, endIcon}} userLocation={userLocation} coords={{fromLat, fromLon, toLat, toLon}} />}
</Map>
)
}
I got rid of the the code that's not important to the problem at hand!
Thanks in advance!
I actually wound up solving it myself!
Think the head scratcher was while react-leaflet has its lifeCycle methods, i.e. createLeaflet, updateLeafletElement you should not forget the regular React life methods!
Not sure if they overlap, but I found adding componentDidMount:
componentDidMount() {
const { map } = this.props;
map.addControl(this.routing);
}
with updateLeafletElement (I'm using the API for the function correctly now) it accepts a fromProps and toProps to just check the value of the prop I pass to the Routing Machine...
updateLeafletElement(fromProps, toProps) {
if (toProps.removeRoutingMachine !== false) {
this.routing.setWaypoints([]);
}
}
Finally on unmount, use the removeControl method on the map you pass into the Routing Machine to remove the Router-Machine!
import { MapLayer } from 'react-leaflet';
import L from 'leaflet';
import 'leaflet-routing-machine';
import { withLeaflet } from 'react-leaflet';
class Routing extends MapLayer {
constructor(props) {
super(props);
}
createLeafletElement(props) {
const { map, coords, icon } = this.props;
var dStart = L.latLng(coords.fromLat, coords.fromLon);
var dGoal = L.latLng(coords.toLat, coords.toLon);
if (map && !this.routing) {
this.routing = L.Routing.control({
collapsible: true,
position: 'bottomleft',
lineOptions: {
styles: [{ color: 'chartreuse', opacity: 1, weight: 5 }]
},
waypoints: [dStart, dGoal],
createMarker: function(i, waypoints, n) {
var marker_icon;
if (i === 0) {
marker_icon = icon.startIcon;
} else if (i == n - 1) {
marker_icon = icon.endIcon;
}
var marker = L.marker(i === 0 ? dStart : dGoal, {
draggable: true,
icon: marker_icon
});
return marker;
}
});
}
return this.routing.getPlan();
}
componentDidMount() {
const { map } = this.props;
console.log('map ', map);
map.addControl(this.routing);
}
updateLeafletElement(fromProps, toProps) {
if (toProps.removeRoutingMachine !== false) {
this.routing.setWaypoints([]);
}
}
componentWillUnmount() {
this.destroyRouting();
}
destroyRouting() {
if (this.props.map) {
this.props.map.removeControl(this.routing);
}
}
}
export default withLeaflet(Routing);
Hope this helps! FYI: this is the app I'm using the Routing Machine in case you'd like to browse the other integrations...
I am working on a collaborative project, I wanted to know if I could change the state in my reducer by an onClick event listener or anything in my component.
const defaultState = {
pending: false,
error: null,
trips: {},
activeTrip: null,
privacy: false
}
export const tripReducer = (state = defaultState, action) => {
switch (action.type) {
case TOGGLE_VISIBILITY_ON:
return {
...state,
visibility: true,
}
case TOGGLE_VISIBILITY_OFF:
return {
...state,
visibility: false,
}
}
These are the cases that I am concerned with atm. So I want to change visibility once a person marks their trip public in the activepanel component.
code for activePanel
import React from "react"
import * as s from "./components"
import { connect } from "react-redux"
import moment from "moment"
import PropTypes from "prop-types"
import { TripPropTypes } from "../../propTypes"
import { Button } from "../../../styles/theme/styledComponents"
import { toggleWaypoint } from "../../../redux/actions/trips"
import marker from "../../icons/orange-marker.svg"
import startMarker from "../../icons/green-marker.svg"
import endMarker from "../../icons/black-marker.svg"
import { Link } from "react-router-dom"
class ActiveTripPanel extends React.Component {
constructor(props) {
super(props)
this.state = {
polylines: null,
markers: []
}
}
componentDidMount() {
setTimeout(() => {
this.renderWaypoints()
this.drawPolylines()
}, 500)
}
componentDidUpdate(prevProps) {
if (prevProps.waypoints !== this.props.waypoints) {
this.renderWaypoints()
this.drawPolylines()
}
}
drawPolylines = () => {
if (this.state.polylines !== null) {
this.state.polylines.active.setMap(null)
this.state.polylines.complete.setMap(null)
this.state.polylines.current.setMap(null)
}
let completeIndex = 0
for (let i = 0; i < this.props.waypoints.length; i++) {
if (!this.props.waypoints[i].complete) {
completeIndex = i
break
}
}
const completed = this.props.waypoints.slice(0, completeIndex)
const active = this.props.waypoints.slice(
completeIndex,
this.props.waypoints.length + 1
)
const current = this.props.waypoints.slice(
completeIndex - 1,
completeIndex + 2
)
const completePath = completed.map(waypoint => {
return { lat: waypoint.lat, lng: waypoint.lon }
})
const activePath = active.map(waypoint => {
return { lat: waypoint.lat, lng: waypoint.lon }
})
const currentPath = current.map(waypoint => {
return { lat: waypoint.lat, lng: waypoint.lon }
})
const completePolyline = new window.google.maps.Polyline({
path: completePath,
strokeColor: "#FF0000",
strokeOpacity: 1.0,
strokeWeight: 2
})
const currentPolyline = new window.google.maps.Polyline({
path: currentPath,
strokeColor: "#008000",
stokeOpacity: 1.0,
stokeWeight: 2
})
const activePolyline = new window.google.maps.Polyline({
path: activePath,
strokeColor: "#000000",
strokeOpacity: 1.0,
strokeWeight: 2
})
completePolyline.setMap(window.map)
activePolyline.setMap(window.map)
currentPolyline.setMap(window.map)
this.setState({
polylines: {
active: activePolyline,
complete: completePolyline,
current: currentPolyline
}
})
}
renderWaypoints = () => {
let markers = []
const baseIcon = {
anchor: new window.google.maps.Point(15, 30),
scaledSize: new window.google.maps.Size(30, 30),
labelOrigin: new window.google.maps.Point(15, 13)
}
const icons = {
start: {
url: startMarker,
...baseIcon
},
end: {
url: endMarker,
...baseIcon
},
marker: {
url: marker,
...baseIcon
}
}
this.props.waypoints.map((item, i) => {
const icon =
i === 0
? icons.start
: i === this.props.waypoints.length - 1
? icons.end
: icons.marker
let center = { lat: item.lat, lng: item.lon }
const marker = new window.google.maps.Marker({
position: center,
map: window.map,
icon,
title: item.name,
label: {
text: `${i + 1}`,
color: "white",
fontFamily: "Wals",
fontWeight: "bold"
}
})
markers.push(marker)
})
}
render() {
// console.log(match.params.tripId)
// const publicId = ({ match })
return (
<s.Panel>
{/* <s.PanelHeader>{this.props.trip.name}</s.PanelHeader>
<s.DateLabel>
Start: {moment(this.props.trip.start).format("YYYY-MM-DD")} - End:{" "}
{moment(this.props.trip.end).format("YYYY-MM-DD")}
</s.DateLabel> */}
<Link to={`/public/${this.props.trip.id}`}>Share Trip</Link>
<s.WaypointTracker>
{this.props.waypoints &&
this.props.waypoints.map(waypoint => (
<s.WaypointStepper key={waypoint.id}>
<div>
<h4>{waypoint.name}</h4>
<div>
ETA: {moment(waypoint.start).format("YYYY-MM-DD HH:mm")}
</div>
<div>
Status: Checked In #{" "}
{moment(waypoint.start).format("HH:mm")}
</div>
</div>
<div>
{waypoint.complete ? (
<Button
onClick={() => this.props.toggleWaypoint(waypoint.id)}
>
<i className="fa fa-check" />
</Button>
) : (
<Button
onClick={() => this.props.toggleWaypoint(waypoint.id)}
>
<i className="fa fa-times" />
</Button>
)}
</div>
</s.WaypointStepper>
))}
</s.WaypointTracker>
</s.Panel>
)
}
}
ActiveTripPanel.propTypes = {
trip: TripPropTypes,
waypoints: PropTypes.array.isRequired,
toggleWaypoint: PropTypes.func.isRequired
}
const mapStateToProps = ({ trips }) => ({
trip: trips.activeTrip,
waypoints: trips.activeTrip && trips.activeTrip.waypoints,
visibility: trips.visibility
})
export default connect(
mapStateToProps,
{ toggleWaypoint }
)(ActiveTripPanel)
I want to know if its possible or if theres another way to go about it. I am novice at redux. Thank you for the help.
I read the question's text, not the code (long... :)
It is very common (probably the most common way) to change the reducer's state following an event (e.g. onClick):
the onClick event calls an action creator
The action creator returns an action
The action is handled by the reducer
The reducer changes the state.
Does this make sense? I am not sure I understand your question, since this is a standard flow in redux.
BTW, you might want to read an excellent article which helped me when I started learning redux: https://www.sohamkamani.com/blog/2017/03/31/react-redux-connect-explained/
I have the following screen where I call a helper function pouchDB_helper.sync() to gather a bunch of data for me.
The goal is to be able to record where in the function it is currently at so I can give a percent or a status update in my render()
I'm new to react / react-native so I'm not sure if this is the right way to go about doing it. I'd like to be able to keep it as a helper function if possible because I use this function in other areas, this is just the only place I actually need a status update on where it's at in the process.
import React, { Component } from 'react';
import { ActivityIndicator, AsyncStorage, Button, StatusBar, Text, StyleSheet, View, } from 'react-native';
import * as pouchDB_helper from '../utils/pouchdb';
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'flex-start',
justifyContent: 'center',
padding: "5%",
backgroundColor: "#fff",
width:"100%"
},
statusHeader: {
fontSize: 18,
fontWeight: "600",
marginBottom: 10,
textAlign:'center',
width:'100%'
}
});
type Props = {};
export default class SyncScreen extends Component<Props> {
static navigationOptions = {
title: 'Syncing Settings',
};
render() {
pouchDB_helper.sync().then((response) => {
//IT'S DONE
}, (error) => { alert("THERE WAS AN ERROR"); });
return (
<View style={styles.container}>
<Text style={styles.statusHeader}>Syncing, please wait..</Text>
<Text>WHERE I WANT TO CHANGE TEXT</Text>
</View>
);
}
}
pouchDB_helper example
note: This is just an example. I know the .get() won't take long enough to warrant a status but I'm just trying to understand the concept.
import React from 'react';
import { AsyncStorage } from 'react-native';
import PouchDB from 'pouchdb-react-native'
export async function sync() {
const company_id = await AsyncStorage.getItem('company_id');
const device_db = new PouchDB(company_id, {auto_compaction: true});
//STATUS UPDATE 1
return device_db.get("settings").then((s) => {
//STATUS UPDATE 2
return device_db.get("get_this").then((s) => {
//STATUS UPDATE 3
return device_db.get("get_that").then((s) => {
//STATUS UPDATE 4
}, (error) => { return false; });
}, (error) => { return false; });
}, (error) => { return false; });
}
Simple approach would be passing a function to the sync function which can change the state and set the desired text on component.
Example
constructor(props) {
super(props);
this.state = {
level: 'some init value'
};
}
onChangeState = (level) => {
this.setState({level});
}
componentDidMount() {
pouchDB_helper.sync(this.onChangeState).then((response) => {
//IT'S DONE
this.onChangeState('Finished');
}, (error) => { alert("THERE WAS AN ERROR"); });
}
render() {
return (
<View style={styles.container}>
<Text style={styles.statusHeader}>Syncing, please wait..</Text>
<Text>{`Current level is ${this.state.level}`}</Text>
</View>
);
}
export async function sync(changeState) {
const company_id = await AsyncStorage.getItem('company_id');
const device_db = new PouchDB(company_id, {auto_compaction: true});
//STATUS UPDATE 1
changeState(1);
return device_db.get("settings").then((s) => {
//STATUS UPDATE 2
changeState(2);
return device_db.get("get_this").then((s) => {
//STATUS UPDATE 3
changeState(3);
return device_db.get("get_that").then((s) => {
//STATUS UPDATE 4
changeState(4);
}, (error) => { return false; });
}, (error) => { return false; });
}, (error) => { return false; });
}
Using react-native, I'm creating sub-Components within the parent App and providing their position to the array this.state.objLocation within the parent App.
I can get the initial location data into the array straight after the render, but because my subcomponents are draggable, each time they re-render on drag, it adds a new position object to the array.
I'd like to avoid this, and I thought that creating this.state = { firstRender: true } in the constructor and then using componentDidMount = () => { this.setState({ firstRender: false }) } after the first render would allow me to create a 'gate' to stop the addition of the extra position objects.
I can see that if I comment out //componentDidMount = () => { this.setState({ firstRender: false }) } then I will get multiple entries to my array but if it's included in the class I get absolutely none.
So possibly my interpretation of the render lifecycle and componentDidMount is incorrect?
Here is my code.
// App
import React, { Component } from 'react';
import { View, Text, } from 'react-native';
import styles from './cust/styles';
import Draggable from './cust/draggable';
const dataArray = [{num: 1,id: 'A',},{num: 2,id: 'B',},{num: 3,id: 'Z',}]
export default class Viewport extends Component {
constructor(props){
super(props);
this.state = {
dID : null,
objLocation: [],
firstRender: true,
};
}
render(){
return (
<View style={styles.mainContainer}>
<View style={styles.draggableContainer}>
<Text>Draggable Container</Text> {dataArray.map( d => { return(
<Draggable
id={d.id}
onLayout={ e=> this.onLayout(e)}
onPanResponderGrant={(dID) =>this.setState({ dID })}
onPanResponderRelease={() => this.setState({dID: null})} /> ) })}
<View style={[styles.findPoint ]} />
</View>
<View style={styles.infoBar}>
<Text>{this.state.dID ? this.state.dID : ''}</Text>{this.compFrame()}
</View>
</View>
);
}
onLayout = (e) => {
if ( e && this.state.firstRender) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
objLocation: this.state.objLocation.concat([position])
});
}
}
componentWillMount = () => {
console.log("START");
}
compFrame = () => {
return(
this.state.objLocation.map( d => {<View style={[styles.findPoint2,{left: d.x, top: d.y, width: d.width, height: d.height} ]} ></View>})
)
}
componentDidMount = () => {
this.setState({firstRender: true })
console.log(this.state.objLocation.length);
}
}
// Draggable
import React, { Component } from 'react';
import { Text, PanResponder, Animated } from 'react-native';
import styles from './styles';
class Draggable extends Component {
constructor(props) {
super(props);
this.state = {
pan: new Animated.ValueXY(),
};
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.props.onPanResponderGrant(this.props.id);
},
onPanResponderMove: Animated.event([ null, {
dx: this.state.pan.x,
dy: this.state.pan.y,
},
]),
onPanResponderRelease: () => {
Animated.spring(this.state.pan, { toValue: { x: 0, y: 0 } }).start();
this.props.onPanResponderRelease();
},
});
}
render() {
return (
<Animated.View
onLayout={ (e) => this.props.onLayout(e) }
{...this.panResponder.panHandlers}
style={[this.state.pan.getLayout(), styles.circleAlt, styles.position]}>
<Text style={styles.textAlt}>Drag me!</Text>
<Text style={styles.textNum}>{this.props.id}</Text>
</Animated.View>
);
}
componentDidMount = () => {
this.props.onLayout(this.props.dragEvent)
}
}
export default Draggable;
// Output of console.log
START xxx
0
{width:108,height:108,x:133.5,y:376.5}
{width:108,height:108,x:133.5,y:78.5}
{width:108,height:108,x:133.5,y:227.5}
You could set the firstRender state in onLayout function
onLayout = (e) => {
if ( e && this.state.firstRender) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
firstRender: false,
objLocation: this.state.objLocation.concat([position])
});
}
}
According to the information provided by you, your onLayout function is called by the component so its not included in the component lifecycle process, so when the component completes its lifecycle it goes into componentDidMount after mounting (which is not calling onLayout func) & thus changed the firstRender state to false and hence when you drag the component each time it goes from true to false.
I hope this explains
I feel like I've hacked this, to get it to work, so please correct me as to correct procedure.
This is the onLayout method from the App. I've included an if statement that checks if the new positions array length is equal too the dataArray length that the draggable items are based on.
It looks like this.
onLayout = (e) => {
if ( this.state.objLocation.length != dataArray.length ) {
if ( e ) {
const n = e.nativeEvent.layout;
const position = {
width: n.width,
height: n.height,
x: n.x,
y: n.y
}
console.log(position);
this.setState({
objLocation: this.state.objLocation.concat([position])
});
}
}
}