简体   繁体   中英

RxJS inside React Functional Component

The bulk of this problem is that I'm trying to create react components that each have an observable based on the component's props. I have several locations, each with environmental readings, that I would like to independently generate D3 graphs for using RxJS to manage data coming in.

I have a working version of this concept here:

https://www.lloydrichardsdesign.com/experiment/021

The problem I'm having is with this example, I've hardcoded the firestore observable outside of the component. But in my next version, I would like to make a component that can feed in the locationId to the observable when it loads and then each component manage their own state.

This looks like:

import { Observable } from "rxjs"
import { Dispatch, SetStateAction, useEffect, useState } from "react";

const useObservable = (observable: Observable<any>, setter: Dispatch<SetStateAction<any>>) => {
 useEffect(()=>{
let subscription = observable.subscribe(result => {
    setter(result);
});
return ()=> subscription.unsubscribe()
 },[observable, setter])
}


const LocationItem: React.FC<LocationProps> = ({ location }) => {
   const [readings, setReadings] = useState<Array<Reading>>([]);
   const dataObservable = collectionData(
      db
      .collection('mimirReading')
      .where('locationId', '==', location.id)
      .orderBy('timestamp', 'desc')
      .limit(48)
  );

  useObservable(dataObservable, setReadings);
   return(
    <ol>
     {readings.map(r=><li>{r.timestamp}</li>)}
    </ol>
   )
}

The problem is, this results in the useObservable being called over and over and over again never giving back any data. I end up with an empty readings state and my console going crazy.

I figured, I had to create the dataObservable when the component first mounts, so in useEffect, but then I would get errors related to calling useEffect inside itself. And lastly I tried pulling the subscription out and into the useEffect when first creating the component, but then the observable never collected any information.

Like so:

useEffect(() => {
    const dataObservable = collectionData(
      db
        .collection('mimirReading')
        .where('locationId', '==', location.id)
        .orderBy('timestamp', 'desc')
        .limit(48)
    ).subscribe((reads) => {
      console.log(reads);
      setReadings(reads as Array<Reading>);
    });
    console.log(dataObservable);
    return () => dataObservable.unsubscribe();
  }, []);

I'm at a bit of a loss now and don't know what to do. If anyone has any ideas or solutions it would be very much appreciated!

Keep the useObservable hook isolated, and create a observable value (memoized to the location id) to pass to it:

const useObservable = (observable, setter) => {
  useEffect(() => {
    let subscription = observable.subscribe(result => {
      setter(result);
    });
    return () => subscription.unsubscribe()
    },
    [observable, setter]
  );
};

const LocationItem = ({ location }) => {
  const [readings, setReadings] = useState([]);

  const dataObservable = useMemo(() => {
    return collectionData(
      db
        .collection('mimirReading')
        .where('locationId', '==', location.id)
        .orderBy('timestamp', 'desc')
        .limit(48)
    );
  }, [location.id]);

  useObservable(dataObservable, setReadings);

  return (
    <ol>
      {readings.map((r) => (
        <li>{r.timestamp}</li>
      ))}
    </ol>
  );
};

Optionally, I'd further suggest changing the ownership of the state to useObservable :

const useObservable = (observable) => {
  const [value, setValue] = useState();
  useEffect(() => {
    let subscription = observable.subscribe((result) => {
      setValue(result);
    });
    return () => subscription.unsubscribe();
  }, [observable]);
  return value;
};

This way you don't need the outer state setter, it's always handled within the hook. You could also use setState within useObservable to capture the observable's error and complete events.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM