├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── App.js ├── AsyncAwaitApproach └── index.js ├── AxiosApproach ├── index.js └── test.js ├── FetchApproach └── index.js ├── HigherOrderComponentApproach └── index.js ├── RenderPropApproach └── index.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-data-fetching 2 | 3 | [![Slack](https://slack-the-road-to-learn-react.wieruch.com/badge.svg)](https://slack-the-road-to-learn-react.wieruch.com/) 4 | 5 | Showcase on how to perform data fetching in React. Read more about it in this article: [How to fetch data in React?](https://www.robinwieruch.de/react-fetching-data) 6 | 7 | ## Installation 8 | 9 | * `git clone git@github.com:rwieruch/react-data-fetching.git` 10 | * `cd react-data-fetching` 11 | * `npm install` 12 | * `npm start` 13 | * visit http://localhost:3000/ 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-fetching-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "react": "^16.4.1", 8 | "react-dom": "^16.4.1", 9 | "react-scripts": "1.1.4" 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test --env=jsdom", 15 | "eject": "react-scripts eject" 16 | }, 17 | "devDependencies": { 18 | "enzyme": "^3.3.0", 19 | "enzyme-adapter-react-16": "^1.1.1", 20 | "sinon": "^6.1.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rwieruch/react-data-fetching/b8c44a07363ae5fbad9e0cc3d7141273c4b5cb31/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | 3 | import FetchApproach from './FetchApproach'; 4 | import AxiosApproach from './AxiosApproach'; 5 | import AsyncAwaitApproach from './AsyncAwaitApproach'; 6 | import HigherOrderComponentApproach from './HigherOrderComponentApproach'; 7 | import RenderPropApproach from './RenderPropApproach'; 8 | 9 | const App = () => 10 | 11 | 12 | 13 | // 14 | // 15 | // 16 | // 17 | // 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /src/AsyncAwaitApproach/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | 4 | const API = 'https://hn.algolia.com/api/v1/search?query='; 5 | const DEFAULT_QUERY = 'redux'; 6 | 7 | class AxiosApproach extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | hits: [], 13 | isLoading: false, 14 | error: null, 15 | }; 16 | } 17 | 18 | async componentDidMount() { 19 | this.setState({ isLoading: true }); 20 | 21 | try { 22 | const result = await axios.get(API + DEFAULT_QUERY); 23 | 24 | this.setState({ 25 | hits: result.data.hits, 26 | isLoading: false 27 | }); 28 | } catch (error) { 29 | this.setState({ 30 | error, 31 | isLoading: false 32 | }); 33 | } 34 | } 35 | 36 | render() { 37 | const { hits, isLoading, error } = this.state; 38 | 39 | if (error) { 40 | return

{error.message}

; 41 | } 42 | 43 | if (isLoading) { 44 | return

Loading ...

; 45 | } 46 | 47 | return ( 48 |
    49 | {hits.map(hit => 50 |
  • 51 | {hit.title} 52 |
  • 53 | )} 54 |
55 | ); 56 | } 57 | } 58 | 59 | export default AxiosApproach; -------------------------------------------------------------------------------- /src/AxiosApproach/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | 4 | const API = 'https://hn.algolia.com/api/v1/search?query='; 5 | const DEFAULT_QUERY = 'redux'; 6 | 7 | class AxiosApproach extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | hits: [], 13 | isLoading: false, 14 | error: null, 15 | }; 16 | } 17 | 18 | componentDidMount() { 19 | this.setState({ isLoading: true }); 20 | 21 | axios.get(API + DEFAULT_QUERY) 22 | .then(result => this.setState({ 23 | hits: result.data.hits, 24 | isLoading: false 25 | })) 26 | .catch(error => this.setState({ 27 | error, 28 | isLoading: false 29 | })); 30 | } 31 | 32 | render() { 33 | const { hits, isLoading, error } = this.state; 34 | 35 | if (error) { 36 | return

{error.message}

; 37 | } 38 | 39 | if (isLoading) { 40 | return

Loading ...

; 41 | } 42 | 43 | return ( 44 |
    45 | {hits.map(hit => 46 |
  • 47 | {hit.title} 48 |
  • 49 | )} 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default AxiosApproach; -------------------------------------------------------------------------------- /src/AxiosApproach/test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import sinon from 'sinon'; 4 | 5 | import { mount, configure} from 'enzyme'; 6 | import Adapter from 'enzyme-adapter-react-16'; 7 | 8 | import AxiosApproach from './'; 9 | 10 | configure({ adapter: new Adapter() }); 11 | 12 | describe('AxiosApproach', () => { 13 | const result = { 14 | data: { 15 | hits: [ 16 | { objectID: '1', url: 'https://blog.com/hello', title: 'hello', }, 17 | { objectID: '2', url: 'https://blog.com/there', title: 'there', }, 18 | ], 19 | } 20 | }; 21 | 22 | const promise = Promise.resolve(result); 23 | 24 | beforeAll(() => { 25 | sinon 26 | .stub(axios, 'get') 27 | .withArgs('https://hn.algolia.com/api/v1/search?query=redux') 28 | .returns(promise); 29 | }); 30 | 31 | afterAll(() => { 32 | axios.get.restore(); 33 | }); 34 | 35 | it('stores data in local state', (done) => { 36 | const wrapper = mount(); 37 | 38 | expect(wrapper.state().hits).toEqual([]); 39 | 40 | promise.then(() => { 41 | wrapper.update(); 42 | 43 | expect(wrapper.state().hits).toEqual(result.data.hits); 44 | 45 | done(); 46 | }); 47 | }); 48 | 49 | it('renders data when it fetched data successfully', (done) => { 50 | const wrapper = mount(); 51 | 52 | expect(wrapper.find('p').text()).toEqual('Loading ...'); 53 | 54 | promise.then(() => { 55 | wrapper.update(); 56 | 57 | expect(wrapper.find('li')).toHaveLength(2); 58 | 59 | done(); 60 | }); 61 | }); 62 | }); -------------------------------------------------------------------------------- /src/FetchApproach/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | const API = 'https://hn.algolia.com/api/v1/search?query='; 4 | const DEFAULT_QUERY = 'redux'; 5 | 6 | class FetchApproach extends Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | this.state = { 11 | hits: [], 12 | isLoading: false, 13 | error: null, 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | this.setState({ isLoading: true }); 19 | 20 | fetch(API + DEFAULT_QUERY) 21 | .then(response => { 22 | if (response.ok) { 23 | return response.json(); 24 | } else { 25 | throw new Error('Something went wrong ...'); 26 | } 27 | }) 28 | .then(data => this.setState({ hits: data.hits, isLoading: false })) 29 | .catch(error => this.setState({ error, isLoading: false })); 30 | } 31 | 32 | render() { 33 | const { hits, isLoading, error } = this.state; 34 | 35 | if (error) { 36 | return

{error.message}

; 37 | } 38 | 39 | if (isLoading) { 40 | return

Loading ...

; 41 | } 42 | 43 | return ( 44 |
    45 | {hits.map(hit => 46 |
  • 47 | {hit.title} 48 |
  • 49 | )} 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default FetchApproach; -------------------------------------------------------------------------------- /src/HigherOrderComponentApproach/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | 4 | const API = 'https://hn.algolia.com/api/v1/search?query='; 5 | const DEFAULT_QUERY = 'redux'; 6 | 7 | const withFetching = (url) => (Component) => 8 | class WithFetching extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = { 13 | data: null, 14 | isLoading: false, 15 | error: null, 16 | }; 17 | } 18 | 19 | componentDidMount() { 20 | this.setState({ isLoading: true }); 21 | 22 | axios.get(url) 23 | .then(result => this.setState({ 24 | data: result.data, 25 | isLoading: false 26 | })) 27 | .catch(error => this.setState({ 28 | error, 29 | isLoading: false 30 | })); 31 | } 32 | 33 | render() { 34 | return ; 35 | } 36 | } 37 | 38 | const HigherOrderComponentApproach = ({ data, isLoading, error }) => { 39 | if (!data) { 40 | return

No data yet ...

; 41 | } 42 | 43 | if (error) { 44 | return

{error.message}

; 45 | } 46 | 47 | if (isLoading) { 48 | return

Loading ...

; 49 | } 50 | 51 | return ( 52 |
    53 | {data.hits.map(hit => 54 |
  • 55 | {hit.title} 56 |
  • 57 | )} 58 |
59 | ); 60 | } 61 | 62 | export default withFetching(API + DEFAULT_QUERY)(HigherOrderComponentApproach); 63 | -------------------------------------------------------------------------------- /src/RenderPropApproach/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | 4 | const API = 'https://hn.algolia.com/api/v1/search?query='; 5 | const DEFAULT_QUERY = 'redux'; 6 | 7 | class Fetcher extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.state = { 12 | data: null, 13 | isLoading: false, 14 | error: null, 15 | }; 16 | } 17 | 18 | componentDidMount() { 19 | this.setState({ isLoading: true }); 20 | 21 | axios.get(this.props.url) 22 | .then(result => this.setState({ 23 | data: result.data, 24 | isLoading: false 25 | })) 26 | .catch(error => this.setState({ 27 | error, 28 | isLoading: false 29 | })); 30 | } 31 | 32 | render() { 33 | return this.props.children(this.state); 34 | } 35 | } 36 | 37 | const RenderPropApproach = ({ data, isLoading, error }) => 38 | 39 | {({ data, isLoading, error }) => { 40 | if (!data) { 41 | return

No data yet ...

; 42 | } 43 | 44 | if (error) { 45 | return

{error.message}

; 46 | } 47 | 48 | if (isLoading) { 49 | return

Loading ...

; 50 | } 51 | 52 | return ( 53 |
    54 | {data.hits.map(hit => 55 |
  • 56 | {hit.title} 57 |
  • 58 | )} 59 |
60 | ); 61 | }} 62 |
63 | 64 | export default RenderPropApproach; 65 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | --------------------------------------------------------------------------------