diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
 		"cosmiconfig": "^3.0.1",
 		"dot-prop": "^4.2.0",
 		"escape-html": "^1.0.3",
+		"events": "^2.0.0",
 		"expand-hash": "^0.2.2",
 		"express": "^4.14.0",
 		"gm": "^1.23.0",
diff --git a/react-components/index.js b/react-components/index.js
--- a/react-components/index.js
+++ b/react-components/index.js
@@ -1,3 +1,6 @@
 module.exports = {
 	ResourceSelect: require("./lib/ResourceSelect.jsx"),
+	Collection: require("./lib/collection.jsx"),
+	Resource: require("./lib/resource.jsx"),
+	QueryStores: require("./lib/query-stores/query-store.js"),
 };
diff --git a/react-components/lib/collection.jsx b/react-components/lib/collection.jsx
new file mode 100644
--- /dev/null
+++ b/react-components/lib/collection.jsx
@@ -0,0 +1,36 @@
+const React = require("react");
+const CachedHttp = require("./cached-http.js");
+const KeyValueStore = require("./stores/key-value-store.js");
+const ConnectWithKeyValueStore = require("./stores/connect-with-key-value-store.jsx");
+
+function Collection({ collection, query_store }, component) {
+	return class Component extends React.Component {
+		constructor() {
+			super();
+			this.state = {
+				loading: true,
+				resources: [],
+			};
+		}
+		componentDidMount() {
+			this.setState({ loading: true });
+			CachedHttp.get(
+				`/api/v1/collections/${collection}`,
+				query_store.getQuery()
+			).then(resources => {
+				console.log(resources);
+				this.setState({ resources, loading: false });
+			});
+		}
+		render() {
+			return React.createElement(component, {
+				collection,
+				query_store,
+				resources: this.state.resources,
+				loading: this.state.loading,
+			});
+		}
+	};
+}
+
+module.exports = Collection;
diff --git a/react-components/lib/query-stores/query-store.js b/react-components/lib/query-stores/query-store.js
new file mode 100644
--- /dev/null
+++ b/react-components/lib/query-stores/query-store.js
@@ -0,0 +1,20 @@
+const EventEmitter = require("events");
+
+class QueryStore extends EventEmitter {
+	constructor() {
+		super();
+	}
+	init() {
+		this.store.on("change", () => this.emit("change"));
+	}
+	setFilter(filter) {
+		this.store.set("filter", filter);
+		this.store.set("pagination.page", 1);
+	}
+	getQuery() {
+		return this.store.getStore();
+	}
+}
+module.exports = QueryStore;
+
+QueryStore.Stateful = require("./stateful-query-store.js");
diff --git a/react-components/lib/query-stores/stateful-query-store.js b/react-components/lib/query-stores/stateful-query-store.js
new file mode 100644
--- /dev/null
+++ b/react-components/lib/query-stores/stateful-query-store.js
@@ -0,0 +1,13 @@
+const KeyValueStore = require("./../stores/key-value-store.js");
+const QueryStore = require("./query-store.js");
+
+module.exports = class StatefulQueryStore extends QueryStore {
+	constructor() {
+		super();
+		this.store = new KeyValueStore({
+			filter: {},
+			pagination: { page: 1, items: 12 },
+		});
+		this.init();
+	}
+};
diff --git a/react-components/lib/resource.jsx b/react-components/lib/resource.jsx
new file mode 100644
--- /dev/null
+++ b/react-components/lib/resource.jsx
@@ -0,0 +1,27 @@
+const React = require("react");
+const CachedHttp = require("./cached-http.js");
+
+module.exports = ({ collection }, component) =>
+	class Resource extends React.Component {
+		constructor() {
+			super();
+			this.state = {
+				loading: true,
+				resource: null,
+			};
+		}
+		componentDidMount() {
+			CachedHttp.get(`/api/v1/collections/${collection}/${this.props.id}`).then(
+				resource => this.setState({ loading: false, resource })
+			);
+		}
+		render() {
+			if (!this.props.id) {
+				throw Error("Please provide the resource id as an 'id' prop");
+			}
+			return React.createElement(component, {
+				resource: this.state.resource,
+				loading: this.state.loading,
+			});
+		}
+	};
diff --git a/react-components/lib/stores/connect-with-key-value-store.jsx b/react-components/lib/stores/connect-with-key-value-store.jsx
new file mode 100755
--- /dev/null
+++ b/react-components/lib/stores/connect-with-key-value-store.jsx
@@ -0,0 +1,33 @@
+const React = require("react");
+const merge = require("merge");
+
+module.exports = function(store, store_prop_name, component) {
+    return React.createClass({
+        getInitialState: function() {
+            return { [store_prop_name]: {} };
+        },
+        componentDidMount: function() {
+            const self = this;
+            const listener = function(value) {
+                self.setState({ [store_prop_name]: value });
+            };
+            store.on("change", listener);
+            self.setState({
+                listener: listener,
+                [store_prop_name]: store.getStore(),
+            });
+        },
+        componentWillUnmount: function() {
+            const self = this;
+            store.off("change", self.state.listener);
+        },
+        render: function() {
+            return React.createElement(
+                component,
+                merge(true, this.props, {
+                    [store_prop_name]: this.state[store_prop_name],
+                })
+            );
+        },
+    });
+};
diff --git a/react-components/lib/stores/key-value-store.js b/react-components/lib/stores/key-value-store.js
new file mode 100755
--- /dev/null
+++ b/react-components/lib/stores/key-value-store.js
@@ -0,0 +1,51 @@
+const React = require("react");
+const EventEmitter = require("event-emitter");
+
+const KeyValueStore = function(initial_values) {
+	const self = this;
+	var ee = new EventEmitter();
+	this.on = ee.on.bind(ee);
+	this.off = ee.off.bind(ee);
+	this.once = ee.once.bind(ee);
+	this.emit = ee.emit.bind(ee);
+
+	let store = initial_values || {};
+
+	this.set = function(key, value) {
+		const key_elements = key.split(".").reverse();
+		let current_pointer = store;
+		while (key_elements.length > 1) {
+			let current_key = key_elements.pop();
+			if (current_pointer[current_key] === undefined) {
+				current_pointer[current_key] = {};
+			}
+			current_pointer = current_pointer[current_key];
+		}
+		current_pointer[key_elements.pop()] = value;
+		ee.emit("change", store);
+	};
+
+	this.setters = {};
+	this.change_handlers = {};
+	for (const field_name in initial_values) {
+		this.change_handlers[field_name] = function(e) {
+			self.set(field_name, e.target.value);
+		};
+		this.setters[field_name] = this.set.bind(field_name);
+	}
+
+	this.get = function(key) {
+		return store[key];
+	};
+
+	this.replaceStore = function(value) {
+		store = value;
+		ee.emit("change", store);
+	};
+
+	this.getStore = function() {
+		return store;
+	};
+};
+
+module.exports = KeyValueStore;