├── .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 | [](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 |
--------------------------------------------------------------------------------