How to use Rxjs for processing arrays in React

Viewed 95

Following up on the idea of "Rxjs can be used for everything" (and while not saying it should) I'd like to process arrays with Rxjs instead of array functions in React.

I tried different approaches (with different subscribe(), rxjs-hook, ...) but it didn't leave anywhere. Therefore I skip my wrong-doing and provide a simplified example.

Here's what I can do with array functions (map for simplicity):

const externalData = [1,2,3,4,5];
const App = () => {
const transformedData = externalData.map(i => `${i} was processed`)

return <>{transfomedData.join(", ")</>
}

How would I implement the same with rxjs?

// wrong approach
const externalData = [1,2,3,4,5];
const App = () => {
const transformedData = from(externalData).pipe(map(i => `${i} was processed`))

return <>{transfomedData.join(", ")</> // transformedData is Observable, not array
}

2 Answers

What you can do is something like this

let transformedData
from(externalData).pipe(map(i => `${i} was processed`), toArray()).subscribe(
  result => transformedData = result
)

return <>{transfomedData.join(", ")</>

Note that this can work because the subscription to the Observable here works synchronously since there is no asynchronous operation involved. As soon as you start having async operations, you need to define a state variable, e.g. via useState hook, and set the state variable within the subscribe as a side effect of the operation.

Observables don't do anything until they have a subscriber. You could create a state with useState and store the resulting array from your stream there.

const externalData = [1,2,3,4,5];
const App = () => {
  const [transfomedData, setTransfomedData] = useState([]);
  from(externalData)
    .pipe(map(i => `${i} was processed`), toArray())
    .subscribe(setTransfomedData);

  return <>{transfomedData.join(", ")</>
}

But there is an more elegant solution that I discovered recently that takes the pain away of managing subscriptions manually: rixio.

With that lib you only need to utilise a wrapper component (or create a custom one) where you define your observable and another component that is in charge of rendering:

const Wrapper () => {
  const transformedData$ = useMemo(() =>
    from(externalData)
      .pipe(map(i => `${i} was processed`), toArray())
  );
  // RxWrapper was imported from rixio
  return <RxWrapper component={App} transformedData={transformedData$} />;
}

const App (p) =>
  <>{p.transformedData.join(", ")}</>;

Or using a build in wrapper but more complicated since you need to prepare the string first with both toArray and map:

const Wrapper () => {
  const transformedData$ = useMemo(() =>
    from(externalData).pipe(
      map(i => `${i} was processed`),
      toArray(),
      map(arr => arr.join(",")),
    )
  );
  // R was imported from rixio
  return <R.div>{transformedData$}</R.div>;
}

However with the example you provided you won't benefit so much from using rxjs. It is a very simple case where build in array methods are more efficient. Rxjs suits better when you want to write maintainable code solving complex concurrency cases.

Related