Accessing FrintJS App Providers inside React.js Components
--
Previously, we covered creating Apps with providers, and then rendering them with React.js in the browser and server. Today, we will see how we can access the providers defined in FrintJS Apps from inside React components.
Higher-order component
We ship a higher-order component (HoC) called observe
from our React integration package frint-react
.
The observe
HoC allows you to wrap your target Component while giving you access to your FrintJS App’s instance, enabling you to prepare the props that you can pass to your target Component later.
You can either generate a synchronous props object, or stream it asynchronously using an Observable with this HoC.
Example App
For the examples below, we will assume this is how our App is defined and rendered with React:
import { createApp } from 'frint';
import { render } from 'frint-react';import MyComponent from './MyComponent';const App = createApp({
name: 'MyApp',
providers: [
{
name: 'foo',
useValue: 'foo value here',
},
{
name: 'component',
useValue: MyComponent,
},
],
});const app = new App();
render(app, document.getElementById('root'));
Synchronous props
If you already know what props you want to pass to your target Component, and don’t need any asynchronous operations involved, and the props are not expected to change later, this solution is suitable for you.
Let’s assume you have a Component like this:
function MyComponent(props) {
const { appName } = props; return <p>App name: {appName}</p>;
}export default MyComponent;
The component expects an appName
prop, which we expect to be our FrintJS App’s name “MyApp”.
But how can we pass this as a prop? How can we even access the App’s instance?
We do it with our observe
HoC:
import React from 'react';
import { observe } from 'frint-react';function MyComponent(props) {
const { appName } = props; return <p>App name: {appName}</p>;
}export default observe(function (app) {
return {
appName: app.getName(),
};
})(MyComponent);
The observe
HoC receives a function, where you can access the App’s instance. Since you have access to App, you can get its name, and then return a props-compatible object, that your target component will receive when rendering.
You could also get a provider’s value, process it further, and then return the object. For e.g., we could also additionally pass a foo
prop:
export default observe(function (app) {
return {
appName: app.getName(),
foo: app.get('foo'), // `foo value here`
};
})(MyComponent);
Now your target component will receive both appName
and foo
as props.
Streaming props
I wrote another article some time ago on how to stream props with RxJS in React components. I highly recommend you to read that too.
You may wonder by now, if the observe
higher-order component is used for generating props synchronously, why is it called “observe” then?
That’s because observe
HoC previously only supported returning an Observable, and returning plain objects was supported much later.
Imagine, you have a component like this:
import React from 'react';function MyComponent(props) {
const { interval } = props; return <p>Interval: {interval}</p>;
}export default MyComponent;
The component receives an interval
prop, that we expect to increment by 1 every second.
We could visualize it in a marble diagram here:
In the first second, the interval
prop is 1, then 2 and so on…
The interval logic can be expressed with RxJS in a variable like this:
import { interval } from 'rxjs/observable/interval';const interval$ = interval(1000); // emits every 1 second
Now gluing it all up using our observe
HoC is as easy as:
import React from 'react';
import { observe } from 'frint-react';
import { interval } from 'rxjs/observable/interval';
import { map } from 'rxjs/operators/map';function MyComponent(props) {
const { interval } = props; return <p>Interval: {interval}</p>;
}export default observe(function (app) {
const interval$ = interval(1000); return interval$.pipe(
map(x => ({ interval: x }))
);
})(MyComponent);
What we just did above is prepare an interval$
Observable, and then map it to a props-compatible object, and return the final Observable.
The example above uses lettable operators using
pipe
method, which is introduced since RxJS v5.5.
The HoC will now take care of updating the props and keep passing it down to your target component.
While this example didn’t use any provider, but you can imagine the interval$
coming from a provider too instead:
export default observe(function (app) {
const interval$ = app.get('interval'); return interval$.pipe(
map(x => ({ interval: x }))
);
})(MyComponent);
Helper function
We also have a helper function called streamProps
that we ship from our frint-react
package. It is useful if you are combining a stream of props from various sources (involving both sync and async operations).
The above example could have been written like this:
import { streamProps } from 'frint-react';
import { interval } from 'rxjs/observable/interval';const interval$ = interval(1000);const props$ = streamProps()
// sync
.set('foo', 'foo value') // observable, with further mappings
.set(
interval$,
x => ({ interval: x })
) // create a combined Observable of all props together
.get$();
Now when you return this props$
observable from your observe
HoC, your target component will receive both foo
and interval
prop together.
While foo
's value will always remain the same, interval
will keep updating every second.
Benefits of this approach
Using the observe
higher-order component encourages you to keep your React.js components as stateless as possible. This way, when you write your main components, you only need to worry about what props you receive, and nothing else.
And as long as your FrintJS App has something defined as a provider, you are allowed to access it from your components layer anywhere.
This approach also helps in moving a larger share of stateful logic to provider-level in FrintJS Apps, leaving your components responsible for rendering and passing data as props only.