Skip to content

Instantly share code, notes, and snippets.

@busypeoples
Last active October 28, 2020 04:34
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save busypeoples/755e5b34c3826be0db0a7c099454551a to your computer and use it in GitHub Desktop.
Save busypeoples/755e5b34c3826be0db0a7c099454551a to your computer and use it in GitHub Desktop.
Loader

Loader

Introduction

Minimal loader with state management capabilities.

Checkout out the demo.

Setup

Make sure to add a .babelrc file:

{
  "presets": ["es2015", "stage-0", "react"],
  "plugins": ["transform-flow-strip-types", "transform-object-rest-spread"]
}

Example

Uses multiple render props. Loader renders the correct view according to the calculated state.

const fakeFetch = () =>
    new Promise((res, rej) => {
		setTimeout(
	 		() =>
				res([
					{ id: 1, name: 'foo' },
					{ id: 2, name: 'bar' },
					{ id: 3, name: 'baz' }
				]),
			1000
		);
	});

const App = () => (
	<Loader
		fetch={fakeFetch}
		renderError={error => (
			<div class="error">Something went wrong: {error.message}</div>
		)}
		renderSuccess={data => <UserItems data={data} />}
		renderNotAsked={() => <div className="start">Not Data Loaded</div>}
		renderLoading={() => <div className="loader">Loading...</div>}
	>
		{(View, loadData) => (
			<div>
				{View}
				<button onClick={loadData}>Load Data</button>
			</div>
		)}
	</Loader>
);
import React from 'react';
import daggy from 'daggy';
// Inspired by:
// "Slaying a UI Antipattern in Fantasyland" @thinkfunctional
// https://medium.com/javascript-inside/slaying-a-ui-antipattern-in-fantasyland-907cbc322d2a
// "Use a Render Prop!"
// https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce @mjackson
// Loader in ReasonML @ryyppy
// https://github.com/ryyppy/reason-lnd/blob/master/frontend/src/loader.re
const RemoteData = daggy.taggedSum('RemoteData', {
NotAsked: [],
Loading: [],
Failure: ['error'],
Success: ['data']
});
class Loader extends React.Component {
state = { items: RemoteData.NotAsked };
loadData = () => {
const { url, fetch } = this.props;
this.setState(state => ({ items: RemoteData.Loading }));
const getItems = fetch();
getItems.then(
data => {
this.setState(state => ({
items: RemoteData.Success(data)
}));
},
error => {
this.setState(state => ({
items: RemoteData.Failure(error)
}));
}
);
};
getView = items => {
const {
renderNotAsked,
renderLoading,
renderError,
renderSuccess
} = this.props;
return items.cata({
NotAsked: () => renderNotAsked(),
Loading: () => renderLoading(),
Failure: error => renderError(error),
Success: data => renderSuccess(data)
});
};
render() {
const { items } = this.state;
const { children } = this.props;
const View = this.getView(items);
return children(View, this.loadData);
}
}
export default Loader;
{
"name": "loader-demo",
"description": "Minimal loader demo with state handling capabilities",
"scripts": {
"start": "babel src -d lib -w",
"build:lib": "babel src -d lib",
"test": "npm run lint && mocha --compilers js:babel-core/register --recursive --colors"
},
"dependencies": {
"daggy": "^1.2.0",
"react": "^16.0.0"
},
"devDependencies": {
"babel": "^6.23.0",
"babel-cli": "^6.24.1",
"babel-core": "^6.25.0",
"babel-loader": "^7.0.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1"
}
}
@busypeoples
Copy link
Author

busypeoples commented Oct 25, 2017

@Gregoirevda
Copy link

I finally had to use Reason functors! If you want to pass types to your enhancer, you'll have to use them. Makes it more flexible.

@busypeoples
Copy link
Author

Nice. Can you post an example?

@Gregoirevda
Copy link

Gregoirevda commented Nov 4, 2017

So this is specific to Reason:
To make the Loader configurable, or any Library, I made a repo using Function as a Child Component in Reason.

If you want to pass types in your configuration, use functors.

Library.re

module type CreationConfig = {
  let foo: string;
};

module type InstanceConfig = {
  type responseType;
  let bar: string;
};

module Create = fun (CreationConfig:CreationConfig) => fun (InstanceConfig:InstanceConfig) => {
 /* You could now for example fetch remote data and cast it to the responseType the user wants */
  external cast: string => InstanceConfig.responseType = "%identity";
  let component = ReasonReact.statelessComponent "MyLibrary";
  let make _childen => {
    ...component,
    render: _self => <div/>
  }
}

In your project:

Client.re

let Instance = Library.Create { let foo = "foo" };

AnyFile.re

let make = Client.Instance { type responseType = { bar: string }; let bar = string };

So for Reason, using functors is great because you will be able to configure it with module level definitions: anything!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment