Geolocation - can't get latitude and longitude in React/Redux app - reactjs

I have following action creator:
export const getLocation = () => {
const geolocation = navigator.geolocation;
const location = new Promise((resolve, reject) => {
if (!geolocation) {
reject(new Error('Not Supported'));
}
geolocation.getCurrentPosition((position) => {
resolve(position);
}, () => {
reject (new Error('Permission denied'));
});
});
return {
type: GET_LOCATION,
payload: location
}
};
And following reducer for GET_LOCATION type:
case GET_LOCATION: {
return {
...state,
location: {
latitude: action.location.coords.latitude,
longitude: action.location.coords.latitude,
}
}
}
I try to use this data in my component like so:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getLocation } from '../actions';
import { bindActionCreators } from 'redux';
class UserLocation extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.getLocation();
}
render() {
return (
<div>
<div><span>latitude:</span>{this.props.location.latitude}
</div>
<div><span>longitude:</span>{this.props.location.longitude} </div>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return bindActionCreators( { getLocation }, dispatch )
}
export default connect(null, mapDispatchToProps)(UserLocation);
But everytime when I load this component I get TypeError: Cannot read property 'latitude' of undefined
Can you please point me where I was wrong?

The geolocation data will be resolved asynchronously, and is not available the first time your component is rendered. You need to correctly handle the case where that data is not yet available, and there's several ways you can do that. Please see the article Watch Out for Undefined State for descriptions of how to handle data that is not available.

You should use componentWillReceiveProps to receive the returned data from dispatched action.
componentWillReceiveProps = (nextProps) => {
if (this.props.location.latitude !== nextProps.location.latitude) {
// You will have location object here that is returned from recuder
}
}
Check this alternative way to call geocoding to fetch lat and long in react with redux
https://medium.com/#eesh.t/auto-detect-location-react-component-using-google-geocoding-and-reverse-geocoding-in-autocomplete-66a269c59315

You're doing the request in componentWillMount() that it's before mount the component, but this doesn't not mean that the component will wait for the request end. Therefore you should put some validation in the render method like if(!this.props.location) return <div>Loading...</div>

I think I used too the example from the same Codepen as you, in it the author uses a very simplified version of a React Promise Middleware.
But suggest that you use a redux-promise-middleware so you can control how the promise will be resolved.
So you should import the middleware and include it in applyMiddleware when creating the Redux store:
import promiseMiddleware from 'redux-promise-middleware'
composeStoreWithMiddleware = applyMiddleware(
promiseMiddleware(),
)(createStore)
The promise that you're dispatching in your action creator must be handled in the reducer with a FULFILED promise suffix (there are another ways to do the same but you should be ok with this one, also I'm not a native English speaker, so watchout with typos in _FULFILLED).
case `${GET_LOCATION}_FULFILLED`:
return {
...state,
location: {
latitude: action.location.coords.latitude,
longitude: action.location.coords.latitude,
}
}
I use the location object from the render() method so you can use const mapStateToProps so you can use it from there.
const mapStateToProps = (state) => {
return {location: state.location};
};
Then at the render method of your component you can destructure your location object from the props:
render() {
const { location: { latitude, longitude } } = this.props;
return (
<div>
<div><span>latitude:</span>{latitude}</div>
<div><span>longitude:</span>{longitude}</div>
</div>
);

Related

Redux api calling

I'm wanting to update my trending array with the results calling the tmdb api. I'm not sure if im going about this the wrong way with calling the api or if im messing up somewhere else along the way. So far I've really been going in circles with what ive tried. Repeating the same things and not coming to a real solution. Havent been able to find another question similar to mine.
my actions
export const getTrending = url => dispatch => {
console.log("trending action");
axios.get(url).then(res =>
dispatch({
type: "TRENDING",
payload: res.data
})
);
};
my reducer
const INITIAL_STATE = {
results: [],
trending: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case "SEARCH_INFO":
return {
results: [action.payload]
};
case "TRENDING":
return { trending: action.payload };
default:
return state;
}
};
and my component im trying to get the results from
import React, { Component } from "react";
import Trending from "./Treding";
import "../App.css";
import { getTrending } from "../actions/index";
import { connect } from "react-redux";
export class Sidebar extends Component {
componentDidMount = () => {
const proxy = `https://cors-anywhere.herokuapp.com/`;
getTrending(`${proxy}https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};
render() {
return (
<div>
<h3 className="trending">Trending</h3>
{
this.props.trending ? (
<Trending movies={this.props.trending} />
) : (
<div>Loading</div>
)}
</div>
);
}
}
const mapStateToProps = state => {
return {
trending: state.trending
};
};
export default connect(mapStateToProps)(Sidebar);
Since you are directly calling the getTrending without passing it to connect method, it might be the issue.
Instead that you can pass getTrending to connect method so it can be available as props in the component. After that it can be dispatched and it will be handled by redux/ redux-thunk.
export default connect(mapStateToProps, { getTrending })(Sidebar);
And access it as props in the component.
componentDidMount = () => {
// const proxy = `https://cors-anywhere.herokuapp.com/`;
this.props.getTrending(`https://api.themoviedb.org/3/trending/all/day?api_key=53fbbb11b66907711709a6f1e90fc884
`);
};

dispatch an action in componentDidMount which receives a redux props as payload

What is the best way to trigger an action inside componentDidMount () using a redux props? ex:
import { fetchUser } from '../actions'
class Example extends Component {
ComponentDidMount(){
this.props.fetchUser(this.props.id)
} ...
mapDispatchToProps = dispatch => ({
fetchUser: (payload) => dispatch(fetchUser(payload))
})
mapStateToProps = state => ({
id: state.user.id
})
The problem is that ComponentDidMount () is mounted before the class even receives props from the store. That way my this.props.id is = 'undefined' inside the method.
One solution I found was to run as follows but I do not know if it's the best way:
import { fetchUser } from '../actions'
class Example extends Component {
fetchUser = () => {
this.props.fetchUser(this.props.id)
}
render(){
if(this.props.id !== undefined) this.fetchUser()
} ...
}
mapDispatchToProps = dispatch => ({
fetchUser: (payload) => dispatch(fetchUser(payload))
})
mapStateToProps = state => ({
id: state.user.id
})
That way I get the requisition, but I do not think it's the best way. Any suggestion?
Have you tried using async/await?
async ComponentDidMount(){
await this.props.fetchUser(this.props.id)
} ...
You have to understand the lifecycle of react components. When the component gets mounted, it can fetch data, but your component at that point needs something to render. If the data hasn't been loaded yet, you should either return null to tell react that it's not rendering anything at that point, or perhaps a loading indicator to show that it's fetching data?
import { fetchUser } from '../actions'
class Example extends Component {
componentDidMount() {
this.props.fetchUser();
}
render(){
const { loading, error, user } = this.props;
if (loading) {
return <LoadingIndicator />;
}
if (error) {
return <div>Oh noes, we have an error: {error}</div>;
}
// Render your component normally
return <div>{user.name}</div>;
}
}
Your reducer should have loading set to true by default, and when your fetch completes, set loading to false, and either set the user or error depending on if the fetch fails/completes.

How to access redux-store from within react's componentDIdMount()

In the following code I am trying to pass the state.userData.userDetails from the redux-store to getleftpaneProductCatalogue(), but state.userData.userDetails is unaccessible to componentDidMount(). I tried assigning the state.userData.userDetails to this.prop.userProfile, but still this.prop.userProfile is an empty value. How to access the prop within componentDidMount?
import React,{Component} from 'react';
import { connect } from 'react-redux';
import {Row, Col } from 'react-materialize';
import {getleftpaneProductCatalogue} from '../actions/leftpane-actions';
import ProductCatalogueLeftPaneComp from '../components/pages/product-catalogue-leftpane';
class ProductCatalogueLeftPane extends Component {
constructor(props) {
super(props)
}
componentDidMount() {
console.log('this.props^', JSON.stringify(this.props));
this.props.getleftpaneProductCatalogue().then((data) => {
console.log('productdata', data);
})
}
render() {
return (
<div>
{JSON.stringify(this.props.userProfile)}
</div>
)
}
}
const mapStateToProps = (state) => {
console.log('state^', JSON.stringify(state));
return {leftpaneProductCatalogue: state.leftpaneProductCatalogue, userProfile: state.userData.userDetails};
};
const mapDispatchToProps = (dispatch) => {
return {
getleftpaneProductCatalogue: () => dispatch(getleftpaneProductCatalogue()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ProductCatalogueLeftPane);
You can access the state directly in mapDispatchToProps and pass it to getleftpaneProductCatalogue:
componentDidMount() {
const { dispatch, getleftpaneProductCatalogue }
dispatch(getleftpaneProductCatalogue())
}
const mapDispatchToProps = dispatch => {
return {
getleftpaneProductCatalogue: () => (dispatch, getState) => {
const state = getState()
const details = state.userData.userDetails
return dispatch(getleftpaneProductCatalogue(details))
},
dispatch
}
}
However, the way you're doing it, passing the state via mapStateToProps is still valid, but more verbose. Therefore the problem would be somewhere else.
Here's my bet. I guess you're getting the userData somewhere in your code with async API call and it's not being fetched yet. If that's the case - then you should wait for data being fetched firstly, then you can access it in your component ProductCatalogueLeftPane.

ComponentWIllReceiveProps not getting called unless page is refreshed

I am building a react native application but I noticed that componentWillReceiveProps is not getting called as soon as I dispatch some actions to the redux store, it only gets called when I refresh the screen.
Component
import React from 'react';
import { connect } from 'react-redux';
import { renderLogin } from '../../components/Auth/Login';
class HomeScreen extends React.Component {
componentWillReceiveProps(props) {
const { navigate } = props.navigation;
if (props.userData.authenticated) {
navigate('dashboard')
}
}
login = () => {
renderLogin()
}
render() {
const { navigate } = this.props.navigation;
return (
<Container style={styles.home}>
// Some data
</container>
)
}
}
function mapStateToProps(state) {
return {
userData: state.auth
}
}
export default connect(mapStateToProps)(HomeScreen)
RenderLogin
export function renderLogin() {
auth0
.webAuth
.authorize({
scope: 'openid email profile',
audience: 'https://siteurl.auth0.com/userinfo'
})
.then(function (credentials) {
loginAction(credentials)
}
)
.catch(error => console.log(error))
}
loginAction
const store = configureStore();
export function loginAction(credentials) {
const decoded = decode(credentials.idToken);
saveItem('token', credentials.idToken)
store.dispatch(setCurrentUser(decoded));
}
export async function saveItem(item, selectedValue) {
try {
await AsyncStorage.setItem(item, JSON.stringify(selectedValue));
const decoded = decode(selectedValue);
} catch (error) {
console.error('AsyncStorage error: ' + error.message);
}
}
I believe your problem has something to do with mapStateToProps, i.e. when you have updated your state in redux but not yet map the new state to your props, therefore props in HomeScreen will remain unchanged and componentWillReceiveProps will only be triggered once.
Have a read on Proper use of react-redux connect and Understanding React-Redux and mapStateToProps.

How can I do ajax call each time when store gets updated?

How can I do an ajax call each time the store gets updated?
Basically, I want to fetch products with new API params, let's say there is a drop-down for items per page. It is working fine on load, i.e on call of method componentWillMount
But I'm not sure how to do a fetch again on when the store changes.
import React, { Component } from 'react';
import ProductsList from '../components/ProductsList'
import { connect } from 'react-redux'
// Action Creators
import doFetchProducts from '../actions/doFetchProducts'
import queryString from 'query-string'
class Products extends Component {
constructor (props) {
super(props);
}
componentWillMount () {
this.fetch()
}
fetch () {
let q = queryString.stringify({
categories: 'rings',
'attributes.Style': 'Classic',
limit: this.props.applyItemsPerPage,
page: 1,
status: 'Active'
})
this.props.dispatch(
doFetchProducts(q)
)
}
render() {
return (
<section className="content">
<ProductsList {...this.props} />
</section>
);
}
}
const mapStateToProps = state => {
return {
products: state.applyFetchProducts.products,
isLoading: state.applyFetchProducts.isLoading,
itemsPerPage: state.applyItemsPerPage
}
}
export default connect(
mapStateToProps
)(Products);
Thanks in advance.
You can use Redux's store.subscribe to listen for changes.
componentWillMount() {
this.unsubscribeStore = store.subscribe(this.onStoreChange);
this.setState({
currentState: store.getState()
});
}
componentWillUnmount() {
this.unsubscribeStore();
}
onStoreChange = () => {
const newState = store.getState();
// Check the relevant part of store for changes.
// Depends on your store implementation
if (newState.reducer.prop !== this.state.currentState.reducer.prop) {
this.fetch();
}
}
this.onStoreChange will get called on each dispatch, so be careful not to create infinite loops. That is also the reason why you have to manually check for changes when the callback function is executed.

Resources