Skip to content

Instantly share code, notes, and snippets.

@daxeh
Last active June 20, 2018 02:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daxeh/4972a339c7569c6eba45f5e1e6db1f89 to your computer and use it in GitHub Desktop.
Save daxeh/4972a339c7569c6eba45f5e1e6db1f89 to your computer and use it in GitHub Desktop.
An infinite data feed stream React container that fetches json data into a stateless presenter component
/**
* Adhoc non-persistent model for demo/development purposes (single session).
* @type {{}}
*/
const m = {};
const AdhocModel = {
add: (k, v) => {
m[ k ] = v;
//console.log(m);
},
remove: (k) => {
if (m.includes(k)) {
m[ k ] = undefined;
}
//console.log(m);
},
retrieve: (k) => {
if (m.includes(k)) {
return m[ k ];
}
},
size: () => {
return m.length;
},
print: () => {
console.log(m);
}
}
export default AdhocModel;
import React from 'react';
import PropTypes from 'prop-types';
import DefaultSpinner from '../common/DefaultSpinner';
import FetchedItem from './FetchedItem';
import RaisedButton from '../../elements/Button/RaisedButton';
import styled from 'styled-components';
import AdhocModel from '../common/AdhocModel';
// When `intervalInSeconds` > 0 is recieved then requests will be made every
// nth `intervalInSeconds` passed. `fetching` and `loading` describes the current
// state of the component when inspected.
const getDefaultProps = () => {
return {
data: {},
error: null,
isLoading: false,
intervalInSeconds: 0
}
}
/**
* Container that fetches and renders the fetched data as an item feed.
*/
export default class Container extends React.Component {
constructor(props) {
super(props);
this.state = getDefaultProps();
this.nextClickHandler = this.nextClickHandler.bind(this);
this.fetchData = this.fetchData.bind(this);
}
// Pre method invoked when attaching component to DOM, before `render()`
// and by calling `setState()` triggers the re-rendering process.
componentWillMount() {
this.setState({ isLoading: true });
// Updates state on response success or error
this.fetchData();
//console.log('componentWillMount');
}
componentWillReceiveProps(props) {
}
// Get data and set new state
fetchData() {
const self = this;
this.setState({ isLoading: true });
// Updates state on response success or error
fetch('https://api.example.io/random').then(response => {
if (response.ok) {
return response;
} else {
// 404's
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}).then(results => results.json()).then(data => {
AdhocModel.add(data.id, data);
//console.log(AdhocModel);
self.setState({
data,
isLoading: false
});
}).catch(
error => self.setState({
error,
isLoading: false
})
);
}
// Triggers new data fetch
nextClickHandler(event) {
this.fetchData();
}
// Render an item feed or loader.
render() {
const {data, error, isLoading} = this.state;
if (isLoading) {
return (
<DefaultSpinner/>
);
}
return (
<FetchedItem data={data} error={error} isLoading={isLoading}>
<RaisedButton onClick={this.nextClickHandler}>Next</RaisedButton>
</FetchedItem>
);
}
}
import React from 'react';
// Usecase example: Mixin our presenter to a container that has fetch features
// ---------------
// createFetchableContainer('https://api.example.io/categories')(CategoryList);
// const CategoryList = ({data, isLoading, error}) => {
// return data.map(createCategoryList);
// }
/**
* Returns a function to produce FetchableContainer, composable
* component to fetch data. If stateless component is not given,
* apply default Container.
*
* @param url location where the container will fetch data from
* @param httpOptions
* @returns fn
*/
const createFetchableContainer = (url, httpOptions) => (Comp) =>
class FetchableContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
data: {},
isLoading: false,
error: null
};
this.fetchData = this.fetchData.bind(this);
}
// Callable fetch method to supplied url endpoint
fetchData() {
const self = this;
this.setState({ isLoading: true });
// Updates state on response success or error
fetch(url, httpOptions = {}).then(response => {
if (response.ok) {
return response;
} else {
// 404's
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}).then(results => results.json()).then(data => {
self.setState({
data,
isLoading: false
});
}).catch(
error => self.setState({
error,
isLoading: false
})
);
}
render() {
return <Comp { ...this.props } { ...this.state } />
}
}
/**
* Presenter (StatelessComponent)
* @param data
* @param isLoading
* @param error
* @constructor
*/
const Container = ({data, isLoading, error, ...props}) => {
if (error) {
return <p>error.message</p>
}
return <div {...props.children}/>
}
/**
* Factory function
* @usage
* const fn = createFetchableContainer(url, options)(StatelessComponent);
* @return fn
*/
export default createFetchableContainer;
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const ItemWrapper = styled.div`
margin: 0 auto;
font-size: 20px;
margin-bottom: 30px;
text-align: center;
`;
const ErrorItemWrapper = styled(ItemWrapper)`
background-image: ${((props) => props['background-image'] ? props['background-image'] : '') };
color: #f04816;
height: 100%;
width: 100%;
`;
/**
* The data item to be rendered
* @param data
* @param error
* @param isLoading
* @returns {XML}
*/
export default function FetchedItem({data, error, ...otherProps}) {
if (error) {
return (
<ErrorItemWrapper>
{error.message}
{otherProps.children}
<Background background-image={animatedError}></Background>
</ErrorItemWrapper>
);
} else {
return (
<ItemWrapper id={data.id}>
{data.value}
{otherProps.children}
</ItemWrapper>
);
}
}
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { BasePencilDiv } from '../../elements/Div/index';
import Container from './Container';
const InfiniteStreamWrapper = styled(BasePencilDiv)`
background: #ffdbb6;
color: #41403E;
font-size: 2rem;
letter-spacing: 1px;
border: solid 7px #bbd186;
min-height: 300px;
-webkit-box-shadow: 0px 10px 15px 5px rgba(0,0,0,0.75);
box-shadow: 0px 10px 15px 5px rgba(0,0,0,0.75);
transition: all 0.6s ease
-webkit-transition: all 0.6s ease;
&:hover {
box-shadow: 2px 8px 4px -6px rgba(0, 0, 0, 0.5);
}
`;
const CategoryHeader = styled.h1`
`;
export default function InfiniteStream(props) {
return (
<InfiniteStreamWrapper>
{props.children}
<CategoryHeader>{props.category}</CategoryHeader>
<Container/>
</InfiniteStreamWrapper>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment