Using FrintJS with React.js and Redux

Fahad Heylaal
FrintJS
Published in
7 min readDec 1, 2017

Redux is an extremely popular state management library in the React.js ecosystem. It also comes with integrations like react-redux which gives you a higher-order component to connect your Redux store to your React components.

How can this killer duo be utilized in FrintJS Apps too?

Example Store

Before getting into FrintJS, let’s first have a basic store with Redux. Our application will have a counter, that we can increment and decrement.

Constants:

Define some constants for our action types:

// constants/index.jsexport const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

Action creators:

// actions/counter.jsimport { 
INCREMENT_COUNTER,
DECREMENT_COUNTER
} from '../constants';
export function incrementCounter() {
return { type: INCREMENT_COUNTER };
}
export function decrementCounter() {
return { type: DECREMENT_COUNTER };
}

Reducer:

Function that will receive existing state, and return a new updated state based on incoming action:

// reducers/counter.jsimport { 
INCREMENT_COUNTER,
DECREMENT_COUNTER
} from '../constants';
const INITIAL_STATE = { value: 0 };function counterReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case 'INCREMENT_COUNTER':
return Object.assign({}, {
value: state.value + 1
});
case 'DECREMENT_COUNTER':
return Object.assign({}, {
value: state.value - 1
});
default:
return state;
}
}
export default counterReducer;

Store:

And finally, create a Redux store based on our counter reducer:

// index.jsimport { createStore } from 'redux';
import reducer from './reducers/counter';
const store = createStore(reducer);

Actions can later be dispatched to the store like this:

import { incrementCounter } from './actions/counter';store.dispatch(incrementCounter());

Redux store as a FrintJS provider

In FrintJS Apps, we have a concept of “providers”, which are basically dependencies of your App.

The Redux store that we just created, can be a provider of our App here. We can define it when creating our App:

import { createApp } from 'frint';
import { createStore } from 'redux';
import reducer from './reducers/counter';const store = createStore(reducer);const App = createApp({
name: 'MyApp',
providers: [
{
name: 'store',
useValue: store,

},
],
});

Now when we instantiate our FrintJS App, we can access the store as follows:

const app = new App();
const store = app.get('store');

We can actually create our Redux store only when our App itself is instantiated. We can easily do this by switching from useValue to useFactory, which generates the value for your provider when the App initializes itself:

const App = createApp({
name: 'MyApp',
providers: [
{
name: 'store',
useFactory: function () {
return createStore(reducer);
}
,
},
],
});

You can read more about various ways of defining providers in this post.

Connecting it to React.js

FrintJS supports React integration via frint-react package. You can read more about rendering with React in this post.

Let’s set up our first React component, and render it with FrintJS:

import React from 'react';
import { render } from 'frint-react';
function MyComponent() {
return <p>Hello World</p>;
}
const App = createApp({
name: 'MyApp',
providers: [
{
name: 'store',
useFactory: function () {
return createStore(reducer);
},
},
{
name: 'component',
useValue: MyComponent,
},

],
});
const app = new App();
render(app, document.getElementById('root'));

All we have so far is just a “Hello World” in the UI. We want to ultimately show our counter value from Redux store, and also an increment and decrement button that will dispatch the actions to the Redux store appropriately. And after those dispatches, our counter value should automatically update in the UI.

Component with behaviour:

Let’s extend our MyComponent to reflect the expected behaviour:

// components/MyComponent.jsimport React from 'react';function MyComponent(props) {
const {
counter,
increment,
decrement
} = props;
return (
<div>
Value: {counter}
<button onClick={() => increment()}>+</button>
<button onClick={() => decrement()}>-</button>
</div>
);
}
export default MyComponent;

We now expect our stateless MyComponent to receive three props:

  • counter: which should be the counter’s integer value
  • increment and decrement: function when called should dispatch actions to the Redux store

But where would those props come from?

Higher-order component:

React integration package frint-react ships with an observe higher-order component (HoC), which allows you to access your App and its providers in your components, and also stream props using RxJS. Read more about it in this post.

Now we will use our observe higher-order component to stream the state of counter (number) as a prop:

