One place for hosting & domains

      How To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Writing CSS selectors most often involves setting a condition and locating the element in the HTML that fulfills that condition as true. You can do this broadly with an element selector to select high-level tags, but to gain more control over the selected elements, you can create specific identifiers in the HTML and apply them in the CSS.

      In this tutorial, you will use the id, class, and attribute selectors to scope styles to intentionally written HTML. You will begin by creating an HTML and a CSS file that you will edit throughout the tutorial. You will then add id attributes and use those id values to target styles. You will also add class attributes to elements and use those class values independently and together to create more specific and reusable styles. Lastly, you will use the attribute selector to create styles that match much more specific scenarios than an id or class selector alone can accomplish.

      Prerequisites

      Setting Up the Base HTML and CSS

      To start working with id, class, and attribute selectors, you will first set up the HTML and CSS code that you will work on through the rest of the tutorial. In this section, you will write out all the necessary HTML and some initial CSS styles that will handle layout and start the visual aesthetic.

      To begin, open index.html in your text editor. Then, add the following HTML to the file:

      index.html

      <!doctype html>
      <html>
        <head>
        </head>
        <body>
        </body>
      </html>
      

      Next, go to the <head> tag and add a <meta> tag to define the character set for the HTML file. Then add a <meta> tag defining how mobile devices should render the page, set the title of the page, and finally load the CSS file that you will make later with a <link> tag.

      These additions are highlighted in the following code block. You will encounter this highlighting method throughout the tutorial as code is added and changed:

      index.html

      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>About CSS Selectors</title>
          <link rel="stylesheet" href="https://www.digitalocean.com/community/tutorials/styles.css">
        </head>
        <body>
        </body>
      </html>
      

      After adding the <head> content, move to the <body> element where you will add content for a page talking about CSS selectors. Add the highlighted section from this code block to your index.html file in your text editor:

      index.html

      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Link and Buttons with State</title>
          <link rel="stylesheet" href="https://www.digitalocean.com/community/tutorials/styles.css">
        </head>
        <body>
          <header>
            <h1>Using CSS Selectors</h1>
          </header>
      
          <main>
            <section>
              <h2>Selecting HTML To Style</h2>
              <p>Selecting the right element and providing the right visual styles is the basis of <a href="https://do.co/community">writing CSS</a> code. Whenever you need to adjust how an element on a webpage looks, using selectors is key.</p>
              <p><a href="https://example.com">Read this to learn more about selectors</a></p>
            </section>
      
            <section>
              <h2>The Type Selector</h2>
              <p>The type selector finds elements on the page by tag name. This is a common way to style elements on the page. Type selectors are often referred to as element selectors.</p>
            </section>
      
            <section>
              <h2>Combining Selectors</h2>
              <p>A combinator selector is defined by a space character between selectors, with the HTML ancestry reading left to right. The right-most selector is the intended target. This can be as complex or as simple as necessary to <em>scope</em>, or provide sufficient specificity, to the intended element.</p>
            </section>
          </main>
        </body>
      </html>
      

      Save your changes to index.html and leave it open in your text editor. Open up your browser and load index.html to render the content of the page with the browser default styles. The following image shows these default styles with black serif text on a white background:

      Black serif text in various sizes with two blue, underlined links.

      Next, return to your text editor and create a file called styles.css. This is the file that you referenced in the <head> element in your index.html. In the styles.css file, add the following code:

      styles.css

      body {
        font-family: sans-serif;
        line-height: 1.5;
        color: #444;
      }
      

      This CSS uses a body type selector to set the default font-family for the page to the browser’s sans-serif font. Then it changes the spacing between lines of text to 1.5 times the font-size. Lastly, the color value changes the text to a dark grey, instead of the default black.

      Save these changes to styles.css and return to the browser to see how these changes will appear, as shown in the following image:

      Dark gray sans-serif text in various sizes with two blue links with underlines.

      You have now set up the starting points for your index.html and your styles.css. In the next section, you will add an id attribute to elements in the HTML and create id selectors in the CSS to apply styles to those elements.

      Using the ID Selector

      In this section, you will learn about the id attribute and its purpose, then add several id attributes to your HTML. Finally, you will use some of these id selectors to apply layout styles.

      The id attribute is a unique identifier that links together content on the page. For example, say there is an element midway down the content of a web page with an id attribute of my-id. By appending that id value with a pound or hash symbol (#) to the URL, such as http://example.com/index.html#my-id, the browser will scroll to that section of the page. Since this can be used as part of the URL and for other scripting purposes, each id value on a page must be unique.

      To begin working with the id attribute, open index.html in your text editor. First, locate the <header> element and give it an id attribute with the value of header. Next, move to the <main> element and give it an id attribute with a value of content. These id attributes provide a unique identifier for the two primary sections of the content. The highlighted additions in the following code block show the changes you will make to your index.html file:

      index.html

      ...
      <header id="header">
        ...
      </header>
      <main id="content">
        ...
      </main>
      ...
      

      Note that well-written HTML will only have one <header> element and one <main> element, so it may seem redundant to assign a unique ID to elements that are already unique. However, applying specific IDs like this can separate your styling from your HTML structure, allowing you to retain consistent styling attached to the IDs even if the underlying HTML changes.

      Be sure to save your changes to index.html before continuing.

      The id selector in CSS uses the same format as when referenced in a URL: a pound sign followed by the name of the id, such as #my-id. To begin using the id selector, open styles.css in your text editor. Then, add the two id attribute values from your index.html as the group combinator #header, #content. You will use this selector to set the content of the <header> and the <main> element uniformly in the middle of the page. The following code block has the selector block and the code necessary for this layout:

      styles.css

      body {
        font-family: sans-serif;
        line-height: 1.5;
        color: #444;
      }
      
      #header,
      #content {
        margin: 1em auto;
        width: 90%;
        max-width: 60ch;
      }
      

      The id selectors will apply a margin of 1em to the top and bottom of both the <header> and the <main> element. The auto value on the margin property keeps the content centered to the page when paired with the width property of 90%. Lastly, the max-width property stops both containers from growing any wider once they reach a size of 60ch, which is equivalent to 60 0 characters in width.

      Save your changes to styles.css then open index.html in your browser. The contents of the page are now centered to the page instead of stretching across the length of the window. The following image illustrates how this appears in the browser:

      Text content centered to roughly half the width of the full size.

      In this section, you learned about the id attribute and used this attribute to apply styles to elements. You also learned that the id attribute is required to have a unique value because of how it can be used by the browser in URLs and in scripting. Next, you will use the class attribute to refactor the code from this section to apply colors to the content with new classes.

      Using the Class Selector

      In this section, you will use the class selector to find specific element on a page using values found in the class HTML attribute. You will refactor the id selectors to use a reusable class instead. The class attribute is meant to create a specific targetable value that styles can be applied to. Unlike the id, the values of class attributes can be reused, which is its advantage over the id selector.

      To begin styling elements with class, start by opening the index.html file in your text editor. On both the <header> and <main> elements, add a class attribute with a value of content-width. The highlighted section of the following code block indicates how to add the class attribute:

      index.html

      ...
      <header id="header" class="content-width">
        ...
      </header>
      <main id="content" class="content-width">
        ...
      </main>
      ...
      

      Save the changes to index.html, then open styles.css in your text editor to create the class selector for this class value.

      The class selector is similar to id in that it has a special character in CSS, which is indicated by the period symbol (.). In the styles.css file, locate the selector with the IDs #header, #content and replace both selectors with the .content-width class selector. The following highlighted code indicates the change you will make in your code:

      styles.css

      body {
        font-family: sans-serif;
        line-height: 1.5;
        color: #444;
      }
      
      .content-width {
        margin: 1em auto;
        width: 90%;
        max-width: 60ch;
      }
      

      Save these change to your styles.css and return to your browser. Refresh index.html and the styles will remain unchanged. The refactor to the code didn’t change the styles, but did change how the elements are selected. The versatility of a class is that the styles can be used more than once and on different element types.

      Next, return to index.html in your text editor and add a class attribute to the <h1> element with a value of style-1. This defines a class name that will be used in your CSS. The following code block indicates this change in the highlighted section:

      index.html

      ...
      <header id="header" class="content-width">
        <h1 class="style-1">Using CSS Selectors</h1>
      </header>
      ...
      

      Next, on each of the <h2> elements within the <main> element, add a class attribute. Give all three of these class attributes the value of style-2. Use the highlighted sections of the following code block for reference:

      index.html

      ...
      <main id="content" class="content-width">
        <section> 
          <h2 class="style-2">Selecting HTML To Style</h2>
          ...
        </section>
      
        <section>
          <h2 class="style-2">The Type Selector</h2>
          ...
        </section>
      
        <section>
          <h2 class="style-2">Combining Selectors</h2>
          ...
        </section>
      </main>
      ...
      

      After adding the class attributes to the elements in the index.html file, be sure to save the changes. Then, open styles.css in your text editor and create a class selector for the style-1 class.

      Add to the end of the styles.css a selector called .style-1. The period tells the browser to look for a class attribute on the page with the name of style-1. Then create a selector block and add a font-size property with a value of 1.25rem, a text-transform property set to uppercase, and a letter-spacing property to put 0.125em space between the characters. The following code block indicates these additions in the highlighted sections:

      styles.css

      ...
      .content-width {
        margin: 1em auto;
        width: 90%;
        max-width: 60ch;
      }
      .style-1 {
        font-size: 1.25rem;
        text-transform: uppercase;
        letter-spacing: 0.125em;
      }
      

      Next, create another class selector, this time for the style-2 class. Be sure to add the period before the name of the class value. Then add to that selector a font-size property with a 2rem value and a font-weight property set to normal. Reference the highlighted code in the following code block:

      styles.css

      ...
      .style-1 {
        font-size: 1.25rem;
        text-transform: uppercase;
        letter-spacing: 0.125em;
      }
      
      .style-2 {
        font-size: 2rem;
        font-weight: normal;
      }
      

      Save your changes to styles.css and refresh the index.html file in your browser. The <h1> content will now be smaller and uppercased while the <h2> content will be a bit larger and no longer bold. The following screenshot showcases how these changes will appear in the browser:

      Text in dark gray with a heading text that is short bold and uppercase, with other heading text much larger and title cased.

      Classes are a powerful selector, as they can be reused and applied to any element on the page. This means you can swap where and how the style-1 and style-2 classes are used, and no changes need to be made to the CSS.

      To make this change, open index.html and change the class attribute value style-1 on the <h1> element to style-2. Afterward, change the style-2 values on the <h2> elements’ class attribute to style-1. Reference the highlighted sections of the following code block for these changes:

      index.html

      ...
      <header id="header" class="content-width">
        <h1 class="style-2">Using CSS Selectors</h1>
      </header>
      
      <main id="content" class="content-width">
        <section> 
          <h2 class="style-1">Selecting HTML To Style</h2>
          ...
        </section>
      
        <section>
          <h2 class="style-1">The Type Selector</h2>
          ...
        </section>
      
        <section>
          <h2 class="style-1">Combining Selectors</h2>
          ...
        </section>
      </main>
      ...
      

      Save these changes to styles.css, return to your browser, and reload index.html. The large, normal font weight of style-2 is now applied to the content in the <h1> element, and the shorter, uppercase styles of style-1 now apply to the section <h2> elements. The following image shows how this will appear in the browser:

      Text in dark gray with a large title case heading text with other heading text that is short bold and uppercase.

      In this section you used class selectors to refactor the id selectors from the previous section. You also created two new class selectors to apply font styles to the <h1> and <h2> heading elements and you swapped those class attribute values to change which elements received what styles. In the next, section you will continue working with class selectors by applying more than one class to an HTML element.

      Combining Class Selectors

      In this section, you will apply multiple class values to the same HTML element to combine the styles from more than one CSS class selector. The advantage of class over id is its reusability, as shown in the previous section. class selectors can also be combined to target more specific elements. The class selector is the most common and preferred CSS selector for this versatility and specificity in finding an element and applying styles.

      To begin, open your styles.css file in your text editor. Then add three new class selectors: .color-1, .color-2, and .color-3. Each of these three selectors will have a color property with similar HSL values that differ by increments of 50 on the hue spectrum. This will provide a color palette that you can apply to elements in the HTML. See the highlighted portions of the following code block for what to add to your CSS file:

      styles.css

      ...
      .style-2 {
        font-size: 2rem;
        font-weight: normal;
      }
      
      .color-1 {
        color: hsl(300, 70%, 40%);
      }
      
      .color-2 {
        color: hsl(250, 70%, 40%);
      }
      
      .color-3 {
        color: hsl(200, 70%, 40%);
      }
      

      Save your changes to styles.css, then open index.html in your text editor. You will now add a color to each of the three <h2> elements in the <main> block. For the first <h2>, add a space after style-1 and then add color-1. The space between each value in the class attribute indicates an additional class name that can be referenced from the CSS. Do the same for the other <h2> elements, except use the color-2 value for the second <h2> and color-3 for the third <h2>.

      The highlighted portions of the following code block show how to format the additional class values:

      index.html

      ...
      <main id="content" class="content-width">
        <section> 
          <h2 class="style-1 color-1">Selecting HTML To Style</h2>
          ...
        </section>
      
        <section>
          <h2 class="style-1 color-2">The Type Selector</h2>
          ...
        </section>
      
        <section>
          <h2 class="style-1 color-3">Combining Selectors</h2>
          ...
        </section>
      </main>
      ...
      

      Save your changes to index.html, return to your browser, and refresh the page. The three <h2> section headings retain the same font styles, but now each have a different color. The following image shows how this will appear in your browser:

      Three short bold uppercase headings with one magenta, one dark blue, and one teal.

      Just as class values can be used together in HTML, the class names can also be combined to create more specific situations that may be present in the HTML. By chaining together class names in the CSS, you can select elements that have both classes at the same time.

      For example, the CSS selector .color-1.color-2 would only select elements that have an HTML class value that contains both color-1 and color-2.

      To try using multiple class names in a selector, open styles.css in your text editor. Create a new selector that combines the color-1 class and the color-2 class. The intent with this combination is to apply a color that is halfway between the defined color value of each of these classes.

      Add the following highlighted sections to your CSS file:

      styles.css

      ...
      .color-3 {
        color: hsl(200, 70%, 40%);
      }
      
      .color-1.color-2 {
        color: hsl(275, 70%, 40%);
      }
      
      .color-2.color-3 {
        color: hsl(225, 70%, 40%);
      }
      

      In this code block, you set the color property of the combined class selector .color1.color-2 to hsl(275, 70%, 40%), since that is halfway between the 300 value of .color-1 and the 250 value of .color-2. Then you did the same to combine .color-2 and .color-3.

      Be sure to save your changes to styles.css, then move over to index.html in your text editor and make the highlighted changes in the following code block:

      index.html

      ...
      <main id="content" class="content-width">
        <section> 
          <h2 class="style-1 color-1 color-2">Selecting HTML To Style</h2>
          ...
        </section>
        ...
        <section>
          <h2 class="style-1 color-3 color-2">Combining Selectors</h2>
          ...
        </section>
      </main>
      ...
      

      Note that the new styling you declared for the combined class selectors will take precedence over the style set for each of the individual classes. This is because the combined class selector is selecting a more specific element than the individual class selectors. For example, the rule you wrote for .color-1 is looking for an HTML element that has color-1 as one of its classes. The .color-1.color-2 rule is looking for an HTML element that has both color-1 and color-2 in the list of its classes. Since the second rule is a subset of the first, it can be said to be more specific, and thus overrides the first.

      With the two combined class CSS rules you have written, there are situations in which both rules could apply. For example, an HTML element with a class value of color-1 color-2 color-3 would satisfy both the .color-1.color-2 and .color-2.color-3 rules. In this case, since they both have the same amount of specificity, the cascade would come into effect, and the last rule declared would override the styling. In this case, .color-2.color-3 would apply its styling, since it is the final rule declared.

      Save the changes to index.html and then refresh the page in your browser. The colors of all three <h2> elements will still be distinct, but will now be visually closer together in hue.

      The following screenshot shows how this will appear in the browser:

      Three short bold uppercase headings with one purple, one dark blue, and one a medium blue.

      This section presented you with a way to string class values together to create more specific scenarios in which to apply styles. In the next section, you will learn how to find an element on the page based on any attribute type.

      Using the Attribute Selector

      In this section, you will use the attribute selector to find and select an element. In the previous sections, you have learned that the id and class selectors have a symbol indicating the kind of attribute to target followed by a value to select. The attribute selector’s format consists of the name of the attribute wrapped in a pair of square brackets, like so: [attr]. The attribute selector can be used with any attribute, including id and class.

      To begin using an attribute selector, open the index.html file in your text editor. You will add the HTML element for abbreviations, <abbr>, as a situation where attribute selectors would be beneficial. Place the <abbr> around the first instance of CSS in the <h1> element. Then, place another <abbr> around the first instance of HTML in the first <h2> element. With the HTML <abbr>, add a title attribute with a value of Hypertext Markup Language. Reference the following code block for how this will be formatted:

      index.html

      ...
      <h1 class="style-2">Using <abbr>CSS</abbr> Selectors</h1>
      ...
      <h2 class="style-1 color-1 color-2">Selecting <abbr title="Hypertext Markup Language">HTML</abbr> To Style</h2>
      ...
      

      Save your changes to index.html, then return to your browser to load the file. If you are using Chrome or Firefox, the browser default for <abbr> elements with a title attribute is to add a dotted underline. The purpose of the visual indicator is to have a user hover over the text, at which point the meaning of the abbreviation, the title attribute’s value, will appear. The following image demonstrates the default <abbr> styling in Firefox:

      Short bold uppercase headings in purple with an abbreviation text with a dotted underline.

      Unlike Chrome and Firefox, some browsers, like Safari and other older browsers, do not have an indicator of when an <abbr> element has a title. This same styling can be brought to these other browsers, along with some customizations with the attribute selector.

      To begin styling with an attribute selector, open the styles.css file in your text editor. Add an attribute selector for How To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS and then curly brackets for the selector block. In the selector block, add the text-decoration property set to underline. Then add the text-decoration-style property with a value of dotted. These two properties will equalize the styling across browsers. Lastly, to add a custom look for the underline, add the property text-decoration-color and set it to a light gray with the value hsl(0, 0%, 70%).

      Reference the following code block for how this will be formatted in your code:

      styles.css

      ...
      .color-2.color-3 {
        color: hsl(225, 70%, 40%);
      }
      
      How To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS {
        text-decoration: underline;
        text-decoration-style: dotted;
        text-decoration-color: hsl(0, 0%, 70%);
      }
      

      Save your changes to styles.css, then return to your browser and refresh index.html. The styling for the HTML text will now have a light gray dotted underline. The following image demonstrates how this will appear in the browser:

      Short bold uppercase headings in purple with an abbreviation text with a light gray dotted underline.

      The one issue with using the How To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS attribute as a selector is that it can be used on almost any element on the page. This means, with the current selector in place, <img> or <a> elements with the title attribute would also receive these styles. In this case, the selector needs to be scoped to work only with the <abbr> element.

      To make this adjustment to the selector, return to styles.css in your text editor. To the left of the How To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS attribute selector, add the abbr element selector. Be sure there is no space between these selectors as this tells the browser to specifically look for an <abbr> element with a title attribute. The following code block demonstrates how this will look, with the added element selector highlighted:

      styles.css

      ...
      abbrHow To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS {
        text-decoration: underline;
        text-decoration-style: dotted;
        text-decoration-color: hsl(0, 0%, 70%);
      }
      

      Save your changes to styles.css. The styles are now scoped to only work with <abbr> elements with a title attribute. The CSS <abbr> in the <h1> element will not receive these styles either. Though it does match the abbr selector, it does not have a title attribute and so does not match.

      In this section, you used the attribute selector to scope styles to an <abbr> element to apply styles when a title attribute is present. In the next section, you will use more advanced capabilities of the attribute selector by matching the value of a given attribute.

      Creating Advanced Attribute Selectors

      The attribute selector is useful for finding particular attributes in the HTML and applying styles to them. However, the attribute selector also has several ways to look for the value of an attribute. In this final section, you will use the attribute selector to look for matching value strings and apply styles that meet these specific conditions.

      To begin, open your styles.css file in the text editor. In the HTML there is a link that goes to the website https://example.com; to target that specific URL link, create an attribute selector for an href attribute. Then, within the brackets following the attribute name, add an equal sign followed by string quotes. Within those quotes, add https://example.com. This selector will target a link that goes to the specified URL. Add a color property with a green color using hsl(150, 70%, 40%).

      The highlighted code in the following code block shows how this is formatted in your styles.css file:

      styles.css

      ...
      abbrHow To Select HTML Elements Using ID, Class, and Attribute Selectors in CSS {
        text-decoration: underline;
        text-decoration-style: dotted;
        text-decoration-color: hsl(0, 0%, 70%);
      }
      
      [href="https://example.com"] {
        color: hsl(150, 70%, 40%);
      }
      

      Save these changes to styles.css and return to your browser to refresh index.html. Only the link that goes to https://example.com has changed to the color green, while the other link on the page remains the default blue. The following image demonstrates how this will appear in the browser:

      Link text in green color with underline.

      Next, return to styles.css in your text editor and add a custom hover state by using the :hover pseudo selector. The selector is set up the same as the previous selector, and immediately follows after the closing square bracket with :hover. Then within the selector add a color property with a darkened version of the previous green by changing the lightness value of the HSL from 40% to 20%.

      Reference the highlighted code in the following code block for how to format this in your code:

      styles.css

      ...
      [href="https://example.com"] {
        color: hsl(150, 70%, 40%); 
      }
      
      [href="https://example.com"]:hover {
        color: hsl(150, 70%, 20%);
      }
      

      Save your changes to styles.css and then reload index.html in your browser. The link that goes to https://example.com now has a hover state that switches to a darker green, as demonstrated in the following animation:

      Animation of green text link with underline changing to dark green when cursor hovers text.

      The attribute selector has additional condition modifiers to look for particular scenarios within a value string. One of these conditions that an attribute can look for is if a value starts with a given string. This is represented with the ^ symbol between the attribute name and the equal sign. This approach can be used to highlight links that are secure, meaning they go to a URL that begins with https:// instead of http://.

      Return to styles.css in your text editor and add an attribute selector that looks for a value that begins with a secure URL by using [href^="https://"]. This selector will only match elements that have an href element that begins with https://. Then, to add a visual indicator that the link is secure, use a pseudo element selector by adding ::before immediately following the closing bracket. Lastly, the ::before pseudo element selector requires a content property to be used, which will add a string before the content of the HTML element. For the value of this content property, use the unicode identifier for the lock emoji, 1F512.

      The highlighted section of the following code block demonstrates this syntax:

      styles.css

      ...
      
      [href^="https://"]::before {
        content: '1F512';
      }
      

      Save your changes to styles.css and return to your web browser to reload index.html. The two links in the content both have secure links and are now indicated as such with the lock emoji, as demonstrated in the following image:

      Links in two different styles with lock emoji preceding the link text.

      In this section you used the advanced features of the attribute selector to look for specific strings in the HTML attribute’s value. There are more options available for attribute value scenarios than were covered here. If you want to learn more about the different attribute selector options, read The Skinny on CSS Attribute Selectors by Chris Coyier.

      Conclusion

      Writing styles that are specific and reusable is a cornerstone to effective CSS architecture. In this tutorial, you learned how and when to use the id selector, how you can use and reuse the class selector in multiple combinations, and how to use the attribute selector and some of its features to create very specific scenarios in which to add styling. These selectors are a powerful asset to create styles that bring life and functionality to a website.

      If you would like to read more CSS tutorials, try out the other tutorials in the How To Style HTML with CSS series.



      Source link

      How To Manage State on React Class Components


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

      Introduction

      In React, state refers to a structure that keeps track of how data changes over time in your application. Managing state is a crucial skill in React because it allows you to make interactive components and dynamic web applications. State is used for everything from tracking form inputs to capturing dynamic data from an API. In this tutorial, you’ll run through an example of managing state on class-based components.

      As of the writing of this tutorial, the official React documentation encourages developers to adopt React Hooks to manage state with functional components when writing new code, rather than using class-based components. Although the use of React Hooks is considered a more modern practice, it’s important to understand how to manage state on class-based components as well. Learning the concepts behind state management will help you navigate and troubleshoot class-based state management in existing code bases and help you decide when class-based state management is more appropriate. There’s also a class-based method called componentDidCatch that is not available in Hooks and will require setting state using class methods.

      This tutorial will first show you how to set state using a static value, which is useful for cases where the next state does not depend on the first state, such as setting data from an API that overrides old values. Then it will run through how to set a state as the current state, which is useful when the next state depends on the current state, such as toggling a value. To explore these different ways of setting state, you’ll create a product page component that you’ll update by adding purchases from a list of options.

      Prerequisites

      Step 1 — Creating an Empty Project

      In this step, you’ll create a new project using Create React App. Then you will delete the sample project and related files that are installed when you bootstrap the project. Finally, you will create a simple file structure to organize your components. This will give you a solid basis on which to build this tutorial’s sample application for managing state on class-based components.

      To start, make a new project. In your terminal, run the following script to install a fresh project using create-react-app:

      • npx create-react-app state-class-tutorial

      After the project is finished, change into the directory:

      In a new terminal tab or window, start the project using the Create React App start script. The browser will auto-refresh on changes, so leave this script running while you work:

      You will get a running local server. If the project did not open in a browser window, you can open it with http://localhost:3000/. If you are running this from a remote server, the address will be http://your_domain:3000.

      Your browser will load with a simple React application included as part of Create React App:

      React template project

      You will be building a completely new set of custom components, so you’ll need to start by clearing out some boilerplate code so that you can have an empty project.

      To start, open src/App.js in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js at How To Set Up a React Project with Create React App.

      Open src/App.js with the following command:

      You will see a file like this:

      state-class-tutorial/src/App.js

      import React from 'react';
      import logo from './logo.svg';
      import './App.css';
      
      function App() {
        return (
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <p>
                Edit <code>src/App.js</code> and save to reload.
              </p>
              <a
                className="App-link"
                href="https://reactjs.org"
                target="_blank"
                rel="noopener noreferrer"
              >
                Learn React
              </a>
            </header>
          </div>
        );
      }
      
      export default App;
      

      Delete the line import logo from './logo.svg';. Then replace everything in the return statement to return a set of empty tags: <></>. This will give you a valid page that returns nothing. The final code will look like this:

      state-class-tutorial/src/App.js

      
      import React from 'react';
      import './App.css';
      
      function App() {
        return <></>;
      }
      
      export default App;
      

      Save and exit the text editor.

      Finally, delete the logo. You won’t be using it in your application and you should remove unused files as you work. It will save you from confusion in the long run.

      In the terminal window type the following command:

      If you look at your browser, you will see a blank screen.

      blank screen in chrome

      Now that you have cleared out the sample Create React App project, create a simple file structure. This will help you keep your components isolated and independent.

      Create a directory called components in the src directory. This will hold all of your custom components.

      Each component will have its own directory to store the component file along with the styles, images, and tests.

      Create a directory for App:

      Move all of the App files into that directory. Use the wildcard, *, to select any files that start with App. regardless of file extension. Then use the mv command to put them into the new directory:

      • mv src/App.* src/components/App

      Next, update the relative import path in index.js, which is the root component that bootstraps the whole process:

      The import statement needs to point to the App.js file in the App directory, so make the following highlighted change:

      state-class-tutorial/src/index.js

      import React from 'react';
      import ReactDOM from 'react-dom';
      import './index.css';
      import App from './components/App/App';
      import * as serviceWorker from './serviceWorker';
      
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        document.getElementById('root')
      );
      
      // If you want your app to work offline and load faster, you can change
      // unregister() to register() below. Note this comes with some pitfalls.
      // Learn more about service workers: https://bit.ly/CRA-PWA
      serviceWorker.unregister();
      

      Save and exit the file.

      Now that the project is set up, you can create your first component.

      Step 2 — Using State in a Component

      In this step, you’ll set the initial state of a component on its class and reference the state to display a value. You’ll then make a product page with a shopping cart that displays the total items in the cart using the state value. By the end of the step, you’ll know the different ways to hold a value and when you should use state rather than a prop or a static value.

      Building the Components

      Start by creating a directory for Product:

      • mkdir src/components/Product

      Next, open up Product.js in that directory:

      • nano src/components/Product/Product.js

      Start by creating a component with no state. The component will have two parts: The cart, which has the number of items and the total price, and the product, which has a button to add and remove an item. For now, the buttons will have no actions.

      Add the following code to Product.js:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      export default class Product extends Component {
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: 0 total items.
              </div>
              <div>Total: 0</div>
      
              <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
              <button>Add</button> <button>Remove</button>
            </div>
          )
        }
      }
      

      You have also included a couple of div elements that have JSX class names so you can add some basic styling.

      Save and close the file, then open Product.css:

      • nano src/components/Product/Product.css

      Give some light styling to increase the font-size for the text and the emoji:

      state-class-tutorial/src/components/Product/Product.css

      .product span {
          font-size: 100px;
      }
      
      .wrapper {
          padding: 20px;
          font-size: 20px;
      }
      
      .wrapper button {
          font-size: 20px;
          background: none;
      }
      

      The emoji will need a much larger font size than the text, since it’s acting as the product image in this example. In addition, you are removing the default gradient background on buttons by setting the background to none.

      Save and close the file.

      Now, render the Product component in the App component so you can see the results in the browser. Open App.js:

      • nano src/components/App/App.js

      Import the component and render it. You can also delete the CSS import since you won’t be using it in this tutorial:

      state-class-tutorial/src/components/App/App.js

      import React from 'react';
      import Product from '../Product/Product';
      
      function App() {
        return <Product />
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will refresh and you’ll see the Product component.

      Product Page

      Setting the Initial State on a Class Component

      There are two values in your component values that are going to change in your display: total number of items and total cost. Instead of hard coding them, in this step you’ll move them into an object called state.

      The state of a React class is a special property that controls the rendering of a page. When you change the state, React knows that the component is out-of-date and will automatically re-render. When a component re-renders, it modifies the rendered output to include the most up-to-date information in state. In this example, the component will re-render whenever you add a product to the cart or remove it from the cart. You can add other properties to a React class, but they won’t have the same ability to trigger re-rendering.

      Open Product.js:

      • nano src/components/Product/Product.js

      Add a property called state to the Product class. Then add two values to the state object: cart and total. The cart will be an array, since it may eventually hold many items. The total will be a number. After assigning these, replace references to the values with this.state.property:

      state-class-tutorial/src/components/Product/Product.js

      
      import React, { Component } from 'react';
      import './Product.css';
      
      export default class Product extends Component {
      
        state = {
          cart: [],
          total: 0
        }
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.state.total}</div>
      
              <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
              <button>Add</button> <button>Remove</button>
            </div>
          )
        }
      }
      

      Notice that in both cases, since you are referencing JavaScript inside of your JSX, you need to wrap the code in curly braces. You are also displaying the length of the cart array to get a count of the number of items in the array.

      Save the file. When you do, the browser will refresh and you’ll see the same page as before.

      Product Page

      The state property is a standard class property, which means that it is accessible in other methods, not just the render method.

      Next, instead of displaying the price as a static value, convert it to a string using the toLocaleString method, which will convert the number to a string that matches the way numbers are displayed in the browser’s region.

      Create a method called getTotal() that takes the state and converts it to a localized string using an array of currencyOptions. Then, replace the reference to state in the JSX with a method call:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      export default class Product extends Component {
      
        state = {
          cart: [],
          total: 0
        }
      
        currencyOptions = {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        }
      
        getTotal = () => {
          return this.state.total.toLocaleString(undefined, this.currencyOptions)
        }
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.getTotal()}</div>
      
              <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
              <button>Add</button> <button>Remove</button>
            </div>
          )
        }
      }
      

      Since total is a price for goods, you are passing currencyOptions that set the maximum and minimum decimal places for your total to two. Note that this is set as a separate property. Often, beginner React developers will put information like this in the state object, but it is best to only add information to state that you expect to change. This way, the information in state will be easier to keep strack of as your application scales.

      Another important change you made was to create the getTotal() method by assigning an arrow function to a class property. Without using the arrow function, this method would create a new this binding, which would interfere with the current this binding and introduce a bug into our code. You’ll see more on this in the next step.

      Save the file. When you do, the page will refresh and you’ll see the value converted to a decimal.

      Price converted to decimal

      You’ve now added state to a component and referenced it in your class. You also accessed values in the render method and in other class methods. Next, you’ll create methods to update the state and show dynamic values.

      Step 3 — Setting State from a Static Value

      So far you’ve created a base state for the component and you’ve referenced that state in your functions and your JSX code. In this step, you’ll update your product page to modify the state on button clicks. You’ll learn how to pass a new object containing updated values to a special method called setState, which will then set the state with the updated data.

      To update state, React developers use a special method called setState that is inherited from the base Component class. The setState method can take either an object or a function as the first argument. If you have a static value that doesn’t need to reference the state, it’s best to pass an object containing the new value, since it’s easier to read. If you need to reference the current state, you pass a function to avoid any references to out-of-date state.

      Start by adding an event to the buttons. If your user clicks Add, then the program will add the item to the cart and update the total. If they click Remove, it will reset the cart to an empty array and the total to 0. For example purposes, the program will not allow a user to add an item more then once.

      Open Product.js:

      • nano src/components/Product/Product.js

      Inside the component, create a new method called add, then pass the method to the onClick prop for the Add button:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      export default class Product extends Component {
      
        state = {
          cart: [],
          total: 0
        }
      
        add = () => {
          this.setState({
            cart: ['ice cream'],
            total: 5
          })
        }
      
        currencyOptions = {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        }
      
        getTotal = () => {
          return this.state.total.toLocaleString(undefined, this.currencyOptions)
        }
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.getTotal()}</div>
      
              <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
              <button onClick={this.add}>Add</button>
              <button>Remove</button>
            </div>
          )
        }
      }
      

      Inside the add method, you call the setState method and pass an object containing the updated cart with a single item ice cream and the updated price of 5. Notice that you again used an arrow function to create the add method. As mentioned before, this will ensure the function has the proper this context when running the update. If you add the function as a method without using the arrow function, the setState would not exist without binding the function to the current context.

      For example, if you created the add function this way:

      export default class Product extends Component {
      ...
        add() {
          this.setState({
            cart: ['ice cream'],
            total: 5
          })
        }
      ...
      }
      

      The user would get an error when they click on the Add button.

      Context Error

      Using an arrow function ensures that you’ll have the proper context to avoid this error.

      Save the file. When you do, the browser will reload, and when you click on the Add button the cart will update with the current amount.

      Click on the button and see state updated

      With the add method, you passed both properties of the state object: cart and total. However, you do not always need to pass a complete object. You only need to pass an object containing the properties that you want to update, and everything else will stay the same.

      To see how React can handle a smaller object, create a new function called remove. Pass a new object containing just the cart with an empty array, then add the method to the onClick property of the Remove button:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      export default class Product extends Component {
      
        ...
        remove = () => {
          this.setState({
            cart: []
          })
        }
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.getTotal()}</div>
      
              <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
              <button onClick={this.add}>Add</button>
              <button onClick={this.remove}>Remove</button>
            </div>
          )
        }
      }
      

      Save the file. When the browser refreshes, click on the Add and Remove buttons. You’ll see the cart update, but not the price. The total state value is preserved during the update. This value is only preserved for example purposes; with this application, you would want to update both properties of the state object. But you will often have components with stateful properties that have different responsibilities, and you can make them persist by leaving them out of the updated object.

      The change in this step was static. You knew exactly what the values would be ahead of time, and they didn’t need to be recalculated from state. But if the product page had many products and you wanted to be able to add them multiple times, passing a static object would provide no guarantee of referencing the most up-to-date state, even if your object used a this.state value. In this case, you could instead use a function.

      In the next step, you’ll update state using functions that reference the current state.

      Step 4 — Setting State Using Current State

      There are many times when you’ll need to reference a previous state to update a current state, such as updating an array, adding a number, or modifying an object. To be as accurate as possible, you need to reference the most up-to-date state object. Unlike updating state with a predefined value, in this step you’ll pass a function to the setState method, which will take the current state as an argument. Using this method, you will update a component’s state using the current state.

      Another benefit of setting state with a function is increased reliability. To improve performance, React may batch setState calls, which means that this.state.value may not be fully reliable. For example, if you update state quickly in several places, it is possible that a value could be out of date. This can happen during data fetches, form validations, or any situation where several actions are occurring in parallel. But using a function with the most up-to-date state as the argument ensures that this bug will not enter your code.

      To demonstrate this form of state management, add some more items to the product page. First, open the Product.js file:

      • nano src/components/Product/Product.js

      Next, create an array of objects for different products. The array will contain the product emoji, name, and price. Then loop over the array to display each product with an Add and Remove button:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      const products = [
        {
          emoji: '🍦',
          name: 'ice cream',
          price: 5
        },
        {
          emoji: '🍩',
          name: 'donuts',
          price: 2.5,
        },
        {
          emoji: '🍉',
          name: 'watermelon',
          price: 4
        }
      ];
      
      export default class Product extends Component {
      
        ...
      
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.getTotal()}</div>
              <div>
                {products.map(product => (
                  <div key={product.name}>
                    <div className="product">
                      <span role="img" aria-label={product.name}>{product.emoji}</span>
                    </div>
                    <button onClick={this.add}>Add</button>
                    <button onClick={this.remove}>Remove</button>
                  </div>
                ))}
              </div>
            </div>
          )
        }
      }
      

      In this code, you are using the map() array method to loop over the products array and return the JSX that will display each element in your browser.

      Save the file. When the browser reloads, you’ll see an updated product list:

      Product list

      Now you need to update your methods. First, change the add() method to take the product as an argument. Then instead of passing an object to setState(), pass a function that takes the state as an argument and returns an object that has the cart updated with the new product and the total updated with the new price:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      ...
      
      export default class Product extends Component {
      
        state = {
          cart: [],
          total: 0
        }
      
        add = (product) => {
          this.setState(state => ({
            cart: [...state.cart, product.name],
            total: state.total + product.price
          }))
        }
      
        currencyOptions = {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        }
      
        getTotal = () => {
          return this.state.total.toLocaleString(undefined, this.currencyOptions)
        }
      
        remove = () => {
          this.setState({
            cart: []
          })
        }
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.getTotal()}</div>
      
              <div>
                {products.map(product => (
                  <div key={product.name}>
                    <div className="product">
                      <span role="img" aria-label={product.name}>{product.emoji}</span>
                    </div>
                    <button onClick={() => this.add(product)}>Add</button>
                    <button onClick={this.remove}>Remove</button>
                  </div>
                ))}
              </div>
            </div>
          )
        }
      }
      

      Inside the anonymous function that you pass to setState(), make sure you reference the argument—state—and not the component’s state—this.state. Otherwise, you still run a risk of getting an out-of-date state object. The state in your function will be otherwise identical.

      Take care not to directly mutate state. Instead, when adding a new value to the cart, you can add the new product to the state by using the spread syntax on the current value and adding the new value onto the end.

      Finally, update the call to this.add by changing the onClick() prop to take an anonymous function that calls this.add() with the relevant product.

      Save the file. When you do, the browser will reload and you’ll be able to add multiple products.

      Adding products

      Next, update the remove() method. Follow the same steps: convert setState to take a function, update the values without mutating, and update the onChange() prop:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      ...
      
      export default class Product extends Component {
      
      ...
      
        remove = (product) => {
          this.setState(state => {
            const cart = [...state.cart];
            cart.splice(cart.indexOf(product.name))
            return ({
              cart,
              total: state.total - product.price
            })
          })
        }
      
        render() {
          return(
            <div className="wrapper">
              <div>
                Shopping Cart: {this.state.cart.length} total items.
              </div>
              <div>Total {this.getTotal()}</div>
              <div>
                {products.map(product => (
                  <div key={product.name}>
                    <div className="product">
                      <span role="img" aria-label={product.name}>{product.emoji}</span>
                    </div>
                    <button onClick={() => this.add(product)}>Add</button>
                    <button onClick={() => this.remove(product)}>Remove</button>
                  </div>
                ))}
              </div>
            </div>
          )
        }
      }
      

      To avoid mutating the state object, you must first make a copy of it using the spread operator. Then you can splice out the item you want from the copy and return the copy in the new object. By copying state as the first step, you can be sure that you will not mutate the state object.

      Save the file. When you do, the browser will refresh and you’ll be able to add and remove items:

      Remove items

      There is still a bug in this application: In the remove method, a user can subtract from the total even if the item is not in the cart. If you click Remove on the ice cream without adding it to your cart, your total will be -5.00.

      You can fix the bug by checking for an item’s existence before subtracting, but an easier way is to keep your state object small by only keeping references to the products and not separating references to products and total cost. Try to avoid double references to the same data. Instead, store the raw data in state— in this case the whole product object—then perform the calculations outside of the state.

      Refactor the component so that the add() method adds the whole object, the remove() method removes the whole object, and the getTotal method uses the cart:

      state-class-tutorial/src/components/Product/Product.js

      import React, { Component } from 'react';
      import './Product.css';
      
      ...
      
      export default class Product extends Component {
      
        state = {
          cart: [],
        }
      
        add = (product) => {
          this.setState(state => ({
            cart: [...state.cart, product],
          }))
        }
      
        currencyOptions = {
          minimumFractionDigits: 2,
          maximumFractionDigits: 2,
        }
      
        getTotal = () => {
          const total = this.state.cart.reduce((totalCost, item) => totalCost + item.price, 0);
          return total.toLocaleString(undefined, this.currencyOptions)
        }
      
        remove = (product) => {
          this.setState(state => {
            const cart = [...state.cart];
            const productIndex = cart.findIndex(p => p.name === product.name);
            if(productIndex < 0) {
              return;
            }
            cart.splice(productIndex, 1)
            return ({
              cart
            })
          })
        }
      
        render() {
          ...
        }
      }
      

      The add() method is similar to what it was before, except that reference to the total property has been removed. In the remove() method, you find the index of the product with findByIndex. If the index doesn’t exist, you’ll get a -1. In that case, you use a conditional statement toreturn nothing. By returning nothing, React will know the state didn’t change and won’t trigger a re-render. If you return state or an empty object, it will still trigger a re-render.

      When using the splice() method, you are now passing 1 as the second argument, which will remove one value and keep the rest.

      Finally, you calculate the total using the reduce() array method.

      Save the file. When you do, the browser will refresh and you’ll have your final cart:

      Add and remove

      The setState function you pass can have an additional argument of the current props, which can be helpful if you have state that needs to reference the current props. You can also pass a callback function to setState as the second argument, regardless of if you pass an object or function for the first argument. This is particularly useful when you are setting state after fetching data from an API and you need to perform a new action after the state update is complete.

      In this step, you learned how to update a new state based on the current state. You passed a function to the setState function and calculated new values without mutating the current state. You also learned how to exit a setState function if there is no update in a manner that will prevent a re-render, adding a slight performance enhancement.

      Conclusion

      In this tutorial, you have developed a class-based component with a dynamic state that you’ve updated statically and using the current state. You now have the tools to make complex projects that respond to users and dynamic information.

      React does have a way to manage state with Hooks, but it is helpful to understand how to use state on components if you need to work with components that must be class-based, such as those that use the componentDidCatch method.

      Managing state is key to nearly all components and is necessary for creating interactive applications. With this knowledge you can recreate many common web components, such as sliders, accordions, forms, and more. You will then use the same concepts as you build applications using hooks or develop components that pull data dynamically from APIs.

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



      Source link