One place for hosting & domains

      APIs

      How To Use Wget to Download Files and Interact with REST APIs


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Wget is a networking command-line tool that lets you download files and interact with REST APIs. It supports the HTTP,HTTPS, FTP, and FTPS internet protocols. Wget can deal with unstable and slow network connections. In the event of a download failure, Wget keeps trying until the entire file has been retrieved. Wget also lets you resume a file download that was interrupted without starting from scratch.

      You can also use Wget to interact with REST APIs without having to install any additional external programs. You can make GET, POST, PUT, and DELETE HTTP requests with single and multiple headers right in the terminal.

      In this tutorial, you will use Wget to download files, interact with REST API endpoints, and create and manage a Droplet in your DigitalOcean account.

      To follow along with this tutorial using a terminal in your browser, click the Launch an Interactive Terminal! button below:

      Launch an Interactive Terminal!

      Otherwise, if you’d like to use your local system or a remote server, open a terminal and run the commands there.

      Prerequisites

      To complete this tutorial, you will need:

      • Wget installed. Most Linux distributions have Wget installed by default. To check, type wget in your terminal and press ENTER. If it is not installed, it will display: command not found. You can install it by running the following command: sudo apt-get install wget.

      • A DigitalOcean account. If you do not have one, sign up for a new account.

      • A DigitalOcean Personal Access Token, which you can create via the DigitalOcean control panel. Instructions to do that can be found here: How to Generate a Personal Access Token.

      Downloading Files

      In this section, you will use Wget to customize your download experience. For example, you will learn to download a single file and multiple files, handle file downloads in unstable network conditions, and, in the case of a download interruption, resume a download.

      First, create a directory to save the files that you will download throughout this tutorial:

      • mkdir -p DigitalOcean-Wget-Tutorial/Downloads

      With the command above, you have created a directory named DigitalOcean-Wget-Tutorial, and inside of it, you created a subdirectory named Downloads. This directory and its subdirectory will be where you will store the files you download.

      Navigate to the DigitalOcean-Wget-Tutorial directory:

      • cd DigitalOcean-Wget-Tutorial

      You have successfully created the directory where you will store the files you download.

      Downloading a file

      In order to download a file using Wget, type wget followed by the URL of the file that you wish to download. Wget will download the file in the given URL and save it in the current directory.

      Let’s download a minified version of jQuery using the following command:

      • wget https://code.jquery.com/jquery-3.6.0.min.js

      Don’t worry if you don’t know what jQuery is – you could have downloaded any file available on the internet. All you need to know is that you successfully used Wget to download a file from the internet.

      The output will look similar to this:

      Output

      --2021-07-21 16:25:11-- https://code.jquery.com/jquery-3.6.0.min.js Resolving code.jquery.com (code.jquery.com)... 69.16.175.10, 69.16.175.42, 2001:4de0:ac18::1:a:1a, ... Connecting to code.jquery.com (code.jquery.com)|69.16.175.10|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 89501 (87K) [application/javascript] Saving to: ‘jquery-3.6.0.min.js’ jquery-3.6.0.min.js 100%[===================>] 87.40K 114KB/s in 0.8s 2021-07-21 16:25:13 (114 KB/s) - ‘jquery-3.6.0.min.js’ saved [89501/89501]

      According to the output above, you have successfully downloaded and saved a file named jquery-3.6.0.min.js to your current directory.

      You can check the contents of the current directory using the following command:

      The output will look similar to this:

      Output

      Downloads jquery-3.6.0.min.js

      Specifying the filename for the downloaded file

      When downloading a file, Wget defaults to storing it using the name that the file has on the server. You can change that by using the -O option to specify a new name.

      Download the jQuery file you downloaded previously, but this time save it under a different name:

      • wget -O jquery.min.js https://code.jquery.com/jquery-3.6.0.min.js

      With the command above, you set the jQuery file to be saved as jquery.min.js instead of jquery-3.6.0.min.js

      The output will look similar to this:

      Output

      --2021-07-21 16:27:01-- https://code.jquery.com/jquery-3.6.0.min.js Resolving code.jquery.com (code.jquery.com)... 69.16.175.10, 69.16.175.42, 2001:4de0:ac18::1:a:2b, ... Connecting to code.jquery.com (code.jquery.com)|69.16.175.10|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 89501 (87K) [application/javascript] Saving to: ‘jquery.min.js’ jquery.min.js 100%[==================================>] 87.40K 194KB/s in 0.4s 2021-07-21 16:27:03 (194 KB/s) - ‘jquery.min.js’ saved [89501/89501]

      According to the output above, you have successfully downloaded the jQuery file and saved it as jquery.min.js.

      You can use the ls command to list the contents of your current directory, and you will see the jquery.min.js file there:

      The output will look similar to this:

      Output

      Downloads jquery-3.6.0.min.js jquery.min.js

      So far, you have used wget to download files to the current directory. Next, you will download to a specific directory.

      Downloading a file to a specific directory

      When downloading a file, Wget stores it in the current directory by default. You can change that by using the -P option to specify the name of the directory where you want to save the file.

      Download the jQuery file you downloaded previously, but this time save it in the Downloads subdirectory.

      • wget -P Downloads/ https://code.jquery.com/jquery-3.6.0.min.js

      The output will look similar to this:

      Output

      --2021-07-21 16:28:50-- https://code.jquery.com/jquery-3.6.0.min.js Resolving code.jquery.com (code.jquery.com)... 69.16.175.42, 69.16.175.10, 2001:4de0:ac18::1:a:2b, ... Connecting to code.jquery.com (code.jquery.com)|69.16.175.42|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 89501 (87K) [application/javascript] Saving to: ‘Downloads/jquery-3.6.0.min.js’ jquery-3.6.0.min.js 100%[==================================>] 87.40K 43.6KB/s in 2.0s 2021-07-21 16:28:53 (43.6 KB/s) - ‘Downloads/jquery-3.6.0.min.js’ saved [89501/89501]

      Notice the last line where it says that the jquery-3.6.0.min.js file was saved in the Downloads directory.

      If you use the ls Downloads command to list the contents of the Downloads directory, you will see the jQuery file there:

      Run the ls command:

      The output will look similar to this:

      Output

      jquery-3.6.0.min.js

      Turning Wget’s output off

      By default, Wget outputs a lot of information to the terminal when you download a file. You can use the -q option to turn off all output.

      Download the jQuery file, but this time without showing any output:

      • wget -q https://code.jquery.com/jquery-3.6.0.min.js

      You won’t see any output, but if you use the ls command to list the contents of the current directory you will find a file named jquery-3.6.0.min.js.1:

      The output will look similar to this:

      Output

      Downloads jquery-3.6.0.min.js jquery-3.6.0.min.js.1 jquery.min.js

      Before saving a file, Wget checks whether the file exists in the desired directory. If it does, Wget adds a number to the end of the file. If you ran the command above one more time, Wget would create a file named jquery-3.6.0.min.js.2. This number increases every time you download a file to a directory that already has a file with the same name.

      You have successfully turned off Wget’s output, but now you can’t monitor the download progress. Let’s look at how to show the download progress bar.

      Showing the download progress bar

      Wget lets you show the download progress bar but hide any other output by using the -q option alongside the --show-progress option.

      Download the jQuery file, but this time only show the download progress bar:

      • wget -q --show-progress https://code.jquery.com/jquery-3.6.0.min.js

      The output will look similar to this:

      Output

      jquery-3.6.0.min.js.2 100%[================================================>] 87.40K 207KB/s in 0.4s

      Use the ls command to check the contents of the current directory and you will find the file you have just downloaded with the name jquery-3.6.0.min.js.2

      From this point forward you will be using the -q and --show-progress options in most of the subsequent Wget commands.

      So far you have only downloaded a single file. Next, you will download multiple files.

      Downloading multiple files

      In order to download multiples files using Wget, you need to create a .txt file and insert the URLs of the files you wish to download. After inserting the URLs inside the file, use the wget command with the -i option followed by the name of the .txt file containing the URLs.

      Create a file named images.txt:

      In images.txt, add the following URLs:

      images.txt

      https://cdn.pixabay.com/photo/2016/12/13/05/15/puppy-1903313__340.jpg
      https://cdn.pixabay.com/photo/2016/01/05/17/51/maltese-1123016__340.jpg
      https://cdn.pixabay.com/photo/2020/06/30/22/34/dog-5357794__340.jpg
      

      The URLs link to three random images of dogs found on Pixabay. After you have added the URLs, save and close the file.

      Now you will use the -i option alongside the -P,-q and --show-progress options that you learned earlier to download all three images to the Downloads directory:

      • wget -i images.txt -P Downloads/ -q --show-progress

      The output will look similar to this:

      Output

      puppy-1903313__340.jp 100%[=========================>] 26.44K 93.0KB/s in 0.3s maltese-1123016__340. 100%[=========================>] 50.81K --.-KB/s in 0.06s dog-5357794__340.jpg 100%[=========================>] 30.59K --.-KB/s in 0.07s

      If you use the ls Downloads command to list the contents of the Downloads directory, you will find the names of the three images you have just downloaded:

      The output will look similar to this:

      Output

      dog-5357794__340.jpg jquery-3.6.0.min.js maltese-1123016__340.jpg puppy-1903313__340.jpg

      Limiting download speed

      So far, you have download files with the maximum available download speed. However, you might want to limit the download speed to preserve resources for other tasks. You can limit the download speed by using the --limit-rate option followed by the maximum speed allowed in kiloBits per second and the letter k.

      Download the first image in the images.txt file with a speed of 15 kB/S to the Downloads directory:

      • wget --limit-rate 15k -P Downloads/ -q --show-progress https://cdn.pixabay.com/photo/2016/12/13/05/15/puppy-1903313__340.jpg

      The output will look similar to this:

      Output

      puppy-1903313__340.jpg.1 100%[====================================================>] 26.44K 16.1KB/s in 1.6s

      If you use the ls Downloads command to check the contents of the Downloads directory, you will see the file you have just downloaded with the name puppy-1903313__340.jpg.1.

      When downloading a file that already exists, Wget creates a new file instead of overwriting the existing file. Next, you will overwrite a downloaded file.

      Overwriting a downloaded file

      You can overwrite a file you have downloaded by using the -O option alongside the name of the file. In the code below, you will first download the second image listed in the images.txt file to the current directory and then you will overwrite it.

      First, download the second image to the current directory and set the name to image2.jpg:

      • wget -O image2.jpg -q --show-progress https://cdn.pixabay.com/photo/2016/12/13/05/15/puppy-1903313__340.jpg

      The output will look similar to this::

      Output

      image2.jpg 100%[====================================================>] 26.44K --.-KB/s in 0.04s

      If you use the ls command to check the contents of the current directory, you will see the file you have just downloaded with the name image2.jpg.

      If you wish to overwrite this image2.jpg file, you can run the same command you ran earlier :

      • wget -O image2.jpg -q --show-progress https://cdn.pixabay.com/photo/2016/12/13/05/15/puppy-1903313__340.jpg

      You can run the command above as many times as you like and Wget will download the file and overwrite the existing one. If you run the command above without the -O option, Wget will create a new file each time you run it.

      Resuming a download

      Thus far, you have successfully downloaded multiple files without interruption. However, if the download was interrupted, you can resume it by using the -c option.

      Run the following command to download a random image of a dog found on Pixabay. Note that in the command, you have set the maximum speed to 1 KB/S. Before the image finishes downloading, press Ctrl+C to cancel the download:

      • wget --limit-rate 1k -q --show-progress https://cdn.pixabay.com/photo/2018/03/07/19/51/grass-3206938__340.jpg

      To resume the download, pass the -c option. Note that this will only work if you run this command in the same directory as the incomplete file:

      • wget -c --limit-rate 1k -q --show-progress https://cdn.pixabay.com/photo/2018/03/07/19/51/grass-3206938__340.jpg

      Up until now, you have only downloaded files in the foreground. Next, you will download files in the background.

      Downloading in the background

      You can download files in the background by using the -b option.

      Run the command below to download a random image of a dog from Pixabay in the background:

      • wget -b https://cdn.pixabay.com/photo/2018/03/07/19/51/grass-3206938__340.jpg

      When you download files in the background, Wget creates a file named wget-log in the current directory and redirects all output to this file. If you wish to watch the status of the download, you can use the following command:

      The output will look similar to this:

      Output

      Resolving cdn.pixabay.com (cdn.pixabay.com)... 104.18.20.183, 104.18.21.183, 2606:4700::6812:14b7, ... Connecting to cdn.pixabay.com (cdn.pixabay.com)|104.18.20.183|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 33520 (33K) [image/jpeg] Saving to: ‘grass-3206938__340.jpg’ 0K .......... .......... .......... .. 100% 338K=0.1s 2021-07-20 23:49:52 (338 KB/s) - ‘grass-3206938__340.jpg’ saved [33520/33520]

      Setting a timeout

      Until this point, we have assumed that the server that you are trying to download files from is working properly. However, let’s assume that the server is not working properly. You can use Wget to first limit the amount of time that you wait for the server to respond and then limit the number of times that Wget tries to reach the server.

      If you wish to download a file but you are unsure if the server is working properly, you can set a timeout by using the -T option followed by the time in seconds.

      In the following command, you are setting the timeout to 5 seconds:

      • wget -T 5 -q --show-progress https://cdn.pixabay.com/photo/2016/12/13/05/15/puppy-1903313__340.jpg

      Setting maximum number of tries

      You can also set how many times Wget attempts to download a file after being interrupted by passing the --tries option followed by the number of tries.

      By running the command below, you are limiting the number of tries to 3:

      • wget --tries=3 -q --show-progress https://cdn.pixabay.com/photo/2018/03/07/19/51/grass-3206938__340.jpg

      If you would like to try indefinitely you can pass inf alongside the --tries option:

      • wget --tries=inf -q --show-progress https://cdn.pixabay.com/photo/2018/03/07/19/51/grass-3206938__340.jpg

      In this section, you used Wget to download a single file and multiple files, resume downloads, and handle network issues. In the next section, you will learn to interact with REST API endpoints.

      Interacting with REST APIs

      In this section, you will use Wget to interact with REST APIs without having to install an external program. You will learn the syntax to send the most commonly used HTTP methods: GET, POST, PUT, and DELETE.

      We are going to use JSONPlaceholder as the mock REST API. JSONPlaceholder is a free online REST API that you can use for fake data. (The requests you send to it won’t affect any databases and the data won’t be saved.)

      Sending GET requests

      Wget lets you send GET requests by running a command that looks like the following:

      In the command above, the - after the -O option means standard output, so Wget will send the output of the URL to the terminal instead of sending it to a file as you did in the previous section. GET is the default HTTP method that Wget uses.

      Run the following command in the terminal window:

      • wget -O- https://jsonplaceholder.typicode.com/posts?_limit=2

      In the command above, you used wget to send a GET request to JSON Placeholder in order to retrieve two posts from the REST API.

      The output will look similar to this:

      Output

      --2021-07-21 16:52:51-- https://jsonplaceholder.typicode.com/posts?_limit=2 Resolving jsonplaceholder.typicode.com (jsonplaceholder.typicode.com)... 104.21.10.8, 172.67.189.217, 2606:4700:3032::6815:a08, ... Connecting to jsonplaceholder.typicode.com (jsonplaceholder.typicode.com)|104.21.10.8|:443... connected. HTTP request sent, awaiting response... 200 OK' Length: 600 [application/json] Saving to: ‘STDOUT’ - 0%[ ] 0 --.-KB/s [ { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipitnsuscipit recusandae consequuntur expedita et cumnreprehenderit molestiae ut ut quas totamnnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitaensequi sint nihil reprehenderit dolor beatae ea dolores nequenfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendisnqui aperiam non debitis possimus qui neque nisi nulla" } - 100%[==================================>] 600 --.-KB/s in 0s 2021-07-21 16:52:53 (4.12 MB/s) - written to stdout [600/600]

      Notice the line where it says HTTP request sent, awaiting response... 200 OK, which means that you have successfully sent a GET request to JSONPlaceholder.

      If that is too much output you can use the -q option that you learned in the previous section to restrict the output to the results of the GET request:

      • wget -O- -q https://jsonplaceholder.typicode.com/posts?_limit=2

      The output will look similar to this:

      Output

      [ { "userId": 1, "id": 1, "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", "body": "quia et suscipitnsuscipit recusandae consequuntur expedita et cumnreprehenderit molestiae ut ut quas totamnnostrum rerum est autem sunt rem eveniet architecto" }, { "userId": 1, "id": 2, "title": "qui est esse", "body": "est rerum tempore vitaensequi sint nihil reprehenderit dolor beatae ea dolores nequenfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendisnqui aperiam non debitis possimus qui neque nisi nulla" } ]

      Sending POST requests

      Wget lets you send POST requests by running a command that looks like the following:

      • wget --method==[post] -O- --body-data=[ body in json format ] --header=[ String ] [ URL ]

      Run the following command:

      • wget --method=post -O- -q --body-data="{"title": "Wget POST","body": "Wget POST example body","userId":1}" --header=Content-Type:application/json https://jsonplaceholder.typicode.com/posts

      In the command above, you used wget to send a POST request to JSON Placeholder to create a new post. You set the method to post, the Header to Content-Type:application/json and sent the following request body to it :{"title": "Wget POST","body": "Wget POST example body","userId":1}.

      The output will look similar to this:

      Output

      { "title": "Wget POST", "body": "Wget POST example body", "userId": 1, "id": 101 }

      Sending PUT requests

      Wget lets you send PUT requests by running a command that looks like the following:

      • wget --method==[put] -O- --body-data=[ body in json format ] --header=[ String ] [ URL ]

      Run the following command:

      • wget --method=put -O- -q --body-data="{"title": "Wget PUT", "body": "Wget PUT example body", "userId": 1, "id":1}" --header=Content-Type:application/json https://jsonplaceholder.typicode.com/posts/1

      In the command above you used wget to send a PUT request to JSON Placeholder to edit the first post in this REST API. You set the method to put, the Header to Content-Type:application/json and sent the following request body to it :{"title": "Wget PUT", "body": "Wget PUT example body", "userId": 1, "id":1} .

      The output will look similar to this:

      Output

      { "body": "Wget PUT example body", "title": "Wget PUT", "userId": 1, "id": 1 }

      Sending DELETE requests

      Wget lets you send DELETE requests by running a command that looks like the following:

      • wget --method==[delete] -O- [ URL ]

      Run the following command:

      • wget --method=delete -O- -q --header=Content-Type:application/json https://jsonplaceholder.typicode.com/posts/1

      In the command above you used wget to send a DELETE request to JSON Placeholder to delete the first post in this REST API. You set the method to delete, and set the post you want to delete to 1 in the URL.

      The output will look similar to this:

      Output

      {}

      In this section, you learned how to use Wget to send GET, POST, PUT and DELETE requests with only one header field. In the next section, you will learn how to send multiple header fields in order to create and manage a Droplet in your DigitalOcean account.

      Creating and Managing a DigitalOcean Droplet

      In this section, you will apply what you learned in the previous section and use Wget to create and manage a Droplet in your DigitalOcean account. But before you do that, you will learn how to send multiple headers fields in a HTTP method.

      The syntax for a command to send multiple headers looks like this:

      • wget --header=[ first header ] --header=[ second header] --header=[ N header] [ URL ]

      You can have as many headers fields as you like by repeating the --header option as many times as you need.

      To create a Droplet or interact with any other resource in the DigitalOcean API, you will need to send two request headers:

      Content-Type: application/json
      Authorization: Bearer your_personal_access_token
      

      You already saw the first header in the previous section. The second header is what lets you authenticate your account. It has the String named Bearer followed by your DigitalOcean account Personal Access Token.

      Run the following command, replacing your_personal_access_token with your DigitalOcean Personal Access Token:

      • wget --method=post -O- -q --header="Content-Type: application/json" --header="Authorization: Bearer your_personal_access_token" --body-data="{"name":"Wget-example","region":"nyc1","size":"s-1vcpu-1gb","image":"ubuntu-20-04-x64","tags": ["Wget-tutorial"]}" https://api.digitalocean.com/v2/droplets

      With the command above, you have created an ubuntu-20-04-x64 Droplet in the nyc1 region named Wget-example with 1vcpu and 1gb of memory, and you have set the tag to Wget-tutorial. For more information about the attributes in the body-data field, see the DigitalOcean API documentation.

      The output will look similar to this:

      Output

      {"droplet":{"id":237171073,"name":"Wget-example","memory":1024,"vcpus":1,"disk":25,"locked":false,"status":"new","kernel":null,"created_at":"2021-03-16T12:38:59Z","features":[],"backup_ids":[],"next_backup_window":null,"snapshot_ids":[],"image":{"id":72067660,"name":"20.04 (LTS) x64","distribution":"Ubuntu","slug":"ubuntu-20-04-x64","public":true,"regions":["nyc3","nyc1","sfo1","nyc2","ams2","sgp1","lon1","ams3","fra1","tor1","sfo2","blr1","sfo3"],"created_at":"2020-10-20T16:34:30Z","min_disk_size":15,"type":"base","size_gigabytes":0.52,"description":"Ubuntu 20.04 x86","tags":[],"status":"available"},"volume_ids":[],"size":{"slug":"s-1vcpu-1gb","memory":1024,"vcpus":1,"disk":25,"transfer":1.0,"price_monthly":5.0,"price_hourly":0.00744,"regions":["ams2","ams3","blr1","fra1","lon1","nyc1","nyc2","nyc3","sfo1","sfo3","sgp1","tor1"],"available":true,"description":"Basic"},"size_slug":"s-1vcpu-1gb","networks":{"v4":[],"v6":[]},"region":{"name":"New York 1","slug":"nyc1","features":["backups","ipv6","metadata","install_agent","storage","image_transfer"],"available":true,"sizes":["s-1vcpu-1gb","s-1vcpu-1gb-intel","s-1vcpu-2gb","s-1vcpu-2gb-intel","s-2vcpu-2gb","s-2vcpu-2gb-intel","s-2vcpu-4gb","s-2vcpu-4gb-intel","s-4vcpu-8gb","c-2","c2-2vcpu-4gb","s-4vcpu-8gb-intel","g-2vcpu-8gb","gd-2vcpu-8gb","s-8vcpu-16gb","m-2vcpu-16gb","c-4","c2-4vcpu-8gb","s-8vcpu-16gb-intel","m3-2vcpu-16gb","g-4vcpu-16gb","so-2vcpu-16gb","m6-2vcpu-16gb","gd-4vcpu-16gb","so1_5-2vcpu-16gb","m-4vcpu-32gb","c-8","c2-8vcpu-16gb","m3-4vcpu-32gb","g-8vcpu-32gb","so-4vcpu-32gb","m6-4vcpu-32gb","gd-8vcpu-32gb","so1_5-4vcpu-32gb","m-8vcpu-64gb","c-16","c2-16vcpu-32gb","m3-8vcpu-64gb","g-16vcpu-64gb","so-8vcpu-64gb","m6-8vcpu-64gb","gd-16vcpu-64gb","so1_5-8vcpu-64gb","m-16vcpu-128gb","c-32","c2-32vcpu-64gb","m3-16vcpu-128gb","m-24vcpu-192gb","g-32vcpu-128gb","so-16vcpu-128gb","m6-16vcpu-128gb","gd-32vcpu-128gb","m3-24vcpu-192gb","g-40vcpu-160gb","so1_5-16vcpu-128gb","m-32vcpu-256gb","gd-40vcpu-160gb","so-24vcpu-192gb","m6-24vcpu-192gb","m3-32vcpu-256gb","so1_5-24vcpu-192gb"]},"tags":["Wget-tutorial"]},"links":{"actions":[{"id":1164336542,"rel":"create","href":"https://api.digitalocean.com/v2/actions/1164336542"}]}}

      If you see an output similar to the one above that means that you have successfully created a Droplet.

      Now let’s get a list of all the Droplets in your account that have the tag Wget-tutorial. Run the following command, replacing your_personal_access_token with your DigitalOcean Personal Access Token:

      • wget -O- -q --header="Content-Type: application/json" --header="Authorization: Bearer your_personal_access_token" https://api.digitalocean.com/v2/droplets?tag_name=Wget-tutorial

      You should see the name of the Droplet you have just created in the output:

      Output

      {"droplets":[{"id":237171073,"name":"Wget-example","memory":1024,"vcpus":1,"disk":25,"locked":false,"status":"active","kernel":null,"created_at":"2021-03-16T12:38:59Z","features":["private_networking"],"backup_ids":[],"next_backup_window":null,"snapshot_ids":[],"image":{"id":72067660,"name":"20.04 (LTS) x64","distribution":"Ubuntu","slug":"ubuntu-20-04-x64","public":true,"regions":["nyc3","nyc1","sfo1","nyc2","ams2","sgp1","lon1","ams3","fra1","tor1","sfo2","blr1","sfo3"],"created_at":"2020-10-20T16:34:30Z","min_disk_size":15,"type":"base","size_gigabytes":0.52,"description":"Ubuntu 20.04 x86","tags":[],"status":"available"},"volume_ids":[],"size":{"slug":"s-1vcpu-1gb","memory":1024,"vcpus":1,"disk":25,"transfer":1.0,"price_monthly":5.0,"price_hourly":0.00744,"regions":["ams2","ams3","blr1","fra1","lon1","nyc1","nyc2","nyc3","sfo1","sfo3","sgp1","tor1"],"available":true,"description":"Basic"},"size_slug":"s-1vcpu-1gb","networks":{"v4":[{"ip_address":"10.116.0.2","netmask":"255.255.240.0","gateway":"","type":"private"},{"ip_address":"204.48.20.197","netmask":"255.255.240.0","gateway":"204.48.16.1","type":"public"}],"v6":[]},"region":{"name":"New York 1","slug":"nyc1","features":["backups","ipv6","metadata","install_agent","storage","image_transfer"],"available":true,"sizes":["s-1vcpu-1gb","s-1vcpu-1gb-intel","s-1vcpu-2gb","s-1vcpu-2gb-intel","s-2vcpu-2gb","s-2vcpu-2gb-intel","s-2vcpu-4gb","s-2vcpu-4gb-intel","s-4vcpu-8gb","c-2","c2-2vcpu-4gb","s-4vcpu-8gb-intel","g-2vcpu-8gb","gd-2vcpu-8gb","s-8vcpu-16gb","m-2vcpu-16gb","c-4","c2-4vcpu-8gb","s-8vcpu-16gb-intel","m3-2vcpu-16gb","g-4vcpu-16gb","so-2vcpu-16gb","m6-2vcpu-16gb","gd-4vcpu-16gb","so1_5-2vcpu-16gb","m-4vcpu-32gb","c-8","c2-8vcpu-16gb","m3-4vcpu-32gb","g-8vcpu-32gb","so-4vcpu-32gb","m6-4vcpu-32gb","gd-8vcpu-32gb","so1_5-4vcpu-32gb","m-8vcpu-64gb","c-16","c2-16vcpu-32gb","m3-8vcpu-64gb","g-16vcpu-64gb","so-8vcpu-64gb","m6-8vcpu-64gb","gd-16vcpu-64gb","so1_5-8vcpu-64gb","m-16vcpu-128gb","c-32","c2-32vcpu-64gb","m3-16vcpu-128gb","m-24vcpu-192gb","g-32vcpu-128gb","so-16vcpu-128gb","m6-16vcpu-128gb","gd-32vcpu-128gb","m3-24vcpu-192gb","g-40vcpu-160gb","so1_5-16vcpu-128gb","m-32vcpu-256gb","gd-40vcpu-160gb","so-24vcpu-192gb","m6-24vcpu-192gb","m3-32vcpu-256gb","so1_5-24vcpu-192gb"]},"tags":["Wget-tutorial"],"vpc_uuid":"5ee0a168-39d1-4c60-a89c-0b47390f3f7e"}],"links":{},"meta":{"total":1}}

      Now let’s take the id of the Droplet you have created and use it to delete the Droplet. Run the following command, replacing your_personal_access_token with your DigitalOcean Personal Access Token and your_droplet_id with your Droplet id:

      • wget --method=delete -O- --header="Content-Type: application/json" --header="Authorization: Bearer your_personal_access_token" https://api.digitalocean.com/v2/droplets/your_droplet_id

      In the command above, you added your Droplet id to the URL to delete it. If you are seeing a 204 No Content in the output, that means that you succeeded in deleting the Droplet.

      In this section, you used Wget to send multiple headers. Then, you created and managed a Droplet in your DigitalOcean account.

      Conclusion

      In this tutorial, you used Wget to download files in stable and unstable network conditions and interact with REST API endpoints. You then used this knowledge to create and manage a Droplet in your DigitalOcean account. If you would like to learn more about Wget, visit this tool’s manual page. For more Linux command-line tutorials visit DigitalOcean community tutorials.



      Source link

      Sailing Through a Sea of CMS: Build and Extend APIs Faster With Strapi


      Video

      About the Talk

      The role of content management systems keeps evolving and in the self hosted utopia we’re approaching, it’s easy to get lost in a sea of micro services. I’ll be explaining what makes a good CMS, things to consider when picking one and why Strapi is a great pick for your business, taking the API-first approach and extending its usefulness to other parts of your business.

      Resources

      About the Presenter

      Daniel Madalitso Phiri is a retired podcaster and developer interested in developer tooling, and is currently a Developer Advocate at Strapi.



      Source link

      How To Call Web APIs with the useEffect Hook in React


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      In React development, web application programming interfaces (APIs) are an integral part of single-page application (SPA) designs. APIs are the primary way for applications to programmatically communicate with servers to provide users with real-time data and to save user changes. In React applications, you will use APIs to load user preferences, display user information, fetch configuration or security information, and save application state changes.

      In this tutorial, you’ll use the useEffect and useState Hooks to fetch and display information in a sample application, using JSON server as a local API for testing purposes. You’ll load information when a component first mounts and save customer inputs with an API. You’ll also refresh data when a user makes a change and learn how to ignore API requests when a component unmounts. By the end of this tutorial, you’ll be able to connect your React applications to a variety of APIs and you’ll be able to send and receive real-time data.

      Prerequisites

      Step 1 — Creating a Project and a Local API

      In this step, you’ll create a local REST API using JSON server, which you will use as a test data source. Later, you’ll build an application to display a grocery list and to add items to the list. JSON server will be your local API and will give you a live URL to make GET and POST requests. With a local API, you have the opportunity to prototype and test components while you or another team develops live APIs.

      By the end of this step, you’ll be able to create local mock APIs that you can connect to with your React applications.

      On many agile teams, front-end and API teams work on a problem in parallel. In order to develop a front-end application while a remote API is still in development, you can create a local version that you can use while waiting for a complete remote API.

      There are many ways to make a mock local API. You can create a simple server using Node or another language, but the quickest way is to use the JSON server Node package. This project creates a local REST API from a JSON file.

      To begin, install json-server:

      • npm install --save-dev json-server

      When the installation is finished, you’ll receive a success message:

      Output

      + json-server@0.16.1 added 108 packages from 40 contributors and audited 1723 packages in 14.505s 73 packages are looking for funding run `npm fund` for details found 0 vulnerabilities

      json-server creates an API based on a JavaScript object. The keys are the URL paths and the values are returned as a response. You store the JavaScript object locally and commit it to your source control.

      Open a file called db.json in the root of your application. This will be the JSON that stores the information you request from the API:

      Add an object with the key of list and an array of values with an id and a key of item. This will list the item for the grocery list. The key list will eventually give you a URL with an endpoint of /list:

      api-tutorial/db.json

      {
        "list": [
          { "id": 1, "item": "bread" },
          { "id": 2, "item": "grapes" }
        ]
      }
      

      In this snippet, you have hard-coded bread and grapes as a starting point for your grocery list.

      Save and close the file. To run the API server, you will use json-server from the command line with an argument point to the API configuration file. Add it as a script in your package.json.

      Open package.json:

      Then add a script to run the API. In addition, add a delay property. This will throttle the response, creating a delay between your API request and the API response. This will give you some insights into how the application will behave when waiting for a server response. Add a delay of 1500 milliseconds. Finally, run the API on port 3333 using the -p option so it won’t conflict with the create-react-app run script:

      api-tutorial/package.json

      {
        "name": "do-14-api",
        "version": "0.1.0",
        "private": true,
        "dependencies": {
          "@testing-library/jest-dom": "^4.2.4",
          "@testing-library/react": "^9.3.2",
          "@testing-library/user-event": "^7.1.2",
          "react": "^16.13.1",
          "react-dom": "^16.13.1",
          "react-scripts": "3.4.3"
        },
        "scripts": {
          "api": "json-server db.json -p 3333 --delay 1500",
          "start": "react-scripts start",
          "build": "react-scripts build",
          "test": "react-scripts test",
          "eject": "react-scripts eject"
        },
        "eslintConfig": {
          "extends": "react-app"
        },
        "browserslist": {
          "production": [
            ">0.2%",
            "not dead",
            "not op_mini all"
          ],
          "development": [
            "last 1 chrome version",
            "last 1 firefox version",
            "last 1 safari version"
          ]
        },
        "devDependencies": {
          "json-server": "^0.16.1"
        }
      }
      

      Save and close the file. In a new terminal or tab, start the API server with the following command:

      Keep this running during the rest of the tutorial.

      When you run the command, you will receive an output that lists the API resources:

      Output

      > json-server db.json -p 3333 {^_^}/ hi! Loading db.json Done Resources http://localhost:3333/list Home http://localhost:3333 Type s + enter at any time to create a snapshot of the database

      Open http://localhost:3333/list and you’ll find the live API:

      API results, 1

      When you open an endpoint in your browser, you are using the GET method. But json-server is not limited to the GET method. You can perform many other REST methods as well. For example, you can POST new items. In a new terminal window or tab, use curl to POST a new item with a type of application/json:

      • curl -d '{"item":"rice"}' -H 'Content-Type: application/json' -X POST http://localhost:3333/list

      Note that you must stringify the content before you send it. After running the curl command, you’ll receive a success message:

      Output

      { "item": "rice", "id": 3 }

      If you refresh the browser, the new item will appear:

      Updated content, 2

      The POST request will also update the db.json file. Be mindful of the changes, since there are no barriers to accidentally saving unstructured or unhelpful content as you work on your application. Be sure to check any changes before committing into version control.

      In this step, you created a local API. You learned how to create a static file with default values and how to fetch or update those values using RESTful actions such as GET and POST. In the next step, you’ll create services to fetch data from the API and to display in your application.

      Step 2 — Fetching Data from an API with useEffect

      In this step, you’ll fetch a list of groceries using the useEffect Hook. You’ll create a service to consume APIs in separate directories and call that service in your React components. After you call the service, you’ll save the data with the useState Hook and display the results in your component.

      By the end of this step, you’ll be able to call web APIs using the Fetch method and the useEffect Hook. You’ll also be able to save and display the results.

      Now that you have a working API, you need a service to fetch the data and components to display the information. Start by creating a service. You can fetch data directly inside any React component, but your projects will be easier to browse and update if you keep your data retrieval functions separate from your display components. This will allow you to reuse methods across components, mock in tests, and update URLs when endpoints change.

      Create a directory called services inside the src directory:

      Then open a file called list.js in your text editor:

      • nano src/services/list.js

      You’ll use this file for any actions on the /list endpoint. Add a function to retrieve the data using the fetch function:

      api-tutorial/src/services/list

      export function getList() {
        return fetch('http://localhost:3333/list')
          .then(data => data.json())
      }
      

      The only goal of this function is to access the data and to convert the response into JSON using the data.json() method. GET is the default action, so you don’t need any other parameters.

      In addition to fetch, there are other popular libraries such as Axios that can give you an intuitive interface and will allow you to add default headers or perform other actions on the service. But fetch will work for most requests.

      Save and close the file. Next, open App.css and add some minimal styling:

      • nano src/components/App/App.css

      Add a class of wrapper with a small amount of padding by replacing the CSS with the following:

      api-tutorial/src/components/App/App.css

      .wrapper {
          padding: 15px;
      }
      

      Save and close the file. Now you need to add in code to retrieve the data and display it in your application.

      Open App.js:

      • nano src/components/App/App.js

      In functional components, you use the useEffect Hook to fetch data when the component loads or some information changes. For more information on the useEffect Hook, check out How To Handle Async Data Loading, Lazy Loading, and Code Splitting with React. You’ll also need to save the results with the useState Hook.

      Import useEffect and useState, then create a variable called list and a setter called setList to hold the data you fetch from the service using the useState Hook:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useState } from 'react';
      import './App.css';
      
      function App() {
        const [list, setList] = useState([]);
        return(
          <>
          </>
        )
      }
      
      export default App;
      

      Next, import the service, then call the service inside your useEffect Hook. Update the list with setList if the component is mounted. To understand why you should check if the component is mounted before setting the data, see Step 2 — Preventing Errors on Unmounted Components in How To Handle Async Data Loading, Lazy Loading, and Code Splitting with React.

      Currently you are only running the effect once when the page loads, so the dependency array will be empty. In the next step, you’ll trigger the effect based on different page actions to ensure that you always have the most up-to-date information.

      Add the following highlighted code:

      api-tutorial/src/components/App/App.js

      
      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList } from '../../services/list';
      
      function App() {
        const [list, setList] = useState([]);
      
        useEffect(() => {
         let mounted = true;
         getList()
           .then(items => {
             if(mounted) {
               setList(items)
             }
           })
         return () => mounted = false;
       }, [])
      
        return(
          <>
          </>
        )
      }
      
      export default App;
      

      Finally, loop over the items with .map and display them in a list:

      api-tutorial/src/components/App/App

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList } from '../../services/list';
      
      function App() {
        const [list, setList] = useState([]);
      
        useEffect(() => {
          let mounted = true;
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [])
      
        return(
          <div className="wrapper">
           <h1>My Grocery List</h1>
           <ul>
             {list.map(item => <li key={item.item}>{item.item}</li>)}
           </ul>
         </div>
        )
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will refresh and you’ll find a list of items:

      List Items, 3

      In this step, you set up a service to retrieve data from an API. You learned how to call the service using the useEffect Hook and how to set the data on the page. You also displayed the data inside your JSX.

      In the next step, you’ll submit data to the API using POST and use the response to alert your users that an actions was successful.

      Step 3 — Sending Data to an API

      In this step, you’ll send data back to an API using the Fetch API and the POST method. You’ll create a component that will use a web form to send the data with the onSubmit event handler and will display a success message when the action is complete.

      By the end of this step, you’ll be able to send information to an API and you’ll be able to alert the user when the request resolves.

      Sending Data to a Service

      You have an application that will display a list of grocery items, but it’s not a very useful grocery app unless you can save content as well. You need to create a service that will POST a new item to the API.

      Open up src/services/list.js:

      • nano src/services/list.js

      Inside the file, add a function that will take an item as an argument and will send the data using the POST method to the API. As before, you can use the Fetch API. This time, you’ll need more information. Add an object of options as the second argument. Include the method—POST—along with headers to set the Content-Type to application/json. Finally, send the new object in the body. Be sure to convert the object to a string using JSON.stringify.

      When you receive a response, convert the value to JSON:

      tutorial/src/services/list.js

      export function getList() {
        return fetch('http://localhost:3333/list')
          .then(data => data.json())
      }
      
      export function setItem(item) {
       return fetch('http://localhost:3333/list', {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json'
         },
         body: JSON.stringify({ item })
       })
         .then(data => data.json())
      }
      

      Save and close the file.

      Note: In production applications, you’ll need to add error handling and checking. For example, if you misspelled the endpoint, you’d still receive a 404 response and the data.json() method would return an empty object. To solve the issue, instead of converting the response to JSON, you could check the data.ok property. If it is falsy, you could throw an error and then use the .catch method in your component to display a failure message to the users.

      Now that you have created a service, you need to consume the service inside your component.

      Open App.js:

      • nano src/components/App/App.js

      Add a form element surrounding an input and a submit button:

      api-tutorial/src/components/App/App.js

      
      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList } from '../../services/list';
      
      function App() {
        const [list, setList] = useState([]);
      
        useEffect(() => {
          let mounted = true;
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [])
      
        return(
          <div className="wrapper">
            <h1>My Grocery List</h1>
            <ul>
              {list.map(item => <li key={item.item}>{item.item}</li>)}
            </ul>
            <form>
             <label>
               <p>New Item</p>
               <input type="text" />
             </label>
             <button type="submit">Submit</button>
           </form>
          </div>
        )
      }
      
      export default App;
      

      Be sure to surround the input with a label so that the form is accessible by a screen reader. It’s also a good practice to add a type="submit" to the button so that it’s clear the role is to submit the form.

      Save the file. When you do, the browser will refresh and you’ll find your form.

      Grocery List Form

      Next, convert the input to a controlled component. You’ll need a controlled component so that you can clear the field after the user successfully submits a new list item.

      First, create a new state handler to hold and set the input information using the useState Hook:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList } from '../../services/list';
      
      function App() {
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
      
        useEffect(() => {
          let mounted = true;
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [])
      
        return(
          <div className="wrapper">
            <h1>My Grocery List</h1>
            <ul>
              {list.map(item => <li key={item.item}>{item.item}</li>)}
            </ul>
            <form>
              <label>
                <p>New Item</p>
                <input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
              </label>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      After creating the state handlers, set the value of the input to itemInput and update the value by passing the event.target.value to the setItemInput function using the onChange event handler.

      Now your users can fill out a form with new list items. Next you will connect the form to your service.

      Create a function called handleSubmit. handleSubmit will take an event as an argument and will call event.preventDefault() to stop the form from refreshing the browser.

      Import setItem from the service, then call setItem with the itemInput value inside the handleSubmit function. Connect handleSubmit to the form by passing it to the onSubmit event handler:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList, setItem } from '../../services/list';
      
      function App() {
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
      
        useEffect(() => {
          let mounted = true;
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [])
      
        const handleSubmit = (e) => {
          e.preventDefault();
          setItem(itemInput)
        };
      
        return(
          <div className="wrapper">
            <h1>My Grocery List</h1>
            <ul>
              {list.map(item => <li key={item.item}>{item.item}</li>)}
            </ul>
            <form onSubmit={handleSubmit}>
              <label>
                <p>New Item</p>
                <input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
              </label>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, you’ll be able to submit values. Notice that you’ll receive a successful response in the network tab. But the list doesn’t update and the input doesn’t clear.

      Submit successful, 5

      Showing a Success Message

      It’s always a good practice to give the user some indication that their action was successful. Otherwise a user may try and resubmit a value multiple times or may think their action failed and will leave the application.

      To do this, create a stateful variable and setter function with useState to indicate whether to show a user an alert message. If alert is true, display an <h2> tag with the message Submit Successful.

      When the setItem promise resolves, clear the input and set the alert message:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList, setItem } from '../../services/list';
      
      function App() {
        const [alert, setAlert] = useState(false);
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
      
        useEffect(() => {
          let mounted = true;
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [])
      
        const handleSubmit = (e) => {
          e.preventDefault();
          setItem(itemInput)
            .then(() => {
              setItemInput('');
              setAlert(true);
            })
        };
      
        return(
          <div className="wrapper">
            <h1>My Grocery List</h1>
            <ul>
              {list.map(item => <li key={item.item}>{item.item}</li>)}
            </ul>
            {alert && <h2> Submit Successful</h2>}
            <form onSubmit={handleSubmit}>
              <label>
                <p>New Item</p>
                <input type="text" onChange={event => setItemInput(event.target.value)} value={itemInput} />
              </label>
              <button type="submit">Submit</button>
            </form>
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, the page will refresh and you’ll see a success message after the API request resolves.

      Submit and message, 6

      There are many other optimizations you can add. For example, you may want to disable form inputs while there is an active request. You can learn more about disabling form elements in How To Build Forms in React.

      Now you have alerted a user that the result was successful, but the alert message doesn’t go away and the list doesn’t update. To fix this, start by hiding the alert. In this case, you’d want to hide the information after a brief period, such as one second. You can use the setTimeout function to call setAlert(false), but you’ll need to wrap it in useEffect to ensure that it does not run on every component render.

      Inside of App.js create a new effect and pass the alert to the array of triggers. This will cause the effect to run any time alert changes. Notice that this will run if alert changes from false to true, but it will also run when alert changes from true to false. Since you only want to hide the alert if it is displayed, add a condition inside the effect to only run setTimeout if alert is true:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList, setItem } from '../../services/list';
      
      function App() {
        const [alert, setAlert] = useState(false);
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
          ...
      
        useEffect(() => {
          if(alert) {
            setTimeout(() => {
              setAlert(false);
            }, 1000)
          }
        }, [alert])
      
        const handleSubmit = (e) => {
          e.preventDefault();
          setItem(itemInput)
            .then(() => {
              setItemInput('');
              setAlert(true);
            })
        };
      
        return(
          <div className="wrapper">
            ...
          </div>
        )
      }
      
      export default App;
      

      Run the setTimeout function after 1000 milliseconds to ensure the user has time to read the change.

      Save the file. Now you have an effect that will run whenever alert changes. If there is an active alert, it will start a timeout function that will close the alert after one second.

      Hide alert, 7

      Refreshing Fetched Data

      Now you need a way to refresh the stale list of data. To do this, you can add a new trigger to the useEffect Hook to rerun the getList request. To ensure you have the most relevant data, you need a trigger that will update anytime there is a change to the remote data. Fortunately, you can reuse the alert state to trigger another data refresh since you know it will run any time a user updates the data. As before, you have to plan for the fact that the effect will run every time alert changes including when the alert message disappears.

      This time, the effect also needs to fetch data when the page loads. Create a conditional that will exit the function before the data fetch if list.length is truthy—indicating you have already fetched the data—and alert is false—indicating you have already refreshed the data. Be sure to add alert and list to the array of triggers:

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList, setItem } from '../../services/list';
      
      function App() {
        const [alert, setAlert] = useState(false);
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
      
        useEffect(() => {
          let mounted = true;
          if(list.length && !alert) {
            return;
          }
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [alert, list])
      
        ...
      
        return(
          <div className="wrapper">
            ...
          </div>
        )
      }
      
      export default App;
      

      Save the file. When you do, the data will refresh after you submit a new item:

      List Refresh, 8

      In this case, alert is not directly related to the list state. However, it does occur at the same time as an event that will invalidate the old data, so you can use it to refresh the data.

      Preventing Updates on Unmounted Components

      The last problem you need to account for is making sure you do not set state on an unmounted component. You have a solution to the problem with let mounted = true in your effect to fetch data, but the solution will not work for handleSubmit, since it is not an effect. You can’t return a function to set the value to false when it is unmounted. Further, it would be inefficient to add the same check to every function.

      To solve this problem, you can make a shared variable that is used by multiple functions by lifting mounted out of the useEffect Hook and holding it on the level of the component. You’ll still use the function to set the value to false at the end of the useEffect.

      Inside App.js, declare mounted at the start of the function. Then check if the component is mounted before setting data in the other asynchronous functions. Make sure to remove the mounted declaration inside the useEffect function:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useState } from 'react';
      import './App.css';
      import { getList, setItem } from '../../services/list';
      
      function App() {
        const [alert, setAlert] = useState(false);
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
        let mounted = true;
      
        useEffect(() => {
          if(list.length && !alert) {
            return;
          }
          getList()
            .then(items => {
              if(mounted) {
                setList(items)
              }
            })
          return () => mounted = false;
        }, [alert, list])
      
        useEffect(() => {
          if(alert) {
            setTimeout(() => {
              if(mounted) {
                setAlert(false);
              }
            }, 1000)
          }
        }, [alert])
      
        const handleSubmit = (e) => {
          e.preventDefault();
          setItem(itemInput)
            .then(() => {
              if(mounted) {
                setItemInput('');
                setAlert(true);
              }
            })
        };
      
        return(
          <div className="wrapper">
            ...
          </div>
        )
      }
      
      export default App;
      

      When you make the change, you’ll receive an error in the terminal where you are running your React app:

      Error

      Assignments to the 'mounted' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect react-hooks/exhaustive-deps

      React is alerting you that variables are not stable. Whenever there is a re-render, it will recalculate the variable. Normally, this will ensure up-to-date information. In this case, you are relying on that variable to persist.

      The solution is another Hook called useRef. The useRef Hook will preserve a variable for the lifetime of the component. The only trick is to get the value you need to use the .current property.

      Inside App.js, convert mounted to a reference using the useRef Hook. Then convert each usage of mounted to mounted.current:

      api-tutorial/src/components/App/App.js

      import React, { useEffect, useRef, useState } from 'react';
      import './App.css';
      import { getList, setItem } from '../../services/list';
      
      function App() {
        const [alert, setAlert] = useState(false);
        const [itemInput, setItemInput] = useState('');
        const [list, setList] = useState([]);
        const mounted = useRef(true);
      
        useEffect(() => {
          mounted.current = true;
          if(list.length && !alert) {
            return;
          }
          getList()
            .then(items => {
              if(mounted.current) {
                setList(items)
              }
            })
          return () => mounted.current = false;
        }, [alert, list])
      
        useEffect(() => {
          if(alert) {
            setTimeout(() => {
              if(mounted.current) {
                setAlert(false);
              }
            }, 1000)
          }
        }, [alert])
      
        const handleSubmit = (e) => {
          e.preventDefault();
          setItem(itemInput)
            .then(() => {
              if(mounted.current) {
                setItemInput('');
                setAlert(true);
              }
            })
        };
      
        return(
          <div className="wrapper">
             ...
          </div>
        )
      }
      
      export default App;
      

      In addition, be cautious about setting the variable in the cleanup function for useEffect. The cleanup function will always run before the effect reruns. That means that the cleanup function () => mounted.current = false will run every time the alert or list change. To avoid any false results, be sure to update the mounted.current to true at the start of the effect. Then you can be sure it will only be set to false when the component is unmounted.

      Save and close the file. When the browser refreshes, you’ll be able to save new list items:

      Saving again, 9

      Note: It is a common problem to accidentally rerun an API multiple times. Every time a component is removed and then remounted, you will rerun all the original data fetching. To avoid this, consider a caching method for APIs that are particularly data heavy or slow. You can use anything from memoizing the service calls, to caching with service workers, to a custom Hook. There are a few popular custom Hooks for caching service calls, including useSWR and react query.

      No matter which approach you use, be sure to consider how you will invalidate the cache because there are times where you’ll want to fetch the newest data.

      In this step, you sent data to an API. You learned how to update the user when the data is submitted and how to trigger a refresh on your list data. You also avoided setting data on unmounted components by using the useRef Hook to store the status of the component so that it can be used by multiple services.

      Conclusion

      APIs give you the ability to connect to many useful services. They allow you to store and retrieve data even after a user closes their browser or stops using an application. With well organized code, you can isolate your services from your components so that your components can focus on rendering data without knowing where the data is originating. Web APIs extend your application far beyond the capabilities of a browser session or storage. They open your application to the whole world of web technologies.

      If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link