One place for hosting & domains

      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

      Understanding This, Bind, Call, and Apply in JavaScript


      The author selected the Open Internet/Free Speech Fund to receive a donation as part of the Write for DOnations program.

      The this keyword is a very important concept in JavaScript, and also a particularly confusing one to both new developers and those who have experience in other programming languages. In JavaScript, this is a reference to an object. The object that this refers to can vary, implicitly based on whether it is global, on an object, or in a constructor, and can also vary explicitly based on usage of the Function prototype methods bind, call, and apply.

      Although this is a bit of a complex topic, it is also one that appears as soon as you begin writing your first JavaScript programs. Whether you’re trying to access an element or event in the Document Object Model (DOM), building classes for writing in the object-oriented programming style, or using the properties and methods of regular objects, you will encounter this.

      In this article, you’ll learn what this refers to implicitly based on context, and you’ll learn how to use the bind, call, and apply methods to explicitly determine the value of this.

      Implicit Context

      There are four main contexts in which the value of this can be implicitly inferred:

      • the global context
      • as a method within an object
      • as a constructor on a function or class
      • as a DOM event handler

      Global

      In the global context, this refers to the global object. When you’re working in a browser, the global context is would be window. When you’re working in Node.js, the global context is global.

      Note: If you are not yet familiar with the concept of scope in JavaScript, please review Understanding Variables, Scope, and Hoisting in JavaScript.

      For the examples, you will practice the code in the browser’s Developer Tools console. Read How to Use the JavaScript Developer Console if you are not familiar with running JavaScript code in the browser.

      If you log the value of this without any other code, you will see what object this refers to.

      console.log(this)
      

      Output

      Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

      You can see that this is window, which is the global object of a browser.

      In Understanding Variables, Scope, and Hoisting in JavaScript, you learned that functions have their own context for variables. You might be tempted to think that this would follow the same rules inside a function, but it does not. A top-level function will still retain the this reference of the global object.

      You write a top-level function, or a function that is not associated with any object, like this:

      function printThis() {
        console.log(this)
      }
      
      printThis()
      

      Output

      Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}

      Even within a function, this still refers to the window, or global object.

      However, when using strict mode, the context of this within a function on the global context will be undefined.

      'use strict'
      
      function printThis() {
        console.log(this)
      }
      
      printThis()
      

      Output

      undefined

      Generally, it is safer to use strict mode to reduce the probability of this having an unexpected scope. Rarely will someone want to refer to the window object using this.

      For more information about strict mode and what changes it makes regarding mistakes and security, read the Strict mode documentation on MDN.

      An Object Method

      A method is a function on an object, or a task that an object can perform. A method uses this to refer to the properties of the object.

      const america = {
        name: 'The United States of America',
        yearFounded: 1776,
      
        describe() {
          console.log(`${this.name} was founded in ${this.yearFounded}.`)
        },
      }
      
      america.describe()
      

      Output

      "The United States of America was founded in 1776."

      In this example, this is the same as america.

      In a nested object, this refers to the current object scope of the method. In the following example, this.symbol within the details object refers to details.symbol.

      const america = {
        name: 'The United States of America',
        yearFounded: 1776,
        details: {
          symbol: 'eagle',
          currency: 'USD',
          printDetails() {
            console.log(`The symbol is the ${this.symbol} and the currency is ${this.currency}.`)
          },
        },
      }
      
      america.details.printDetails()
      

      Output

      "The symbol is the eagle and the currency is USD."

      Another way of thinking about it is that this refers to the object on the left side of the dot when calling a method.

      A Function Constructor

      When you use the new keyword, it creates an instance of a constructor function or class. Function constructors were the standard way to initialize a user-defined object before the class syntax was introduced in the ECMAScript 2015 update to JavaScript. In Understanding Classes in JavaScript, you will learn how to create a function constructor and an equivalent class constructor.

      function Country(name, yearFounded) {
        this.name = name
        this.yearFounded = yearFounded
      
        this.describe = function() {
          console.log(`${this.name} was founded in ${this.yearFounded}.`)
        }
      }
      
      const america = new Country('The United States of America', 1776)
      
      america.describe()
      

      Output

      "The United States of America was founded in 1776."

      In this context, this is now bound to the instance of Country, which is contained in the america constant.

      A Class Constructor

      A constructor on a class acts the same as a constructor on a function. Read more about the similarities and differences between function constructors and ES6 classes in Understanding Classes in JavaScript.

      class Country {
        constructor(name, yearFounded) {
          this.name = name
          this.yearFounded = yearFounded
        }
      
        describe() {
          console.log(`${this.name} was founded in ${this.yearFounded}.`)
        }
      }
      
      const america = new Country('The United States of America', 1776)
      
      america.describe()
      

      this in the describe method refers to the instance of Country, which is america.

      Output

      "The United States of America was founded in 1776."

      A DOM Event Handler

      In the browser, there is a special this context for event handlers. In an event handler called by addEventListener, this will refer to event.currentTarget. More often than not, developers will simply use event.target or event.currentTarget as needed to access elements in the DOM, but since the this reference changes in this context, it is important to know.

      In the following example, we’ll create a button, add text to it, and append it to the DOM. When we log the value of this within the event handler, it will print the target.

      const button = document.createElement('button')
      button.textContent = 'Click me'
      document.body.append(button)
      
      button.addEventListener('click', function(event) {
        console.log(this)
      })
      

      Output

      <button>Click me</button>

      Once you paste this into your browser, you will see a button appended to the page that says “Click me”. If you click the button, you will see <button>Click me</button> appear in your console, as clicking the button logs the element, which is the button itself. Therefore, as you can see, this refers to the targeted element, which is the element we added an event listener to.

      Explicit Context

      In all of the previous examples, the value of this was determined by its context—whether it is global, in an object, in a constructed function or class, or on a DOM event handler. However, using call, apply, or bind, you can explicitly determine what this should refer to.

      It is difficult to define exactly when to use call, apply, or bind, as it will depend on the context of your program. bind can be particularly helpful when you want to use events to access properties of one class within another class. For example, if you were to write a simple game, you might separate the user interface and I/O into one class, and the game logic and state into another. Since the game logic would need to access input, such as key press and click, you would want to bind the events to access the this value of the game logic class.

      The important part is to know how to determine what object this refers to, which you can do implicitly with what you learned in the previous sections, or explicitly with the three methods you will learn next.

      Call and Apply

      call and apply are very similar—they invoke a function with a specified this context, and optional arguments. The only difference between call and apply is that call requires the arguments to be passed in one-by-one, and apply takes the arguments as an array.

      In this example, we’ll create an object, and create a function that references this but has no this context.

      const book = {
        title: 'Brave New World',
        author: 'Aldous Huxley',
      }
      
      function summary() {
        console.log(`${this.title} was written by ${this.author}.`)
      }
      
      summary()
      

      Output

      "undefined was written by undefined"

      Since summary and book have no connection, invoking summary by itself will only print undefined, as it’s looking for those properties on the global object.

      Note: Attempting this in strict mode would result in Uncaught TypeError: Cannot read property 'title' of undefined, as this itself would be undefined.

      However, you can use call and apply to invoke the this context of book on the function.

      summary.call(book)
      // or:
      summary.apply(book)
      

      Output

      "Brave New World was written by Aldous Huxley."

      There is now a connection between book and summary when these methods are applied. Let’s confirm exactly what this is.

      function printThis() {
        console.log(this)
      }
      
      printThis.call(book)
      // or:
      whatIsThis.apply(book)
      

      Output

      {title: "Brave New World", author: "Aldous Huxley"}

      In this case, this actually becomes the object passed as an argument.

      This is how call and apply are the same, but there is one small difference. In addition to being able to pass the this context as the first argument, you can also pass additional arguments through.

      function longerSummary(genre, year) {
        console.log(
          `${this.title} was written by ${this.author}. It is a ${genre} novel written in ${year}.`
        )
      }
      

      With call each additional value you want to pass is sent as an additional argument.

      longerSummary.call(book, 'dystopian', 1932)
      

      Output

      "Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."

      If you try to send the exact same arguments with apply, this is what happens:

      longerSummary.apply(book, 'dystopian', 1932)
      

      Output

      Uncaught TypeError: CreateListFromArrayLike called on non-object at <anonymous>:1:15

      Instead, for apply, you have to pass all the arguments in an array.

      longerSummary.apply(book, ['dystopian', 1932])
      

      Output

      "Brave New World was written by Aldous Huxley. It is a dystopian novel written in 1932."

      The difference between passing the arguments individually or in an array is subtle, but it’s important to be aware of. It might be simpler and more convenient to use apply, as it would not require changing the function call if some parameter details changed.

      Bind

      Both call and apply are one-time use methods—if you call the method with the this context it will have it, but the original function will remain unchanged.

      Sometimes, you might need to use a method over and over with the this context of another object, and in that case you could use the bind method to create a brand new function with an explicitly bound this.

      const braveNewWorldSummary = summary.bind(book)
      
      braveNewWorldSummary()
      

      Output

      "Brave New World was written by Aldous Huxley"

      In this example, every time you call braveNewWorldSummary, it will always return the original this value bound to it. Attempting to bind a new this context to it will fail, so you can always trust a bound function to return the this value you expect.

      const braveNewWorldSummary = summary.bind(book)
      
      braveNewWorldSummary() // Brave New World was written by Aldous Huxley.
      
      const book2 = {
        title: '1984',
        author: 'George Orwell',
      }
      
      braveNewWorldSummary.bind(book2)
      
      braveNewWorldSummary() // Brave New World was written by Aldous Huxley.
      

      Although this example tries to bind braveNewWorldSummary once again, it retains the original this context from the first time it was bound.

      Arrow Functions

      Arrow functions do not have their own this binding. Instead, they go up to the next level of execution.

      const whoAmI = {
        name: 'Leslie Knope',
        regularFunction: function() {
          console.log(this.name)
        },
        arrowFunction: () => {
          console.log(this.name)
        },
      }
      
      whoAmI.regularFunction() // "Leslie Knope"
      whoAmI.arrowFunction() // undefined
      

      It can be useful to use the arrow function in cases where you really want this to refer to the outer context. For example, if you had an event listener inside of a class, you would probably want this to refer to some value in the class.

      In this example, you’ll create and append button to the DOM like before, but the class will have an event listener that will change the text value of the button when clicked.

      const button = document.createElement('button')
      button.textContent = 'Click me'
      document.body.append(button)
      
      class Display {
        constructor() {
          this.buttonText = 'New text'
      
          button.addEventListener('click', event => {
            event.target.textContent = this.buttonText
          })
        }
      }
      
      new Display()
      

      If you click the button, the text content will change to the value of buttonText. If you hadn’t used an arrow function here, this would be equal to event.currentTarget, and you wouldn’t be able to use it to access a value within the class without explicitly binding it. This tactic is often used on class methods in frameworks like React.

      Conclusion

      In this article, you learned about this in JavaScript, and the many different values it might have based on implicit runtime binding, and explicit binding through bind, call, and apply. You also learned about how the lack of this binding in arrow functions can be used to refer to a different context. With this knowledge, you should be able to determine the value of this in your programs.



      Source link

      How To Define and Call Functions in Go


      Introduction

      A function is a section of code that, once defined, can be reused. Functions are used to make your code easier to understand by breaking it into small, understandable tasks that can be used more than once throughout your program.

      Go ships with a powerful standard library that has many predefined functions. Ones that you are probably already familiar with from the fmt package are:

      • fmt.Println() which will print objects to standard out (most likely your terminal).
      • fmt.Printf() which will allow you to format your printed output.

      Function names include parentheses and may include parameters.

      In this tutorial, we’ll go over how to define your own functions to use in your coding projects.

      Defining a Function

      Let’s start with turning the classic “Hello, World!” program into a function.

      We’ll create a new text file in our text editor of choice, and call the program hello.go. Then, we’ll define the function.

      A function is defined by using the func keyword. This is then followed by a name of your choosing and a set of parentheses that hold any parameters the function will take (they can be empty). The lines of function code are enclosed in curly brackets {}.

      In this case, we’ll define a function named hello():

      hello.go

      func hello() {}
      

      This sets up the initial statement for creating a function.

      From here, we’ll add a second line to provide the instructions for what the function does. In this case, we’ll be printing Hello, World! to the console:

      hello.go

      func hello() {
          fmt.Println("Hello, World!")
      }
      

      Our function is now fully defined, but if we run the program at this point, nothing will happen since we didn’t call the function.

      So, inside of our main() function block, let’s call the function with hello():

      hello.go

      package main
      
      import "fmt"
      
      func main() {
          hello()
      }
      
      func hello() {
          fmt.Println("Hello, World!")
      }
      

      Now, let’s run the program:

      You’ll receive the following output:

      Output

      Hello, World!

      Notice that we also introduced a function called main(). The main() function is a special function that tells the compiler that this is where the program should start. For any program that you want to be executable (a program that can be run from the command line), you will need a main() function. The main() function must appear only once, be in the main() package, and receive and return no arguments. This allows for program execution in any Go program. As per the following example:

      main.go

      package main
      
      import "fmt"
      
      func main() {
          fmt.Println("this is the main section of the program")
      }
      

      Functions can be more complicated than the hello() function we defined. We can use for loops, conditional statements, and more within our function block.

      For example, the following function uses a conditional statement to check if the input for the name variable contains a vowel, then uses a for loop to iterate over the letters in the name string.

      names.go

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          names()
      }
      
      func names() {
          fmt.Println("Enter your name:")
      
          var name string
          fmt.Scanln(&name)
          // Check whether name has a vowel
          for _, v := range strings.ToLower(name) {
              if v == 'a' || v == 'e' || v == 'i' || v == 'o' || v == 'u' {
                  fmt.Println("Your name contains a vowel.")
                  return
              }
          }
          fmt.Println("Your name does not contain a vowel.")
      }
      

      The names() function we define here sets up a name variable with input, and then sets up a conditional statement within a for loop. This shows how code can be organized within a function definition. However, depending on what we intend with our program and how we want to set up our code, we may want to define the conditional statement and the for loop as two separate functions.

      Defining functions within a program makes our code modular and reusable so that we can call the same functions without rewriting them.

      Working with Parameters

      So far we have looked at functions with empty parentheses that do not take arguments, but we can define parameters in function definitions within their parentheses.

      A parameter is a named entity in a function definition, specifying an argument that the function can accept. In Go, you must specify the data type for each parameter.

      Let’s create a program that repeats a word a specified number of times. It will take a string parameter called word and an int parameter called reps for the number of times to repeat the word.

      repeat.go

      package main
      
      import "fmt"
      
      func main() {
          repeat("Sammy", 5)
      }
      
      func repeat(word string, reps int) {
          for i := 0; i < reps; i++ {
              fmt.Print(word)
          }
      }
      

      We passed the value Sammy in for the word parameter, and 5 for the reps parameter. These values correspond with each parameter in the order they were given. The repeat function has a for loop that will iterate the number of times specified by the reps parameter. For each iteration, the value of the word parameter is printed.

      Here is the output of the program:

      Output

      SammySammySammySammySammy

      If you have a set of parameters that are all the same value, you can omit specifying the type each time. Let’s create a small program that takes in parameters x, y, and z that are all int values. We’ll create a function that adds the parameters together in different configurations. The sums of these will be printed by the function. Then we’ll call the function and pass numbers into the function.

      add_numbers.go

      package main
      
      import "fmt"
      
      func main() {
          addNumbers(1, 2, 3)
      }
      
      func addNumbers(x, y, z int) {
          a := x + y
          b := x + z
          c := y + z
          fmt.Println(a, b, c)
      }
      

      When we created the function signature for addNumbers, we did not need to specify the type each time, but only at the end.

      We passed the number 1 in for the x parameter, 2 in for the y parameter, and 3 in for the z parameter. These values correspond with each parameter in the order they are given.

      The program is doing the following math based on the values we passed to the parameters:

      a = 1 + 2
      b = 1 + 3
      c = 2 + 3
      

      The function also prints a, b, and c, and based on this math we would expect a to be equal to 3, b to be 4, and c to be 5. Let’s run the program:

      Output

      3 4 5

      When we pass 1, 2, and 3 as parameters to the addNumbers() function, we receive the expected output.

      Parameters are arguments that are typically defined as variables within function definitions. They can be assigned values when you run the method, passing the arguments into the function.

      Returning a Value

      You can pass a parameter value into a function, and a function can also produce a value.

      A function can produce a value with the return statement, which will exit a function and optionally pass an expression back to the caller. The return data type must be specified as well.

      So far, we have used the fmt.Println() statement instead of the return statement in our functions. Let’s create a program that instead of printing will return a variable.

      In a new text file called double.go, we’ll create a program that doubles the parameter x and returns the variable y. We issue a call to print the result variable, which is formed by running the double() function with 3 passed into it:

      double.go

      package main
      
      import "fmt"
      
      func main() {
          result := double(3)
          fmt.Println(result)
      }
      
      func double(x int) int {
          y := x * 2
          return y
      }
      
      

      We can run the program and see the output:

      Output

      6

      The integer 6 is returned as output, which is what we would expect by multiplying 3 by 2.

      If a function specifies a return, you must provide a return as part of the code. If you do not, you will receive a compilation error.

      We can demonstrate this by commenting out the line with the return statement:

      double.go

      package main
      
      import "fmt"
      
      func main() {
          result := double(3)
          fmt.Println(result)
      }
      
      func double(x int) int {
          y := x * 2
          // return y
      }
      
      

      Now, let’s run the program again:

      Output

      ./double.go:13:1: missing return at end of function

      Without using the return statement here, the program cannot compile.

      Functions exit immediately when they hit a return statement, even if they are not at the end of the function:

      return_loop.go

      package main
      
      import "fmt"
      
      func main() {
          loopFive()
      }
      
      func loopFive() {
          for i := 0; i < 25; i++ {
              fmt.Print(i)
              if i == 5 {
                  // Stop function at i == 5
                  return
              }
          }
          fmt.Println("This line will not execute.")
      }
      

      Here we iterate through a for loop, and tell the loop to run 25 iterations. However, inside the for loop, we have a conditional if statement that checks to see if the value of i is equal to 5. If it is, we issue a return statement. Because we are in the loopFive function, any return at any point in the function will exit the function. As a result, we never get to the last line in this function to print the statement This line will not execute..

      Using the return statement within the for loop ends the function, so the line that is outside of the loop will not run. If, instead, we had used a break statement, only the loop would have exited at that time, and the last fmt.Println() line would run.

      The return statement exits a function, and may return a value if specified in the function signature.

      Returning Multiple Values

      More than one return value can be specified for a function. Let’s examine the repeat.go program and make it return two values. The first will be the repeated value and the second will be an error if the reps parameter is not a value greater than 0:

      repeat.go

      package main
      
      import "fmt"
      
      func main() {
          val, err := repeat("Sammy", -1)
          if err != nil {
              fmt.Println(err)
              return
          }
          fmt.Println(val)
      }
      
      func repeat(word string, reps int) (string, error) {
          if reps <= 0 {
              return "", fmt.Errorf("invalid value of %d provided for reps. value must be greater than 0.", reps)
          }
          var value string
          for i := 0; i < reps; i++ {
              value = value + word
          }
          return value, nil
      }
      

      The first thing the repeat function does is check to see if the reps argument is a valid value. Any value that is not greater than 0 will cause an error. Since we passed in the value of -1, this branch of code will execute. Notice that when we return from the function, we have to provide both the string and error return values. Because the provided arguments resulted in an error, we will pass back a blank string for the first return value, and the error for the second return value.

      In the main() function, we can receive both return values by declaring two new variables, value and err. Because there could be an error in the return, we want to check to see if we received an error before continuing on with our program. In this example, we did receive an error. We print out the error and return out of the main() function to exit the program.

      If there was not an error, we would print out the return value of the function.

      Note: It is considered best practice to only return two or three values. Additionally, you should always return all errors as the last return value from a function.

      Running the program will result in the following output:

      Output

      invalid value of -1 provided for reps. value must be greater than 0.

      In this section we reviewed how we can use the return statement to return multiple values from a function.

      Conclusion

      Functions are code blocks of instructions that perform actions within a program, helping to make our code reusable and modular.

      To learn more about how to make your code more modular, you can read our guide on How To Write Packages in Go.



      Source link