I am a bit confused as to how to get this to work. I have three components - ReportsWrapper, ReportsNearby, and ReportSingle:
1.) ReportsWrapper gets the device location data then calls ReportsNearby passing the location as a prop
2.) ReportsNearby then uses that location to populate a "reports" collection from the mongodb
3.) Each record from "reports" is rendered via a ReportSingle component
Problem is that the "reports" collection does not populate the page at first load. The page loads, no data (data from mongodb) is shown on the client.
If I navigate to a different page (like /about or /settings) then back to ReportsWrapper, the collection seems to populate as expected and a ReportSingle is displayed for each record. Once I refresh on ReportsWrapper the collection is once again empty.
Any ideas?
ReportsWrapper.jsx
import React, {Component} from "react";
import TrackerReact from "meteor/ultimatejs:tracker-react";
import ReportsNearby from "./ReportsNearby.jsx";
export default class ReportsWrapper extends TrackerReact(Component) {
render() {
if (Geolocation.latLng() == null) {
return (
<span>Determining location...</span>
)
}
return (
<div>
<h1>Reports</h1>
<ReportsNearby latLng={Geolocation.latLng()} />
</div>
)
}
}
ReportsNearby.jsx
import React, {Component} from "react";
import TrackerReact from "meteor/ultimatejs:tracker-react";
import ReportSingle from "./ReportSingle.jsx";
import NearbyMap from "../maps/NearbyMap.jsx"
export default class ReportsNearby extends TrackerReact(Component) {
constructor(props) {
super(props);
this.state = {
subscription: {
reports: Meteor.subscribe("nearbyReports", 5, props.latLng)
}
}
}
componentWillUnmount() {
this.state.subscription.reports.stop();
}
_reports() {
return Reports.find().fetch();
}
render() {
console.log(this._reports()); // this is empty - why???
return (
<div>
<ul>
{this._reports().map((report, index)=> {
return <ReportSingle key={report._id}
report={report}
})}
</ul>
</div>
)
}
}
ReportSingle.jsx
import React, {Component} from 'react';
export default class ReportSingle extends Component {
render() {
return (
<li>
<a href={`/report/${this.props.report._id}`}>
{this.props.report.category}<br/>
{this.props.report.description}<br/>
{this.props.report.status}
</a>
</li>
)
}
}
routes.jsx
import React from "react";
import {mount} from "react-mounter";
import {MainLayout} from "./layouts/MainLayout.jsx";
import ReportsWrapper from "./reports/ReportsWrapper.jsx";
import Settings from "./Settings.jsx";
import About from "./About.jsx";
FlowRouter.route('/', {
action() {
mount(MainLayout, {
content: <ReportsWrapper />
})
}
});
FlowRouter.route('/settings', {
action() {
mount(MainLayout, {
content: <Settings />
})
}
});
FlowRouter.route('/about', {
action() {
mount(MainLayout, {
content: <About />
})
}
});
publish.jsx
Meteor.publish("nearbyReports", function (limit, latLng) {
Reports._ensureIndex({'lngLat': '2dsphere'});
return Reports.find({
lngLat: {
$near: {
$geometry: {
type: "Point",
coordinates: [latLng.lng, latLng.lat]
},
$minDistance: 0,
$maxDistance: 3218.69 // this is 2 miles in meters
}
}
}, {
sort: {createdAt: -1},
limit: limit,
skip: 0
});
});
I ended up getting this working by adding the following code to ReportsWrapper.jsx. I think that ReportsNearby was not getting the latLng prop as expected, even though I had console logged it to verify.
tl;dr Use a timer interval to make sure your app has received the location.
constructor () {
super();
this.state = {
handle: null,
latLng: Geolocation.latLng()
}
}
componentDidMount() {
if (this.state.latLng != null) {
return
}
// Attempt to reload the location if it was not found. do this because the device
// location is not immediately available on app start
this.state.handle = Meteor.setInterval(()=> {
if (Geolocation.latLng() != null) {
this.setState({latLng: Geolocation.latLng()}); // Setting the state will trigger a re-render
Meteor.clearInterval(this.state.handle); // Stop the interval function
}
}, 500)
}
componentWillUnmount() {
// Make sure interval is stopped
Meteor.clearInterval(this.state.handle);
}
Related
I am getting issues while rendering contact data.
Here the case is when I click the continue button in my app it triggers the checkoutContinuedHandler() function that results in a change of URL but the ContactData component is not rendered and my CheckoutSummary component also vanishes as I am rendering it on the same page.
I Checked twice that export is done and there is no spelling mistakes.
I tried different solutions from the stack and discussed them with my mate still the issue is on...
import React, { Component } from "react";
import { Route } from "react-router-dom";
import CheckoutSummary from "../../components/Order/CheckoutSummary/CheckoutSummary";
import ContactData from "./ContactData/ContactData";
class Checkout extends Component {
state = {
ingredients: {
salad: 1,
meat: 1,
cheese: 1,
bacon: 1,
},
};
componentDidMount() {
const query = new URLSearchParams(this.props.location.search);
const ingredients = {};
for (let param of query.entries()) {
// ['salad','1']
ingredients[param[0]] = +param[1];
}
this.setState({ ingredients: ingredients });
}
checkoutCancelledHandler = () => {
this.props.history.goBack();
};
checkoutContinuedHandler = () => {
this.props.history.replace("/checkout/contact-data");
console.log(this);
};
render() {
return (
<div>
<CheckoutSummary
ingredients={this.state.ingredients}
checkoutCancelled={this.checkoutCancelledHandler}
checkoutContinued={this.checkoutContinuedHandler}
/>
<Route
path={this.props.match.path + "/contact-data"}
component={ContactData}
/>
</div>
);
}
}
export default Checkout;
I am getting the following error in my code. Can you please help me to understand the issue. I have included my page component and task list component.
TypeError: this.state.tasks.map is not a function
Page Show.js
import React, { Component } from 'react';
import axios from 'axios';
import TasksList from './TasksList';
export default class Show extends Component {
constructor(props) {
super(props);
this.state = {tasks: [] };
}
componentDidMount(){
axios.post('http://mohamed-bouhlel.com/p5/todolist/todophp/show.php')
.then(response => {
this.setState({ tasks: response.data });
})
.catch(function (error) {
console.log(error);
})
}
tasksList(){
return this.state.tasks.map(function(object,i){
return <TasksList obj = {object} key={i} />;
});
}
render() {
return (
<div>
{ this.tasksList() }
</div>
)
}
}
Page TasksList.js
import React, { Component } from 'react';
export default class TasksList extends Component {
render() {
return (
<div>
<div>{this.props.obj.task}</div>
</div>
)
}
}
Using a GET request and correct protocol (https vs http) seems to resolve the issue.
axios.get("https://mohamed-bouhlel.com/p5/todolist/todophp/show.php")
Response.data is not an array and basically you can't call map on a non-array.
I suggest console.log(response.data) to check the data type.
And I guess maybe you're doing a axios.post instead of a correct axios.get. log the response.data and you'll find out.
This is a reoccurring problem for me… Trying to figure out why an update to a single item in a component results in the entire component re-rendering. If I have a CSS fade in transition on the component, it fades in again when changing a child of the component.
I have a list of items, each with a link. Clicking the link adds the item to the cart. I have it set up to put that item in a “loading” state until the cart action is successful.
This used to work perfectly, but now it just re-renders the entire page, making it disappear for a second then reappear. I’m not entirely sure why.
This is the code stripped down to its basic bits:
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import autobind from 'class-autobind';
import Responsive from 'components/Responsive';
// Selectors
import { createStructuredSelector } from 'reselect';
import { selectCartLoading, selectCartMap, selectFavorites } from 'containers/App/selectors';
import { selectPackages } from 'store/fonts/selectors';
// Actions
import { addToCart } from 'containers/App/actions';
export class Packages extends React.Component {
constructor() {
super();
autobind(this);
}
state = {
loadingID: 0
}
componentWillReceiveProps(nextProps) {
if (this.props.cartLoading === true && nextProps.cartLoading === false) {
this.setState({ loadingID: 0 });
}
}
onAddToCart(e) {
e.preventDefault();
const { onAddToCart } = this.props;
const id = e.currentTarget.dataset.package;
const packageData = {
type: 'package',
id,
quantity: 1
};
onAddToCart(packageData);
this.setState({ loadingID: id });
}
render() {
const { cartMapping, packages } = this.props;
if (!packages) { return null; }
return (
<Responsive>
<div>
<ul>
{ packages.map((pack) => {
const inCart = !!cartMapping[parseInt(pack.id, 10)];
const isFavorited = !favorites ? false : !!find(favorites.favorites, (favorite) => parseInt(pack.id, 10) === favorite.items.id);
return (
<li key={ pack.id }>
<Icon iconName="heart" onClick={ (e) => this.onAddFavorite(e, pack) } />
<span>{ pack.name }</span>
{ inCart && <span>In Cart</span> }
{ !inCart && <a data-package={ pack.id } href="/" onClick={ this.onAddToCart }>Add to Cart</a> }
</li>
);
})}
</ul>
</div>
</Responsive>
);
}
}
Packages.propTypes = {
cartLoading: PropTypes.bool,
cartMapping: PropTypes.object,
onAddToCart: PropTypes.func.isRequired,
packages: PropTypes.array
};
Packages.defaultProps = {
cartLoading: null,
cartMapping: null,
packages: null
};
const mapStateToProps = createStructuredSelector({
cartLoading: selectCartLoading(),
cartMapping: selectCartMap(),
packages: selectPackages()
});
function mapDispatchToProps(dispatch) {
return {
onAddToCart: (data) => dispatch(addToCart(data)),
dispatch
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Packages);
So why does clicking on <a data-package={ pack.id } href="/" onClick={ this.onAddToCart }>Add to Cart</a> result in a complete component re-render?
In your onAddToCart function you are setting the state of the component which will by default trigger a re-render of the component. If you need to set the state but not cause a re-render you can add a shouldComponentUpdate() function and check the changes before issuing a re-render to the component.
Find out more about shouldComponentUpdate() and the rest of the component lifecycle here
Here is my code:
ChartActions.js
import * as types from './ChartTypes.js';
export function chartData(check){
return { type: types.CHART_DATA,check };
}
ChartTypes.js
export const CHART_DATA = 'CHART_DATA';
ChartReducers.js
import {
CHART_DATA,
}from './ChartTypes.js';
const initialState = {
chartData : [],
}
export default function ChartReducers(state = initialState, action) {
switch (action.type) {
case CHART_DATA :
return Object.assign({}, state, {
chartData : action.check
});
default:
return state;
}
}
I am so sure that I setup redux quite accurate and it works perfectly. My problem is:
In a component A I dispatch a function:
handleClick(){
this.props.ChartActions.chartData("test string")
}
so in theory, a component B in my project will receive the string "test string" right after the handeClick function triggered, like this
componentWillReceiveProps(){
console.log(this.props.chartData) // test string
}
But I have no idea why SOMETIMES (it only happens sometimes) I have to trigger handleClick function TWO times in component A so that the component B could be able to get the updated state (in this example, it is "test string"). I supposed it's a bug.
I need the component B will receive the updated state (i.e "test string") RIGHT AFTER the handleClick is triggered only ONE TIME.
I have a container:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as ChartActions from '../../components/center-menu/services/ChartActions.js';
import CenterMenu from '../../components/center-menu/center-menu-index.js'
import RightMenu from '../../components/right-content/right-content-index.js'
class Home extends Component {
render() {
return (
<div>
<CenterMenu
ChartActions = {this.props.ChartActions}
/>
<RightMenu
ChartProps={this.props.ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Here is the component A where I fire an actions:
import React, { Component } from 'react';
class CenterMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
handleClick(){
this.props.ChartActions.chartData('test string')
}
render() {
return (
<div className="center_menu" onClick={this.handleClick.bind(this)}>
Some stuff
</div>
)
}
}
export default CenterMenu;
And in another component B:
import React, { Component } from 'react';
class RightMenu extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.state = {};
}
}
componentWillReceiveProps(){
console.log(this.props.ChartProps.chartData, "right here")
}
render() {
return (
<div className="center_menu">
Some stuff
</div>
)
}
}
export default RightMenu;
Weird thing:
In Component A, if I trigger the handleClick function by clicking in a div tag, it fires an action that change the initial state to "test string"
But...
In the component B the statement
console.log(this.props.ChartProps.chartData, "right here")
show empty string first like this:
right here
But when I trigger the handleClick function the SECOND TIME in component A , then in component B, in the statement
console.log(this.props.ChartProps.chartData, "right here")
it show the following:
test string "right here"
which is the result I want to achieve.
But I don't understand why I HAVE TO trigger the handleClick function twice. I need it by one click.
The problem is your Home component doesn't rerender the children. Try keeping ChartProps in a state in Home like so:
class Home extends Component {
state = {
ChartProps: null //you can use some default value, this might cause undefined is not an object error in you children
}
componentDidMount() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
componentWillReceiveProps() {
const { ChartProps } = this.props
this.setState(() => ({ ChartProps }))
}
render() {
const { ChartProps } = this.state
return (
<div>
<CenterMenu
ChartActions={this.props.ChartActions}
/>
<RightMenu
ChartProps={ChartProps}
/>
</div>
);
}
}
function mapStateToProps(state) {
return {
ChartProps: state.ChartReducers
};
}
function mapDispatchToProps(dispatch) {
return {
ChartActions: bindActionCreators(ChartActions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);
I'm using react 15.3.1, with react-router 2.4.1 and react-router-redux 4.0.5:
When I trap the routing change with:
this.props.router.setRouteLeaveHook(
this.props.route,
this.routerWillLeave
);
private routerWillLeave = () => {
if (this.state.editing)
return 'You may have unsaved changes. Are you sure you want to leave?'
};
... I do get my this.routerWillLeave method called, but the URL in the browser still changes, so even if the user stays on the page by deciding not to leave the page, the URL is now wrong. Ideas?
export default class extends Component {
static contextTypes = {
router: React.PropTypes.object.isRequired
}
state = {
editing: true
}
componentDidMount() {
this.context.router.setRouteLeaveHook(this.props.route, () => {
if (this.state.editing) {
return false;
// At here you can give a confirm dialog, return true when confirm true
}else {
return true;
}
})
}
}
And if your react-route >2.4, you can also use withRouter to wrap your component, this's may be better!
import React, {Component} from 'react';
import {render} from 'react-dom';
import {withRouter} from 'react-router';
export default withRouter(class extends Component {
state = {
unsaved: true
}
componentDidMount() {
this.props.router.setRouteLeaveHook(this.props.route, () => {
if (this.state.unsaved) {
return false;
// At here you can give a confirm dialog, return true when confirm true
}else {
return true;
}
})
}
render() {
return (
<div>
<h2>About</h2>
{this.props.children || "This is outbox default!"}
</div>
)
}
})