Last active
June 20, 2018 02:17
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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