From 9b1749068cddbdd8baa4aedda67e1ebeb770816f Mon Sep 17 00:00:00 2001 From: kvutien Date: Tue, 18 May 2021 16:42:48 +0200 Subject: [PATCH 1/8] more comments and more specific README --- examples/browser-create-react-app/README.md | 67 ++++++--------- .../images/appJsStructure.png | Bin 0 -> 24061 bytes .../{ => images}/screenshot.png | Bin examples/browser-create-react-app/src/App.js | 51 ++++++------ .../src/hooks/use-ipfs-factory.js | 77 ++++++++++-------- .../src/hooks/use-ipfs.js | 30 ++++--- 6 files changed, 111 insertions(+), 114 deletions(-) create mode 100644 examples/browser-create-react-app/images/appJsStructure.png rename examples/browser-create-react-app/{ => images}/screenshot.png (100%) diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index 1d07fd7fc3..8922ec57b6 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -2,24 +2,35 @@ A minimal demonstration of how to use js-ipfs in a `create-react-app` generated app. -It boots up a js-ipfs instance via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`, which is where the magic happens. +It boots up a js-ipfs instance (an IPFS node) via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`. Once the IPFS node is set up, `App.js` displays its ident and its version number. -![Screen shot of the js ipfs node id info](./screenshot.png) +![Screen shot of the js ipfs node id info](./images/screenshot.png) -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). **v2.1.3** +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). **v2.1.3**. It has been tested with `node` **v12.18.4** (`npm` **v6.14.6**) and `node` **v14.17.0** (`npm` **v7.13.0****) -## Before you start +**Note**: this example is useful to learn how to spawn IPFS from a web page, but this is not the best way to use IPFS because it will spawn an IPFS daemon regardless of whether there is already another daemon running on the same computer. See https://github.com/ipfs/in-web-browsers/issues/158. For pragmatic uses, you may want to call directly the IPFS node run by Infura, see tutorial at https://github.com/kvutien/ipfs-dapp. -First clone this repo, install dependencies in the project root and build the project. +## To start + +First clone the whole repo, install dependencies limited to this project `browser-create-react-app` and run the demo. ```console $ git clone https://github.com/ipfs/js-ipfs.git -$ cd js-ipfs +$ cd js-ipfs/examples/browser-create-react-app $ npm install -$ npm run build +$ npm start ``` +## Call structure in `App.js` +All React applications store their main logic in `App.js`. For this demo, the calling sequence of `App.js` is ![App.js](./images/appJsStructure.png) +* `App.js` renders the cosmetics of the demo +* `useIpfsFactory.js` initialises and closes the IPFS daemon +* `useIpfs.js` does the actual calls to IPFS + +## Annex: console message `[HMR] Waiting for update signal from WDS...` + +This message comes from the hot reload capability of webpack, that can update the web app every time you save your development code. To remove it, see here: https://stackoverflow.com/questions/59695102/reactjs-console-error-hmr-waiting-for-update-signal-from-wds -## Available Scripts +## Annex: available Scripts from create-react-app In the project directory, you can run: @@ -42,46 +53,16 @@ Builds the app for production to the `build` folder.
It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! - -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. - -### `npm run eject` +Your app is ready to be deployed! For example drag-and-dropped in the Netlify manual installation page. -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +But with modern hosting services like Heroku, Netlity or Fleek, you can skip the build because they will do a complete github deployment for you. See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. - -Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. - -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. ## Learn More -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). - -To learn React, check out the [React documentation](https://reactjs.org/). - -### Code Splitting - -This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting - -### Analyzing the Bundle Size - -This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size - -### Making a Progressive Web App - -This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app - -### Advanced Configuration - -This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration - -### Deployment +You can learn more on IPFS API in the [IPFS documentation](https://docs.ipfs.io/) and [IPFS npm documentation](https://www.npmjs.com/package/ipfs-http-client). -This section has moved here: https://facebook.github.io/create-react-app/docs/deployment +Details how to use the File System abstraction of IPFS (add, cat, egt, ls etc.) are [here](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md) -### `npm run build` fails to minify +To have a high level view of IPFS, check out the [ConsenSys blog](https://medium.com/@ConsenSys/an-introduction-to-ipfs-9bba4860abd0). -This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify diff --git a/examples/browser-create-react-app/images/appJsStructure.png b/examples/browser-create-react-app/images/appJsStructure.png new file mode 100644 index 0000000000000000000000000000000000000000..17df37ed8064c0ec1921c0192b72ca511df34199 GIT binary patch literal 24061 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 n}FVY@4odX2T_`1Azaj6f}LPMo4 o@bYnleVhOG`G^(zvx`Vb*PhnU)Z7}Lc=@TCl7?cLf<^GZ0SPB0rvLx| literal 0 HcmV?d00001 diff --git a/examples/browser-create-react-app/screenshot.png b/examples/browser-create-react-app/images/screenshot.png similarity index 100% rename from examples/browser-create-react-app/screenshot.png rename to examples/browser-create-react-app/images/screenshot.png diff --git a/examples/browser-create-react-app/src/App.js b/examples/browser-create-react-app/src/App.js index f742be5208..a3e9bc9adf 100755 --- a/examples/browser-create-react-app/src/App.js +++ b/examples/browser-create-react-app/src/App.js @@ -4,8 +4,11 @@ import useIpfs from './hooks/use-ipfs.js' import logo from './ipfs-logo.svg' const App = () => { - const { ipfs, ipfsInitError } = useIpfsFactory({ commands: ['id'] }) - const id = useIpfs(ipfs, 'id') + // there is no change when we comment out the args of useIpfsFactory -> remove args? + const { ipfs, ipfsInitError } = useIpfsFactory(/*{ commands: ['id'] }*/) + // retrieve id of the current peer (see https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/MISCELLANEOUS.md) + const id = useIpfs(ipfs, 'id') + return (
@@ -26,27 +29,29 @@ const App = () => { ) } -const Title = ({ children }) => { - return ( -

{children}

- ) -} - const IpfsId = (props) => { - if (!props) return null - return ( -
-

Connected to IPFS

-
- {['id', 'agentVersion'].map((key) => ( -
- {key} -
{props[key]}
-
- ))} -
-
- ) + // the props of this component are all key-value pairs of the object 'id' + // prints out the values of the keys 'id' and 'agentVersion' of the object 'id' + if (!props) return null + return ( +
+

Connected – properties of current IPFS node

+
+ {['id', 'agentVersion'].map((key) => ( +
+ {key} +
{props[key]}
+
+ ))} +
+
+ ) } -export default App +const Title = ({ children }) => { + return ( +

{children}

+ ) + } + + export default App diff --git a/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js b/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js index dcd4da6607..eb01e110cd 100644 --- a/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js +++ b/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js @@ -1,10 +1,12 @@ import Ipfs from 'ipfs' +// ipfs is the core API, a CLI and a HTTP server that functions as a HTTP to IPFS bridge +// and an RPC endpoint. See https://www.npmjs.com/package/ipfs import { useEffect, useState } from 'react' let ipfs = null /* - * A quick demo using React hooks to create an ipfs instance. + * A quick demo using React hooks to create an ipfs instance. June 2020 * * Hooks are brand new at the time of writing, and this pattern * is intended to show it is possible. I don't know if it is wise. @@ -14,44 +16,49 @@ let ipfs = null * it to be passed in. */ export default function useIpfsFactory () { - const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs)) - const [ipfsInitError, setIpfsInitError] = useState(null) + // initialise state variables, React hooks + const [isIpfsReady, setIpfsReady] = useState(Boolean(ipfs)) + const [ipfsInitError, setIpfsInitError] = useState(null) - useEffect(() => { - // The fn to useEffect should not return anything other than a cleanup fn, - // So it cannot be marked async, which causes it to return a promise, - // Hence we delegate to a async fn rather than making the param an async fn. - async function startIpfs () { - if (ipfs) { - console.log('IPFS already started') - } else if (window.ipfs && window.ipfs.enable) { - console.log('Found window.ipfs') - ipfs = await window.ipfs.enable({ commands: ['id'] }) - } else { - try { - console.time('IPFS Started') - ipfs = await Ipfs.create() - console.timeEnd('IPFS Started') - } catch (error) { - console.error('IPFS init error:', error) - ipfs = null - setIpfsInitError(error) + useEffect(() => { + // useEffect -as used here- is equivalent to componentDidMount in old React + // The hook useEffect should not return anything other than a cleanup fn, + // in addition, in a true life application there are many other context init things + // hence in this example we make only a call to an async that initialises IPFS + startIpfs() + // ... add here any other init fn as required by an application + return function cleanup () { + if (ipfs && ipfs.stop) { + console.log('Stopping IPFS') + ipfs.stop().catch(err => console.error(err)) + ipfs = null + setIpfsReady(false) + } } - } + }, []) - setIpfsReady(Boolean(ipfs)) - } + async function startIpfs () { + // initialise IPFS daemon + if (ipfs) { + console.log('IPFS already started') + } else if (window.ipfs && window.ipfs.enable) { + // legacy IPFS. Disabled. See here: https://docs.ipfs.io/how-to/companion-window-ipfs/ + console.log('Found window.ipfs') + ipfs = await window.ipfs.enable({ commands: ['id'] }) + } else { + try { + console.time('IPFS Started') // start timer + ipfs = await Ipfs.create() + console.timeEnd('IPFS Started') // stop timer and log duration in console + } catch (error) { + console.error('IPFS init error:', error) + ipfs = null + setIpfsInitError(error) + } + } - startIpfs() - return function cleanup () { - if (ipfs && ipfs.stop) { - console.log('Stopping IPFS') - ipfs.stop().catch(err => console.error(err)) - ipfs = null - setIpfsReady(false) - } + setIpfsReady(Boolean(ipfs)) } - }, []) - return { ipfs, isIpfsReady, ipfsInitError } + return { ipfs, isIpfsReady, ipfsInitError } } diff --git a/examples/browser-create-react-app/src/hooks/use-ipfs.js b/examples/browser-create-react-app/src/hooks/use-ipfs.js index 39678bb16b..5e48089100 100644 --- a/examples/browser-create-react-app/src/hooks/use-ipfs.js +++ b/examples/browser-create-react-app/src/hooks/use-ipfs.js @@ -1,26 +1,30 @@ import { useState, useEffect } from 'react' import dotProp from 'dot-prop' +// dot-prop: used to obtain a property of an object when the name of property is a string +// here we get ipfs.id when calling dotProp.get(ipfs, cmd), with cmd = 'id' +// and we get ipfs.hash when calling with cmd = 'hash' etc. /* * Pass the command you'd like to call on an ipfs instance. * - * Uses setState to capture the response, so your component - * will re-render when the result turns up. + * callIpfs uses setState write the response as a state variable, so that your component + * will re-render when the result 'res' turns up from the call await ipfsCmd. * */ export default function useIpfs (ipfs, cmd, opts) { - const [res, setRes] = useState(null) - useEffect(() => { - callIpfs(ipfs, cmd, opts, setRes) - }, [ipfs, cmd, opts]) - return res + // note: opts is not used here and is not passed as args of the call from App.js + const [res, setRes] = useState(null) + useEffect(() => { + callIpfs(ipfs, cmd, opts, setRes) + }, [ipfs, cmd, opts]) + return res } async function callIpfs (ipfs, cmd, opts, setRes) { - if (!ipfs) return null - console.log(`Call ipfs.${cmd}`) - const ipfsCmd = dotProp.get(ipfs, cmd) - const res = await ipfsCmd(opts) - console.log(`Result ipfs.${cmd}`, res) - setRes(res) + if (!ipfs) return null + console.log(`Call ipfs.${cmd}`) + const ipfsCmd = dotProp.get(ipfs, cmd) + const res = await ipfsCmd(opts) + console.log(`Result ipfs.${cmd}`, res) + setRes(res) } From 042c47bc3ebac864a8cde3536bff8477e6ccec30 Mon Sep 17 00:00:00 2001 From: kvutien Date: Tue, 18 May 2021 16:50:19 +0200 Subject: [PATCH 2/8] more comments and more specific README --- examples/browser-create-react-app/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index 8922ec57b6..846a7f338a 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -26,15 +26,16 @@ All React applications store their main logic in `App.js`. For this demo, the ca * `useIpfsFactory.js` initialises and closes the IPFS daemon * `useIpfs.js` does the actual calls to IPFS -## Annex: console message `[HMR] Waiting for update signal from WDS...` +## Annexes +### Console message `[HMR] Waiting for update signal from WDS...` This message comes from the hot reload capability of webpack, that can update the web app every time you save your development code. To remove it, see here: https://stackoverflow.com/questions/59695102/reactjs-console-error-hmr-waiting-for-update-signal-from-wds -## Annex: available Scripts from create-react-app +### Available Scripts from create-react-app In the project directory, you can run: -### `npm start` +#### `npm start` Runs the app in the development mode.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser. @@ -42,12 +43,12 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits.
You will also see any lint errors in the console. -### `npm test` +#### `npm test` Launches the test runner in the interactive watch mode.
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. -### `npm run build` +#### `npm run build` Builds the app for production to the `build` folder.
It correctly bundles React in production mode and optimizes the build for the best performance. From 02c8f0a3752a4a3c85d6b3f9b53a669ce900859b Mon Sep 17 00:00:00 2001 From: kvutien Date: Tue, 29 Jun 2021 18:56:40 +0200 Subject: [PATCH 3/8] more comments and more specific README --- examples/browser-create-react-app/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index 846a7f338a..9e495cfe5a 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -2,13 +2,15 @@ A minimal demonstration of how to use js-ipfs in a `create-react-app` generated app. -It boots up a js-ipfs instance (an IPFS node) via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`. Once the IPFS node is set up, `App.js` displays its ident and its version number. +It boots up a js-ipfs instance (an IPFS node) via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`. Once the IPFS node is set up, `App.js` displays its ident and its version number. + +> _Remember that a Peer ID of an IPFS node is the SHA-256 multihash of the public key of this node, and the public-private key pair of a node is generated by typing `ipfs init`._ ![Screen shot of the js ipfs node id info](./images/screenshot.png) This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). **v2.1.3**. It has been tested with `node` **v12.18.4** (`npm` **v6.14.6**) and `node` **v14.17.0** (`npm` **v7.13.0****) -**Note**: this example is useful to learn how to spawn IPFS from a web page, but this is not the best way to use IPFS because it will spawn an IPFS daemon regardless of whether there is already another daemon running on the same computer. See https://github.com/ipfs/in-web-browsers/issues/158. For pragmatic uses, you may want to call directly the IPFS node run by Infura, see tutorial at https://github.com/kvutien/ipfs-dapp. +**Note**: this example is useful to learn how to spawn IPFS from a web page. It is also possible to spawn an IPFS daemon from the command line with `ipfs daemon`. For pragmatic uses, you may want to call directly the IPFS node run by Infura, see tutorial at https://blog.infura.io/part-2-getting-started-with-ipfs-on-infura/ and at https://github.com/kvutien/ipfs-dapp. ## To start @@ -23,7 +25,7 @@ $ npm start ## Call structure in `App.js` All React applications store their main logic in `App.js`. For this demo, the calling sequence of `App.js` is ![App.js](./images/appJsStructure.png) * `App.js` renders the cosmetics of the demo -* `useIpfsFactory.js` initialises and closes the IPFS daemon +* `useIpfsFactory.js` initialises and closes the IPFS local node * `useIpfs.js` does the actual calls to IPFS ## Annexes @@ -56,7 +58,7 @@ It correctly bundles React in production mode and optimizes the build for the be The build is minified and the filenames include the hashes.
Your app is ready to be deployed! For example drag-and-dropped in the Netlify manual installation page. -But with modern hosting services like Heroku, Netlity or Fleek, you can skip the build because they will do a complete github deployment for you. See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +But with modern hosting services like Heroku, Netlity or Fleek, you can skip the build because they will do a complete github deployment for you. See the React official page about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. ## Learn More From e1a57a7230d7e9e04eb6c33f933205f2ebaa3138 Mon Sep 17 00:00:00 2001 From: kvutien Date: Tue, 29 Jun 2021 19:13:10 +0200 Subject: [PATCH 4/8] more comments and more specific README --- examples/browser-create-react-app/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index 9e495cfe5a..d5c6bc192d 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -24,9 +24,9 @@ $ npm start ``` ## Call structure in `App.js` All React applications store their main logic in `App.js`. For this demo, the calling sequence of `App.js` is ![App.js](./images/appJsStructure.png) -* `App.js` renders the cosmetics of the demo +* `App.js` renders the cosmetics of the demo and call `useIpfs` to retrieve the `id` of the node * `useIpfsFactory.js` initialises and closes the IPFS local node -* `useIpfs.js` does the actual calls to IPFS +* `useIpfs.js` does the actual calls to IPFS to retrieve the property specified in argument (here it's `id`, sent from `App.js`) ## Annexes ### Console message `[HMR] Waiting for update signal from WDS...` From 7a80922c4377358b8c4447a173f58487ce03e0aa Mon Sep 17 00:00:00 2001 From: kvutien Date: Tue, 29 Jun 2021 19:15:52 +0200 Subject: [PATCH 5/8] more comments and more specific README --- examples/browser-create-react-app/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index d5c6bc192d..6f47e3dfd8 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -26,7 +26,7 @@ $ npm start All React applications store their main logic in `App.js`. For this demo, the calling sequence of `App.js` is ![App.js](./images/appJsStructure.png) * `App.js` renders the cosmetics of the demo and call `useIpfs` to retrieve the `id` of the node * `useIpfsFactory.js` initialises and closes the IPFS local node -* `useIpfs.js` does the actual calls to IPFS to retrieve the property specified in argument (here it's `id`, sent from `App.js`) +* `useIpfs.js` does the actual calls to IPFS to retrieve the property specified in argument (here the retrieved property is `id`, requested from `App.js`) ## Annexes ### Console message `[HMR] Waiting for update signal from WDS...` From 28bebac1fc2b1cb7c7928e4d0acda4f1f9edec7c Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Sat, 3 Jul 2021 01:03:10 +0200 Subject: [PATCH 6/8] test: examples/browser-create-react-app/test.js --- examples/browser-create-react-app/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/browser-create-react-app/test.js b/examples/browser-create-react-app/test.js index 812769aca0..6c3c11dcd7 100644 --- a/examples/browser-create-react-app/test.js +++ b/examples/browser-create-react-app/test.js @@ -7,7 +7,7 @@ module.exports = { browser .url(process.env.IPFS_EXAMPLE_TEST_URL) .waitForElementVisible('[data-test=title]') - .assert.containsText('[data-test=title]', 'Connected to IPFS') + .assert.containsText('[data-test=title]', 'Connected – properties of current IPFS node') .assert.elementPresent('[data-test=id') .assert.elementPresent('[data-test=agentVersion') .end() From b83b0d1db086e59cb21dd3a49e8ebc7731be5493 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Sat, 3 Jul 2021 01:41:18 +0200 Subject: [PATCH 7/8] chore: small style tweaks and links from review --- examples/browser-create-react-app/README.md | 9 +++------ .../src/hooks/use-ipfs-factory.js | 6 +----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index 6f47e3dfd8..f29d46afaf 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -4,13 +4,12 @@ A minimal demonstration of how to use js-ipfs in a `create-react-app` generated It boots up a js-ipfs instance (an IPFS node) via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`. Once the IPFS node is set up, `App.js` displays its ident and its version number. -> _Remember that a Peer ID of an IPFS node is the SHA-256 multihash of the public key of this node, and the public-private key pair of a node is generated by typing `ipfs init`._ +> _Remember that a Peer ID of an IPFS node is [the multihash of the public key of this node](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#peer-ids), and the public-private key pair of a node is generated by typing `ipfs init`._ ![Screen shot of the js ipfs node id info](./images/screenshot.png) -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). **v2.1.3**. It has been tested with `node` **v12.18.4** (`npm` **v6.14.6**) and `node` **v14.17.0** (`npm` **v7.13.0****) -**Note**: this example is useful to learn how to spawn IPFS from a web page. It is also possible to spawn an IPFS daemon from the command line with `ipfs daemon`. For pragmatic uses, you may want to call directly the IPFS node run by Infura, see tutorial at https://blog.infura.io/part-2-getting-started-with-ipfs-on-infura/ and at https://github.com/kvutien/ipfs-dapp. +**Note**: this example is useful to learn how to spawn IPFS from a web page. It is also possible to [spawn an IPFS daemon from the command line](https://docs.ipfs.io/install/command-line/) with `ipfs daemon`. While self-hosting is advised, one can also delegate IPFS operations to a third-party like Infura. See tutorials [here](https://blog.infura.io/part-2-getting-started-with-ipfs-on-infura/) and [here](https://blog.infura.io/part-2-getting-started-with-ipfs-on-infura/). ## To start @@ -56,7 +55,7 @@ Builds the app for production to the `build` folder.
It correctly bundles React in production mode and optimizes the build for the best performance. The build is minified and the filenames include the hashes.
-Your app is ready to be deployed! For example drag-and-dropped in the Netlify manual installation page. +Your app is ready to be deployed! Read how to host a [single page](https://docs.ipfs.io/how-to/websites-on-ipfs/single-page-website/) or an [entire website](https://docs.ipfs.io/how-to/websites-on-ipfs/multipage-website/#prerequisites) on IPFS. But with modern hosting services like Heroku, Netlity or Fleek, you can skip the build because they will do a complete github deployment for you. See the React official page about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. @@ -67,5 +66,3 @@ You can learn more on IPFS API in the [IPFS documentation](https://docs.ipfs.io/ Details how to use the File System abstraction of IPFS (add, cat, egt, ls etc.) are [here](https://github.com/ipfs/js-ipfs/blob/master/docs/core-api/FILES.md) -To have a high level view of IPFS, check out the [ConsenSys blog](https://medium.com/@ConsenSys/an-introduction-to-ipfs-9bba4860abd0). - diff --git a/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js b/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js index eb01e110cd..b369a40f4e 100644 --- a/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js +++ b/examples/browser-create-react-app/src/hooks/use-ipfs-factory.js @@ -6,7 +6,7 @@ import { useEffect, useState } from 'react' let ipfs = null /* - * A quick demo using React hooks to create an ipfs instance. June 2020 + * A quick demo using React hooks to create an ipfs instance. * * Hooks are brand new at the time of writing, and this pattern * is intended to show it is possible. I don't know if it is wise. @@ -41,10 +41,6 @@ export default function useIpfsFactory () { // initialise IPFS daemon if (ipfs) { console.log('IPFS already started') - } else if (window.ipfs && window.ipfs.enable) { - // legacy IPFS. Disabled. See here: https://docs.ipfs.io/how-to/companion-window-ipfs/ - console.log('Found window.ipfs') - ipfs = await window.ipfs.enable({ commands: ['id'] }) } else { try { console.time('IPFS Started') // start timer From d0d2e65e4c3ba62c32dab6e1ac2f50063648ec9e Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Mon, 5 Jul 2021 12:37:15 +0200 Subject: [PATCH 8/8] chore: remove appJsStructure.png outdated (window.ipfs is no longer used) + we dont want to maintain it when the app gets refactored in the future --- examples/browser-create-react-app/README.md | 6 +++--- .../images/appJsStructure.png | Bin 24061 -> 0 bytes 2 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 examples/browser-create-react-app/images/appJsStructure.png diff --git a/examples/browser-create-react-app/README.md b/examples/browser-create-react-app/README.md index f29d46afaf..32effb8912 100755 --- a/examples/browser-create-react-app/README.md +++ b/examples/browser-create-react-app/README.md @@ -2,7 +2,7 @@ A minimal demonstration of how to use js-ipfs in a `create-react-app` generated app. -It boots up a js-ipfs instance (an IPFS node) via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`. Once the IPFS node is set up, `App.js` displays its ident and its version number. +It boots up a js-ipfs instance (an IPFS node) via a custom React hook in `./src/hooks/use-ipfs-factory.js`, which is called from `./src/App.js`. Once the IPFS node is set up, `App.js` displays its ident and its version number. > _Remember that a Peer ID of an IPFS node is [the multihash of the public key of this node](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md#peer-ids), and the public-private key pair of a node is generated by typing `ipfs init`._ @@ -22,9 +22,9 @@ $ npm install $ npm start ``` ## Call structure in `App.js` -All React applications store their main logic in `App.js`. For this demo, the calling sequence of `App.js` is ![App.js](./images/appJsStructure.png) +All React applications store their main logic in `App.js`: * `App.js` renders the cosmetics of the demo and call `useIpfs` to retrieve the `id` of the node -* `useIpfsFactory.js` initialises and closes the IPFS local node +* `useIpfsFactory.js` initialises and closes the IPFS local node * `useIpfs.js` does the actual calls to IPFS to retrieve the property specified in argument (here the retrieved property is `id`, requested from `App.js`) ## Annexes diff --git a/examples/browser-create-react-app/images/appJsStructure.png b/examples/browser-create-react-app/images/appJsStructure.png deleted file mode 100644 index 17df37ed8064c0ec1921c0192b72ca511df34199..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24061 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
d<7|q2!JyT7BCM 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 n}FVY@4odX2T_`1Azaj6f}LPMo4 o@bYnleVhOG`G^(zvx`Vb*PhnU)Z7}Lc=@TCl7?cLf<^GZ0SPB0rvLx|