withMulti results become undefined on terms change
See original GitHub issueHi, all is in the title. I setup a Map component that rely on withMulti. The architecture is:
- The parent component wraps its children with
withMulti
(as the Datatable does with DatatableContent) - The children display results on map
- There is an intermediate component that manage
terms
update when the user moves
Terms are correctly updated. But if I move the Map, the marker disappear, as results become undefined
(ie graphql
props.data[resolverName]
is undefined). No query is triggered according to network. A call to refetch
fixes but provokes blinking and sounds like a palliative.
However, when the next polling is triggered, the correct data are retrieved. It’s just changing terms
that does not behave as expected.
This issue seems to have appeared to other people, but not all the time, so it seems related to how we define the React component and how we use withMulti
.
I can reproduce even with apollo-client
and apollo-react
version 2 as well as the current Vulcan version. Maybe smth is failing silently ?
I will try to setup a minimal repro, but in the meantime here is my component code, in case someone understand this issue (or want to setup a Leaflet Map that almost work 😃):
import React, { Component } from 'react';
import { Components, registerComponent, withMulti } from 'meteor/vulcan:core';
// the parent, it creates the component wrapped with `withMulti` and
// handle the initial render
class GeoSearchPage extends Component {
render() {
const { collection, defaultTerms, ...otherProps } = this.props;
const withMultiOptions = {
collection,
terms: defaultTerms
};
const GeoSearchPageInnerWithMulti = withMulti(withMultiOptions)(
Components.GeoSearchPageInner
);
return (
<ViewportProvider
{...otherProps}
defaultTerms={defaultTerms}
C={GeoSearchPageInnerWithMulti}
/>
);
}
}
// manage state, is in charge of updating terms and passing to its child,
// which is a component wrapped with withMulti
class ViewportProvider extends Component {
constructor(props) {
super(props);
this.state = {
currentTerms: props.defaultTerms,
currentViewport: null
};
}
isValidViewport = viewport => {
return (
typeof viewport.center[0] === 'number' &&
typeof viewport.center[1] === 'number' &&
viewport.zoom < Infinity
);
};
shouldUpdateTerms = newViewport => {
const { currentViewport } = this.state;
if (!this.isValidViewport(newViewport)) return false;
if (!currentViewport && newViewport) return true;
return (
newViewport.center[0] !== currentViewport.center[0] ||
newViewport.center[1] !== currentViewport.center[1] ||
newViewport.zoom !== currentViewport.zoom
);
};
computeNewTerms = viewport => {
const { zoom, center } = viewport;
const [lat, lng] = center;
const terms = { ...this.state.currentTerms };
terms.geoQuery.point.lat = lat;
terms.geoQuery.point.lng = lng;
terms.geoQuery.maxDistance = 10000; // TODO compute depending on zoom
return terms;
};
onViewportChanged = viewport => {
const newTerms =
this.shouldUpdateTerms(viewport) && this.computeNewTerms(viewport);
const newViewport = this.isValidViewport(viewport) && viewport;
let newState = {};
if (newTerms) newState['currentTerms'] = newTerms;
if (newViewport) newState['currentViewport'] = newViewport;
this.setState(newState);
};
render() {
const { C, ...otherProps } = this.props;
const { currentViewport, currentTerms: terms } = this.state;
const additionalProps = {
viewport: currentViewport,
terms: terms,
onViewportChanged: this.onViewportChanged
};
// C is a component wrapped with `withMulti`, we pass the terms props to it
return <C {...otherProps} {...additionalProps} />;
}
}
// some helper, ignore it
const computePositions = (results, latLngField = 'placeLatLng') => {
return (
results.filter(r => !!r[latLngField]).map(r => r[latLngField])
.filter(
({ lat, lng }) => typeof lat === 'number' && typeof lng === 'number'
).map(({ lat, lng }) => [lat, lng])
);
};
// inner map, that will be wrapped with withMulti
class GeoSearchPageInner extends Component {
componentWillReceiveProps(newProps) {
if (newProps.terms !== this.props.terms) {
// will work, but the marker still blink as `results` is temporarily undefined
// refetch should not be necessary I think
this.props.refetch()
}
}
render() {
const {
results = [],
onViewportChanged,
...otherProps
} = this.props;
const positions = computePositions(results);
return (
<div>
<Components.LeafletMap
positions={positions}
onViewportChanged={onViewportChanged}
{...otherProps}
/>
</div>
);
}
}
registerComponent({
name: 'GeoSearchPageInner',
component: GeoSearchPageInner
});
registerComponent({ name: 'GeoSearchPage', component: GeoSearchPage });
Issue Analytics
- State:
- Created 5 years ago
- Comments:11 (11 by maintainers)
Top GitHub Comments
i think it can be done here, it should not break things: the only thing we are doing is taking some work out of the queryReducer and doing it above, the same functions will be ran with the same arguments, just a bit earlier in the component tree. I’m doing the PR, this way we can test it and see if we keep it or not
Yes, that’s already done on the Apollo 2 branch.