reactjsrxjsgis

React-rxjs - bind - Multiple Observables are only coming out with last one


I've got this GIS mapping code:

const dummyFeature = {"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]}} as Feature

export const [useOneEOWData, OneEowData$] = bind<Feature>(
  // User Moves or Zooms map
  zoomMove$.pipe(
    tap(({ bBoxString }) => console.log(`zoom move 2: ${bBoxString}`)),
    switchMap(({ bBoxString }) => EOWObservationData.getLayers(bBoxString)),
    tap((data) => console.log(`use oneEowData tap: ${JSON.stringify(data)}`)),
    concatMap((a) => {
      const features = a && a.length ? a[0].data?.features : null
      return features ? from(features) : null;
    }),
    tap((features: Feature) => console.log(`use oneEowData feature: ${JSON.stringify(features)}`)),
  ),
  dummyFeature,
);

OneEowData$.subscribe();

The EOWObservationData.getLayers is a fromFetch call that returns an array of results (Observable) and after wrapping that up it is available in a[0].data.features.

The plan is to break each of the features in the array out as its own Observable and then action each one in the React components. But the react component is only showing the final Observable.

The 2nd tap shows the complete data returned from EOWObservationData.getLayers:

//use oneEowData tap: 

[
  {
    "type": "GEOJSON",
    "data": {
      "type": "FeatureCollection",
      "features": [
        {
          "type": "Feature",
          "geometry": {
            ...
          },
          "properties": {
            ...
          }
        },
        {
          "type": "Feature",
          "geometry": {
            ...
          },
          "properties": {
            ...
          }
        },
        {
          "type": "Feature",
          "geometry": {
            ...
          },
          "properties": {
            ...
          }
        }
      ]
    },
  }
]

In the final tap you can see each feature Observable:

// use oneEowData feature:
{
  "type": "Feature",
  "geometry": {
    ...
  },
  "properties": {
    ...
    "image":"https://eyeonwater.org/media/eyeonwater_upload/25696.png"
  }
}

// use oneEowData feature:
{
  "type": "Feature",
  "geometry": {
    ...
  },
  "properties": {
    ...
    "image":"https://eyeonwater.org/media/eyeonwater_upload/12542.png"
  }
}

// use oneEowData feature:
{
  "type": "Feature",
  "geometry": {
    ...
  },
  "properties": {
    ...
    "image":"https://eyeonwater.org/media/eyeonwater_upload/12541.png"
  }
}

But then in the React Component:

const LeafletLayers = ({ layers }: Props) => {
  ...
  const oneEowData = useOneEOWData();

  ...

  useEffect(() => {
    console.log(`oneEowData try2: ${JSON.stringify(oneEowData)}`);
  }, [oneEowData]);

  ...
}

It is only the final Observable that is seen:

oneEowData try2: {
  "type": "Feature",
  "geometry": {
    ...
  },
  "properties": {
    "image": "https://eyeonwater.org/media/eyeonwater_upload/12541.png"
  }
}

I also tried subscribing to the Observable:

  useEffect(() => {
    const sub = OneEowData$.subscribe(data => console.log(`OneEowData$ data: ${JSON.stringify(data)}`));
    return sub.unsubscribe();
  }, [])

But it only gave the default results and not the ongoing ones:

OneEowData$ data: {"type":"Feature","geometry":{"type":"Point","coordinates":[0,0]}}

Does anyone know why this is happening and what can I do to fix it?


Solution

  • The issue is bind() in React-rxjs, which says (https://react-rxjs.org/docs/api/core/bind) it returns latest emitted value:

    Returns

    [1, 2]:

    A React Hook that yields the *latest emitted value* of the Observable. If the Observable doesn't synchronously emit a value, it will return the defaultValue if provided, otherwise it will leverage React Suspense while it's waiting for the first value. It's equivalent to useStateObservable of the resulting StateObservable.
    
    The StateObservable that the hook uses: It also replays back the latest value emitted. It can be used for composing other streams that depend on it.
    

    The solution is to subscribe to the RXJS StateObservable in a React component.

    export const Measurements = ({ show }: DraggablePanelsImplementerProps) => {
      ...
      const [measurementsList, setMeasurementsList] = useState<MeasurementsSubset[]>([]);
    
    
      useEffect(() => {
        MeasurementFromEOWData$.subscribe((m) => {
          Logger.debug(`measurement from sub - ${JSON.stringify(m)}`);
          setMeasurementsList(oldMeasurementList => [...oldMeasurementList, m.data ]);
    
        });
      }, []);
    
      return (
        ...
        <ul>
          {measurementsList.map((measurement, index) => (<li key={measurement.key}>{measurement.date_photo}</li>))}
        </ul>
        ...
      )
    }
    

    This work. However, I have asked the React-RXJS experts and will update or modify this answer if I'm told something different.