import { observe } from 'frint-react';
import { from } from 'rxjs/observable/from';
import { map } from 'rxjs/operators/map';
function MyComponent({ counter }) {
// ...
}
export default observe(function (app) {
const store = app.get('store');
const state$ = from(store);
return state$.pipe(
map(state => ({ counter: state.value })
);
})(MyComponent);

What we just did above can be summarized as:

  • We got access to our app using the observe HoC
  • From our app, we got the store provider
  • We created a state$ stream from our Redux store
  • Finally mapped the state$ into a props-compatible object

Now whenever the Redux store changes, it will send new updated props to our MyComponent.

You can read further about streaming state from Redux store as an RxJS Observable in this post.

Combining various sources into a single props stream:

But we are still not passing the increment and decrement props, which are supposed to dispatch actions to the Redux store. How can we pass them too?

You can either do it directly with RxJS, or you can also use our streamProps helper function that ships with our frint-react package, and it is also Redux compatible.

We can change our code from above a bit like this:

import { observe, streamProps } from 'frint-react';
import { from } from 'rxjs/observable/from';
import {
incrementCounter,
decrementCounter
} from '../actions/counter';
function MyComponent({ counter, increment, decrement }) {
// ...
}
export default observe(function (app) {
const store = app.get('store');
const state$ = from(store);
return streamProps()
// set state
.set(
state$,
state => ({ counter: state.value })
)
// set dispatch-able actions targeting Redux store
.setDispatch({
increment: incrementCounter,
decrement: decrementCounter,
}, store)
// prepare a final combined Observable of props
.get$();
})(MyComponent);

And that’s it, you have a fully working UI now!

I miss `connect()` HoC from react-redux!

While frint-react gives you the minimum building blocks for building reactive applications, you can still build your own version of connect HoC like the one that exists in react-redux:

import { observe, streamProps } from 'frint-react';
import { from } from 'rxjs/observable/from';
function connect(mapStateToProps, mapDispatchToProps) {
return function (Component) {
return observe(function (app) {
const store = app.get('store');
const state$ = from(store);
return streamProps()
.set(state$, mapStateToProps)
.setDispatch(mapDispatchToProps, store)
.get$();
})(Component);
};
}

Now you can use your custom connect HoC like this for the counter example:

import connect from './connect';import { 
incrementCounter,
decrementCounter
} from '../actions/counter';
function MyComponent(props) {
// ...
}
function mapStateToProps(state) {
return {
counter: state.value,
};
}
export default connect(mapStateToProps, {
increment: incrementCounter,
decrement: decrementCounter,
})(MyComponent);

I still want to use react-redux though

The way react-redux works, it sets the Redux store in React’s context. Which later allows higher-order components like connect to access the store anywhere in the components tree, and process it according to your requirements.

With frint-react, we set the FrintJS App’s instance in React’s context. Which makes it possible for our observe higher order component to access the app and make it available to you anywhere in the component tree.

The basic idea of using FrintJS with React is, you only set the app in React’s context, and anything else you need are defined as providers in the App itself. This way, we can predictably define all of our dependencies in one place only.

But there are use cases where you may decide to use React’s context API yourself, or use any third-party library (like react-redux) that uses it.

Our suggestion would be to still define your dependencies as App providers as much as possible, and then go for the manipulating React’s context.

Here’s an example where you set the Redux store as a provider, and also set it in React’s context with their <Provider> component:

import { createApp } from 'frint';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const App = createApp({
name: 'MyApp',
providers: [
{
name: 'store',
useFactory() {
return createStore(myReducerHere);
},
},
{
name: 'component',
useFactory({ store }) {
return (
<Provider store={store}>
<MyComponent />
</Provider>
);
},
deps: ['store'],
},
],
});

We defined our store just like a regular App provider, and then when defining component, we let FrintJS know that we also need the store's value, which is injected in its useFactory function, and later used by the wrapper component <Provider> coming from react-redux.

Closing thoughts

Redux has helped React.js community to think about building their applications using a single store. One App = One Store. It is as simple as that. Which works great in most cases.

FrintJS encourages you to build your application with “providers”, and Redux store can be considered a provider here. It is one of the dependencies of your App that happens to be stateful.

As your application grows, you may have more providers named “router” (for single-page applications), “api” (for communicating to a web service), and so on. Each doing their own thing, backed by an intuitive dependency injection API.

--

--

Fahad Heylaal
FrintJS

Maker of things · JavaScript · RxJS · ReactJS