• Stars
    star
    735
  • Rank 61,652 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 9 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Feed data into React components by composing containers.

React Komposer

Feed data into React components by composing containers.
(Works with any kind of data store whether it's Redux, Promises, RxJX, MobX or anything else)

TOC

Why?

In React, usually we build UI components and feed data into them via containers. Let's call them data containers. Inside that data containers, we may need to:

  • access different data sources
  • show loading screens
  • handle errors
  • subscribe to data and clean-up subscripts as needed
  • re-fetch data when props changed

Among a lot of other things. React Komposer helps you create such data containers and you only need to worry about writing the data fetching(or integration) logic.

Installation

npm install --save react-komposer@2

Simple Example

Let's assume we've got a UI component called Blog Post like this:

const BlogPost = ({ post }) => (
    <div>
        <h2>{post.title}</h2>
        <p>{post.content}</p>
    </div>
);

Now we need to fetch data from the server. So, we'll create a dataLoader like this:

function postDataLoader(props, onData) {
    // load data from the server. (using props.id to identify the post)
    // (Here'll we'll use setTimeout for demonstration purpose)
    setTimeout(function() {
        const post = {
            id: props.id,
            title: 'Hello React Komposer',
            content: 'This will help you to load data into your components.',
        };
        const data = { post };

        // send the data as props to the BlogPost component.
        // So, BlogPost will see the post object as a prop.
        onData(null, data)
    }, 1000);
}

Then let's create the container:

import { compose } from 'react-komposer';
const BlogPostContainer = compose(postDataLoader)(BlogPost);

Now we could render the BlogPostContainer like this:

import ReactDOM from 'react-dom';
ReactDOM.render(<BlogPostContainer id='post-one' />, document.body);

Play with this example.

Other Core Functionalities

Now we know how to load data to a component using React Komposer. Let's have a look at our other core functionalities.

Subscribing to data

Usually, we need to subscribe to a data source and update the UI as we get new changes. This is a part of the realtime UI's. With React Komposer, you could easily connect those subscriptions with UI components.

For the above BlogPost component, we can write a data loader like this:

function postDataLoader(props, onData) {
    // Create a subscription to the data server.
    // Use props.id to identify the post.
    // (Here'll we'll use setInterval for demonstration purpose)

    const handler = setInterval(function() {
        const post = {
            id: props.id,
            title: 'Hello React Komposer',
            content: `
              This will help you to load data into your components.
              - Updated at: ${new Date().toLocaleString()}
            `
        };
        const data = { post };

        // send the data as BlogPost component.
        // So, BlogPost will see the post object as a prop.
        onData(null, data)
    }, 1000);

    // return a function which cleanup the handler
    return () => { clearInterval(handler) }
}

Here we are calling the onData callback for every one second. We've also returned a function from where it'll be used to clear the resources allocated by the subscription when the container unmounted.

Play with this example.

Show Loading screen

It'll take a bit of time to load data from the server. So, we usually show a loading screen. React storybook will take care of that automatically.

For any data loader, we could get this by providing a loadingHandler like this:

const options = {
  loadingHandler: () => (<p>Loading...</p>)
};
const BlogPostContainer = compose(postDataLoader, options)(BlogPost);

Play with this example.

We show the loading screen until you provide a data object to the onData function. Have a look at the following data loader:

function postDataLoader(props, onData) {

}

Since we've not invoked the onData callback, there'll be the loading screen forever.

Handling errors

Usually when we are dealing with remote data, we need to handle errors as well. React Komposer has its own way of handling errors. Check this example:

function postDataLoader(props, onData) {
   setTimeout(function() {
     // Assume we got an error object
     const error = new Error('Oops. Something is not right.');
     // pass the error
     onData(error);
   }, 1000);
}

By default, we'll throw the error to the console. But you can provide a UI for error like this when creating the container:

const options = {
  errorHandler: (err) => (
    <p style={{color: 'red'}}>
       {err.message}
    </p>
  )
};
const BlogPostContainer = compose(postDataLoader, options)(BlogPost);

Play with this example.

Performance

Performance is really important when building a real world apps. React Komposer comes with few ways to tune the performance. Let's discuss.

Props Watching

By default, we watch and re-run data loader for every prop change.

In the data loader, you can access props passed to your container like this:

function postDataLoader(props, onData) {
   console.log(props);
}

We re-run the dataLoader for every prop change in the container. If we are using a prop inside the data loader, it's required to re-run the dataLoader when the prop changes.

But reruns for any other prop change is not necessary.

So, we can ask React Komposer to only re-run when given props have changed. Have a look at the following code:

const options = {
    propsToWatch: ['id']
};
const BlogPostContainer = compose(postDataLoader, options)(BlogPost);

Here we only re-run the data loader only when the prop named id gets changed.

Should Subscribe

By default, this is null.

This gives the same functionality as props watching, but with more control. With the propsToWatch option, we do a shallow comparison. If that doesn't work for you, you can use the shouldSubscribe option as shown below:

const options = {
    shouldSubscribe(currentProps, nextProps) {
        // return true if you need to re-run the data loader again.
    }
};

Pure

By default, this is false.

This will take care of the component re-rendering for every prop change. You can make the container pure by applying the following option:

const options = {
    pure: true,
};
const BlogPostContainer = compose(postDataLoader, options)(BlogPost);

Then, this will add a pure render mixin to the React component. (This will compare props in shallow manner).

Should Update

By default, this is null

This will provide the same functionality as pure, but with more control. With pure, we compare props shallowly. But, you can use shouldUpdate option to compare it as you want.

Check the following example:

const options = {
    shouldUpdate(currentProps, nextProps) {
        // return true if you need to render the compare with nextProps.
    },
};
const BlogPostContainer = compose(postDataLoader, options)(BlogPost);

Set Defaults

Usually, you may want to use the same set of options for every container you create. So, you could set defaults like this:

import { setDefaults } from 'react-komposer';

const myCompose = setDefaults({
    pure: true,
    propsToWatch: [],
    loadingHandler: () => (<p>Loading...</p>),
});

Then you can use myCompose instead compose when creating containers.

It's pretty useful to setDefaults like this create a customized composer for your app.

You can override any of these options by providing options when creating the container.

Passing an Environment (Like Dependency Injection)

This is a pretty neat feature where you can use to inject dependencies.

Usually, your UI component doesn't know about app specific information. But containers do know that. Using env option of React Komposer you could pass a env where your data loaders could utilize.

This is useful to use with a custom composer created with setDefaults

Have a look at the following example:

import { setDefaults } from 'react-komposer';

// This is the reduxStore of your app.
const reduxStore = {};

const myCompose = setDefaults({
    //...otherOptions
    env: {
        reduxStore
    }
});

Then you can access the environment from any dataLoader when the container is created myCompose.

function postDataLoader(props, onData, env) {
   // access the redux container and subscribe to that
   return env.reduxStore.subscribe((state) => {
       onData(null, state);
   });
}

Server Side Rendering (SSR)

Usually data loaders run in the constructor of the React component. So, it'll run with SSR as well. But it won't stop subscriptions for you.

So, you need to identify the SSR environment from your dataLoader, and do not subscribe for data inside that. Have a look at the following code base:

function postDataLoader(props, onData, env) {
   if (isSSR) {
      const data = fetchData();
      onData(null, data);
      return;
   }

   const stopSubscription = watchData((data) => {
      onData(null, data);
   });
   return stopSubscription;
}

Accessing the UI Component (via refs)

Sometimes ( although not recommended) you may want to access the underlining UI component instead of the container. Then you can call it the child property of container instance.

Merging Multiple Containers

Sometimes, you may want to use multiple data loaders for a single UI component. Then you will have to do something like this:

const Container1 = compose(dataLoader1)(UIComponent);
const Container2 = compose(dataLoader2)(Container1);
const Container3 = compose(dataLoader3)(Container2);

export default Container3;

With our merge utility, you could do it like this:

import { merge } from 'react-komposer';

export default merge(
    compose(dataLoader1),
    compose(dataLoader2),
    compose(dataLoader3),
)(UIComponent);

Stubbing

Sometimes, you may wanna use containers created with React Komposer in environments where it couldn't work.
(For an example, React Storybook. It might not have your dataStores)

For those environments, you could stub your containers.

For that, simply put following lines before import any of your components. (In React Storybook, it should be the config.js file)

import { setStubbingMode } from 'react-stubber';
setStubbingMode(true);

Then you could stub your containers as you want. Follow the react-stubber documentation for more information.

Internally, React Komposer uses react-stubber to add stubbing support.

Migrating from 1.x

React Komposer 2.x is almost compatible with 1.x but with few minor changes. Here are they:

No default error and loading components

1.x comes with default components for error and loading components. But, now you need to specify them manually with errorHandler and loadingHandler options.

We do this because, we want to use React Komposer on both React and React Native environment. Shipping defaults components make it harder to.

ComposeAll is now merge

1.x has a function called composeAll which does exactly the same functionality as merge. Now we have renamed it as merge. But still, composeAll is available.

By Default pure=false

Earlier, all of the containers we created were pure. But now you need to pass the pure option to make it pure.

No utility composers for promises, redux and etc.

Earlier, we shipped a set of composers for different kinds of data stores. But now they are not shipped with this project. We recommend you create a data loader generator instead. Then use it.

You may also publish it into NPM and share it with others.

Have a look at some example data loader generators:

For Promises

function genPromiseLoader(promise) {
    return (props, onData) => {
      promise
        .then((data) => onData(null, data))
        .catch((err) => onData(err))
    };
}

// usage
const Container = compose(genPromiseLoader(somePromiseObject))(UIComponent);

For Redux

function getReduxLoader(mapper) {
    return (props, onData, env) => {
        // Accessing the reduxStore via the env.
        return env.reduxStore.subscribe((state) => {
            onData(null, mapper(state, env));
        });
    };
}

// usage (expect you to pass the reduxStore via the env)
const myMapper = ({user}) => ({user});
const Container = compose(getReduxLoader(myMapper))(UIComponent)

For Meteor's Tracker

function getTrackerLoader(reactiveMapper) {
  return (props, onData, env) => {
    let trackerCleanup = null;
    const handler = Tracker.nonreactive(() => {
      return Tracker.autorun(() => {
      	// assign the custom clean-up function.
        trackerCleanup = reactiveMapper(props, onData, env);
      });
    });

    return () => {
      if(typeof trackerCleanup === 'function') trackerCleanup();
      return handler.stop();
    };
  };
}

// usage
function reactiveMapper(props, onData) {
  if (Meteor.subscribe('post', props.id).ready()) {
    const post = Posts.findOne({ id: props.id });
    onData(null, { post });
  };
}

const Container = compose(getTrackerLoader(reactiveMapper))(UIComponent);

More Repositories

1

meteor-up-legacy

Production Quality Meteor Deployments
JavaScript
2,268
star
2

node-usage

process usage lookup with nodejs
JavaScript
390
star
3

meteor-streams

Realtime messaging for Meteor
JavaScript
286
star
4

laika

testing framework for meteor
JavaScript
242
star
5

meteor-ddp-analyzer

Simple DDP Proxy which logs DDP messages
JavaScript
151
star
6

meteor-smart-collections

Meteor Collections Re-Imagined
JavaScript
148
star
7

use-magic-link

Simple auth setup for your React app in few minutes with Magic Link.
JavaScript
110
star
8

meteor-cluster

Smarter way to run cluster of meteor nodes
JavaScript
100
star
9

learnnextjs-demo

Demo App of the http://learnnextjs.com
JavaScript
94
star
10

nodemiral

Server Automation for NodeJS over SSH
JavaScript
93
star
11

nariya

Continuous Deployment Server
JavaScript
67
star
12

hello-react-meteor

Learning Some React with Proper Meteor Integration (Routing, SSR)
JavaScript
65
star
13

meteor-static-blog

Static Blog Made with Meteor
JavaScript
60
star
14

chrome-node

NodeJS Runtime for Chrome (For Packaged Apps)
55
star
15

mongo-metrics

Metrics tracking and aggregation with MongoDB
JavaScript
54
star
16

node-redis-scripto

Redis Script Manager for NodeJS
JavaScript
52
star
17

travis-ci-meteor-packages

Travis CI support for Meteor (Smart) Packages
JavaScript
52
star
18

open-comment-box

OpenSource Realtime Comments Platforms
JavaScript
49
star
19

horaa

Mocking NodeJS Modules
JavaScript
45
star
20

fastai-shell

A workflow to setup and use fastai on Google Cloud Platfrom
Shell
42
star
21

streams-blackboard

Realtime Blackboard with Meteor Streams
JavaScript
38
star
22

podda

Simple Reactive DataStore for JavaScript
JavaScript
33
star
23

meteor-find-faster

Faster & Efficient Implementation of Meteor's Collection.find()
JavaScript
33
star
24

meteor-streams-chat-app

Chat App with Meteor Streams
JavaScript
31
star
25

coursebook-server

JavaScript
27
star
26

coursebook-ui

Main UI for coursebook
JavaScript
26
star
27

keep-calm-and-node-on

KEEP CALM and NODE ON
23
star
28

drev

DREV - Distributed Redis based EventEmitter for NodeJS
JavaScript
21
star
29

meteor-custom-authentication-system

Custom Authentication System for Meteor
JavaScript
21
star
30

dcoinwallet

"A Bitcoin Wallet" for power users.
JavaScript
20
star
31

meteor-seo-without-spiderable

Meteor SEO support without spiderable
JavaScript
20
star
32

nextjs-e2e-demo

Next.js E2E Demo
JavaScript
19
star
33

nextjs-magic-bank

JavaScript
18
star
34

mocha-mongo

Set of mongodb testing helpers for mocha
JavaScript
18
star
35

reaktor-demo

Demo of Reaktor - New Router Syntax for FlowRouter + ReactLayout
JavaScript
18
star
36

meteor-apm-client

Application Performance Monitoring for Meteor
JavaScript
17
star
37

meteor-ssr-demo-page-rendering

Rendering Pages on the Server via meteor-ssr
JavaScript
17
star
38

bulletproof-next-app

JavaScript
15
star
39

nextjs-issg-example

A simple example featuring Next.js iSSG
JavaScript
15
star
40

jailguard

Safe Way to Run User Provided JavaScript with NodeJS
JavaScript
15
star
41

arunoda.me

My personal website + blog
JavaScript
13
star
42

hello-laika

Sample app with tests written in laika
JavaScript
11
star
43

heroku-nodejs-binary-buildback

Official NodeJS Binaries on Heroku
Shell
11
star
44

stress-test-meteor

Stress Test Meteor
JavaScript
11
star
45

pick-mongo-primary

Get MongoDB Shell command to PRIMARY from the Mongo URL
JavaScript
10
star
46

adeep

Jupyter Notebook
9
star
47

cmake-boilerplate

CMake Boilerplate for C
C
9
star
48

qbox

Quick Flow controller for NodeJS
JavaScript
9
star
49

rn-storybook

React Native storybook demo
Objective-C
9
star
50

travis-ci-laika

Travis CI support for laika
Shell
8
star
51

wait-for-mongo

Simple utility which waits until mongodb to come online
JavaScript
8
star
52

try-apollo

My first proper attempt with Apollo
JavaScript
8
star
53

learn-redux

JavaScript
7
star
54

next-apollo-demo

Simple Next.js example with Apollo
JavaScript
7
star
55

jquery-signature

Getting a unique signature for a dom event using jQuery
JavaScript
7
star
56

node-userdown

Run node apps with stepping down user permissions
JavaScript
7
star
57

awesome-blackboard

JavaScript
7
star
58

coursebook-publish

Publish lessons into coursebook
JavaScript
7
star
59

appzone-ussd-sim

Rich USSD Simulator for Appzone
JavaScript
7
star
60

Appzone-NodeJS

NodeJS Client for Appzone
JavaScript
6
star
61

marlin

Marlin firmware for My Tevo Tornado
C
6
star
62

nextjs-example-preload-data

A Next.js example with data preloading
JavaScript
6
star
63

winstoon

A Simple Wrapper for Winston
JavaScript
6
star
64

meteor-subscription-manager

Subscription Manager for Meteor
JavaScript
6
star
65

cas-pipes

MongoDB Aggregation Pipelines meets Cassandra
JavaScript
5
star
66

kgatsby

Gatsby starter for a Contentful project.
JavaScript
5
star
67

AppZone-Community-Simulator

Simulator for AppZone.lk powered by the Community
JavaScript
5
star
68

meteor-heapsave

Take Heapdumps and save them into S3
JavaScript
5
star
69

mnode

How to find the node version used by Meteor
JavaScript
5
star
70

meteor-runner

Best way to run meteor apps in production
JavaScript
4
star
71

meteor-version-playground

Play with Meteor 0.9 Package Versions
JavaScript
4
star
72

tusker

Redis Based Distributed Task Locking
JavaScript
4
star
73

lesson-nextjs-static-regeneration

JavaScript
3
star
74

telescope-client

Telescope Client Hosted on Heroku
3
star
75

datocms-hugo-portfolio-demo-4731

CSS
3
star
76

very-bad-app

Very bad app which scores 100% on lightshouse
JavaScript
3
star
77

github-cms-demo-data

Data for the Next.js blog using GitHub CMS
3
star
78

github-issue-search

Github Issue Search Meteor App
JavaScript
3
star
79

cryptocurrency

Playing with https://www.coursera.org/learn/cryptocurrency
JavaScript
3
star
80

hello-laika-discover-meteor

Laika Hello World example for the Discover Meteor Screencast
JavaScript
3
star
81

react-kode-camp-todo

A todo app used for React Kode Camp
JavaScript
2
star
82

Appzone-CMD

Command line utility for Appzone.lk platform
2
star
83

meteor-user-authorization-test-with-laika

Meteor user authorization testing with meteor
JavaScript
2
star
84

youfool.art

The Simplest NFT Platform Ever
JavaScript
2
star
85

nariya-helloworld

Nariya Helloworld App
JavaScript
2
star
86

multiuser-sms-simulator

Multiuser SMS Simulator
JavaScript
2
star
87

datocms-snipcart-gatsby-example-demo-6375

CSS
2
star
88

AppZoneSimulator

Rich Simulator for AppZone.lk
JavaScript
2
star
89

fast-render-telescope

Telescope with Fast Render (using for fast-render QA)
JavaScript
2
star
90

hibar

Intelligent JQuery
JavaScript
2
star
91

mongotest

Helper functions for testing with mongodb
JavaScript
2
star
92

meteor-rethink-hello

meteor-rethink-hello
JavaScript
2
star
93

gc-game

JavaScript
2
star
94

fashona-learnbox

Fashona LearnBox
JavaScript
2
star
95

appzone-live-test

Live Test Appzone Applications
JavaScript
2
star
96

wantrepreneur-realised

I was a Wantrepreneur, but I realised!
2
star
97

AppZone-Java-Sample

Java Maven Sample for AppZone.lk
Java
2
star
98

nodespy

Spy and Expectation Framework for NodeJS with Stubbing
JavaScript
2
star
99

AppZone-Java-Client

OpenSource Java Client for AppZone.lk
Java
2
star
100

fastai-courses

Holds notebooks related to fastai courses I'm working on
Jupyter Notebook
2
star