One place for hosting & domains

      State

      How To Use Links and Buttons with State Pseudo-Classes in CSS


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

      Introduction

      An important part of web development is providing feedback when a user interacts with an element. This interaction is accomplished through changing state, which indicates how the user is using or has used a given element on the page. In CSS, there are special variations on selectors called a pseudo-class, which allow state changes to initiate style changes.

      In this tutorial, you will use the :hover, :active, and :focus user actions and the :visited location pseudo-classes. You will use <a> and <button> as the interactive elements in the tutorial. Both of these elements have similar interactive states and are functionally identical to the user. From a development standpoint, <a> elements are specifically for interacting with URLs, whereas the <button> element is used to trigger forms or JavaScript functions. In addition to working with these four distinct states, you will use the transition property to animate the styles between these states of interactivity.

      Prerequisites

      Setting Up the Initial HTML and CSS

      To begin working with links and buttons, you will first set up the HTML and CSS needed as a foundation for 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 highlighted 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 set the title of the page, add a <meta> tag defining how mobile devices should render the page, and finally load the CSS file 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>Link and Buttons with State</title>
          <link rel="stylesheet" href="https://www.digitalocean.com/community/tutorials/styles.css">
        </head>
        <body>
        </body>
      </html>
      

      After adding the <head> content, next move to the <body> element where content is added to make an informational block with <a> and <button> tags as interactive elements. 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>
          <section>
            <header>
              <button class="link" type="button">
                Close Window
              </button>
            </header>
            <div>
              <p>
                This is a demo paragraph for some demo content for a tutorial. By reading this text you are now informed that this is text for the demo in <a href="https://do.co/tutorials">this tutorial</a>. This means you can agree to use this content for the demo or not. If you wish to continue with this tutorial demo content, please select the appropriately styled interactive element below.
              </p>
              <div>
                <button class="button" type="button">
                  Yes, Please
                </button>
                <a class="button" href="https://www.digitalocean.com/community/tutorials/#">
                  No, Thank you
                </a>
              </div>
            </div>
          </section>
        </body>
      </html>
      

      Save your changes to index.html and open your web browser to open the index.html file there. The content of the page will appear in a black serif font on a white background. The <button> element styles will vary based on your browser and operating system, but the page will look similar to the following image:

      Black serif text on a white background with two blue underlined links and two interactive butons.

      Next, create a file called styles.css in the same directory as the index.html file. The following styles in the code block will set up a base style for the container holding the <button> and <a> elements you will be styling throughout the remainder of the tutorial. Add the following code to your styles.css file:

      styles.css

      body {
        background-color: #454545;
        color: #333;
        font-family: sans-serif;
        line-height: 1.5;
      }
      
      section {
        margin: 2rem auto;
        width: 90%;
        max-width: 50rem;
        box-sizing: border-box;
        padding: 2rem;
        border-radius: 2rem;
        border: 0.25rem solid #777;
        background-color: white;
      }
      
      header {
        text-align: right;
      }
      

      The CSS in this code block adds styles for three portions of the demo content area. The first is the body selector, which applies a dark gray background and then defines default font properties. The second selector focuses on the <section> element, which is the primary container for the demo content and creates a white block with big, rounded corners and a maximum width so it will only grow to a specified amount. Lastly, the header element selector sets the text alignment to the right so the Close Window button is to the far top right corner of the <section> container.

      If you’d like to learn more about how to use CSS element selectors, check out How To Select HTML Elements to Style with CSS.

      Save your changes to the styles.css file and reload the index.html file in your browser. The page styles will have transformed from the browser defaults to a customized style, as demonstrated in the following image:

      Black sans serif text in a white container with rounded corners with two blue underlined links and two interactive butons.

      You have set up your HTML and loaded in the base styles for the contents of the page. Next you will create a custom default link style and provide a way for that default style to be applied to button elements.

      In this section, you will create a custom default link style by setting a new color. Then you will remove default button styles in order to make the button look like a default link. a and button have distinct purposes, but website users interact with both in a similar way. Sometimes, these two elements need to appear similar to help explain an interaction to the user or match a design aesthetic.

      For the first part of this section, you will set up the default link style that will be used on the generic <a> element and a class of .link, which can then apply the generic link styles to the <button> element. Start by creating a group selector containing an a element selector and a .link class selector with a color property and value of #25a:

      styles.css

      ...
      a,
      .link {
        color: #25a;
      }
      

      Save your changes to styles.css and open index.html in your browser. The <a> elements on the page are now a deeper blue color than the browser default link blue. Also, the <button> element with the class="link" has the same blue color text in the button. The appearance of change in the browser is demonstrated in the following image:

      Black sans serif text in a white container with rounded corners with two blue underlined links and two interactive buttons.

      Next, begin removing the default appearance of a button. By default, browsers add a lot of customization to the look and feel of <button> elements to make sure that they are distinguishable as interactive elements. To remove the extra styling that browsers add to these elements, return to styles.css in your text editor, create a button element selector, and add two similar properties as shown in the following code block:

      styles.css

      ...
      a,
      .link {
        color: #25a;
      }
      
      button {
        -webkit-appearance: none;
        appearance: none;
      }
      

      The first property is -webkit-appearance: none;, which is known as a vendor prefix property. The -webkit- position of the property is only read by browsers built on a derivative of the WebKit browser engine, such as Safari or Chrome. Some versions of these browsers don’t support the appearance property by themselves and need the -webkit- prefix in order to work.

      Vendor prefix usage is on the decline, but still occurs. It is important to put any vendor prefixed properties before the non-prefixed to avoid overriding properties on browsers that support both the prefixed and non-prefixed variants.

      Save your changes to styles.css and refresh index.html in your browser. The button element will not lose all its styles; instead, it will become simplified to default styles expected of the web specification. The following image demonstrates how this will appear in the browser:

      Black sans serif text in a white container with rounded corners with two blue underlined links and two interactive buttons.

      To remove the remaining default styles of the button, you will need to add several more properties. Return to your styles.css file in your text editor and go to the button selector. Next, you will add the properties to remove the background-color, border, margin, and padding. Then you’ll remove the properties for the button element, color, font, and text-align, to inherit the value of the page.

      The following code block demonstrates how to set this up:

      styles.css

      ...
      button {
        -webkit-appearance: none;
        appearance: none;
        background-color: transparent;
        border: none;
        margin: 0;
        padding: 0;
        color: inherit;
        font: inherit;
        text-align: center;
      }
      

      Save these changes to styles.css and refresh index.html in your web browser. Both buttons have now lost their default styles, with the Close Window button being closer in styles to the link. The Yes, Please button styles will be addressed in the next section. The following image demonstrates how this will appear in the browser:

      Black sans serif text in a white container with rounded corners with two blue underlined links and two interactive buttons that appear as plain text.

      To finish making the Close Window button look like a text link, open styles.css in your text editor. Below the a, .link group selector, add a new class selector for only the .link class. Add to that a text-decoration property with a value of underline. Then add a property called cursor, which defines how the mouse cursor appears when on that element, and set the value to pointer. The pointer value styles cursor is the hand-style that appears over a link by default:

      styles.css

      ...
      a,
      .link {
      ...
      }
      
      .link {
        text-decoration: underline;
        cursor: pointer;
      }
      ...
      

      Save these changes to your styles.css file and then return to your browser and refresh index.html. The Close Window button will now appear and behave in a similar manner to the generic <a> element styles. The following animation demonstrates how the cursor changes when moving over the Close Window button:

      A button styled to look like a defaul link with blue text and underlined.

      In this section, you created a custom default style for the <a> element and created a .link class to apply link styling to a <button> element. In the next section, you will create a custom, button-like style that can be applied to both <button> and <a> elements.

      Creating Styles for a Button

      Next, you will create a custom, button-like style with a class selector so the styles can be used on either a <button> or an <a> element. Unlike <a> elements, which are used throughout text content, the <button> element has a more intentional purpose. This makes it less necessary to create generic styles for the <button> element, and instead allows you to always add a class attribute.

      Start by opening up styles.css in your text editor. Create a new class selector called .button. The styles here will redefine many of the properties that were reset in the previous step on the button element selector. You will add color to the text with the color property, fill the shape with the background-color property, then provide some definition with the border property. Afterward you will give a rounded corner to the button with the border-radius property. Finally, you will use the padding shorthand to give space above and below the text, then double that amount on the left and right.

      The following code block contains these values:

      styles.css

      ...
      .button {
        color: #25a;
        background-color: #cef;
        border: 1px solid #67c;
        border-radius: 0.5rem;
        padding: 0.75rem 1.5rem;
      }
      

      Save your changes to styles.css and return to your browser to refresh the index.html file. The look of the Yes, Please and No, Thank you buttons will change to match the properties. The text is the same blue as the default a style, with a much lighter blue in the background, and another deep blue for the border. The next image demonstrates how this will appear in the browser:

      Text with two buttons below with one button taller than the other. The shorter button has underlined text.

      There is a noticeable difference in size between the two buttons. Since the No, Thank you button is using an <a> element instead of a <button>, there are a few additional properties that need to be added to the .button selector to equalize the defaults between these two elements.

      Return to styles.css in your text editor and go to the .button class selector. To begin, add a display: inline-block, which is the default style on button elements. Next, add a text-decoration: none to remove the underline from the <a> element. As you did with the .link selector, add a cursor: pointer to the selector to get the pointing hand icon when a mouse cursor is over the element. Lastly, add a vertical-align: bottom rule. This last property isn’t necessary for all browsers, but it defines where the bottom of elements are positioned on a line:

      styles.css

      ...
      .button {
        color: #25a;
        background-color: #cef;
        border: 1px solid #67c;
        border-radius: 0.5rem;
        padding: 0.75rem 1.5rem;
        display: inline-block;
        text-decoration: none;
        cursor: pointer;
        vertical-align: bottom;
      }
      

      Save these additions to styles.css and then refresh index.html in your browser. The two buttons are now equivalent in visual appearance and have borrowed default attributes from one another, so they are perceived to have a similar interaction.

      Text with two light blue button of equal height with dark blue text and a dark blue thin border.

      You created a custom class selector to style both <button> and <a> elements with a button-like style in this section. Next, you will create a conditional style when a mouse cursor is on top of the interactive elements.

      Now you will use the :hover pseudo-class to create an alternative style that displays when the cursor is on the element. Pseudo-classes are a special group of conditions that are defined by a colon (:) and the name of the condition appended to the selector. For example, the a element selector with a hover pseudo-class becomes a:hover.

      Open styles.css in your text editor. Below the group selector for a, .link, add a new selector for the hover state by appending each selector with the :hover pseudo-class: a:hover, .link:hover. Then, to make a noticeable change when the cursor hovers over the element, add a color property with a value of #b1b, which is a dark pink color:

      styles.css

      ...
      a,
      .link {
        ...
      }
      
      a:hover,
      .link:hover {
        color: #b1b;
      }
      ...
      

      Save the changes to your styles.css file and refresh index.html in your browser. Hover over either the this tutorial link or the Close Window button to initiate the color change on hover. The following image shows what the hover state looks like when the cursor is over the this tutorial link.

      A paragraph of text with a link with a hand cursor icon over the link. The link text is a dark pink color with an underline.

      Next, to add a hover state to the .button elements, return to styles.css in your text editor. Below the .button class selector, add a .button:hover selector to create styles specifically for hover interaction. Next, within the selector, add color properties that will change the button appearance when the cursor is on the buttons. Set a color property to white, then create a background-color and a border-color with both properties set to #25a:

      styles.css

      ...
      .button {
        ...
      }
      
      .button:hover {
        color: white;
        background-color: #25a;
        border-color: #25a;
      }
      

      Save these changes to your styles.css file and return to your browser to refresh the index.html file. Now, take your mouse cursor and hover over one of the two buttons on the bottom. The styles change from the light blue background with a deep blue text and border to a deep blue background with white text. The following image shows what the hover style looks like when the mouse cursor is over the Yes, Please button.

      Two buttons below a paragraph of text. One button has a hand pointer cursor over it and is a dark blue with white text. The other button is light blue with dark blue text.

      You used the :hover pseudo-class in this section to create style changes to the element based on the cursor being positioned on top of the element. In the next section, you will apply this same concept to a condition when a keyboard is used to navigate through the page.

      Applying the :focus Pseudo-Class

      Instead of using a mouse or a touch screen, website visitors will sometimes use their keyboard to navigate and interact with elements of a page. This is most often done by using the TAB key, which will cycle through the interactive elements on the page. The default style uses the outline property to give a visual indicator that the element has focus. This style can be customized by using the :focus pseudo-class to apply property values for this situation.

      To begin working with the focus state of the elements on the page, open your styles.css file in your text editor. Start with a new selector below the a:hover, .link:hover group selector with a new group selector for the focus state: a:focus, .link:focus.

      The most important part of customizing the focus state is to make sure it is noticeably different from the default state. Here, you will make the :focus pseudo-class styles have black text with a gold background:

      styles.css

      ...
      a:hover,
      .link:hover {
        ...
      }
      
      a:focus,
      .link:focus {
        color: black;
        outline: 2px solid gold;
        background-color: gold;
      }
      ...
      

      In this case, you set the color property to black and the background-color property to gold. You also used the outline property, which added some gold color around the edges of the text, outside where the background-color property can reach.

      The outline property works similar to the border shorthand property, as it accepts a width, a style, and a color. However, unlike the border properties, outline always goes around the whole element and can not be set to a specific side. Also, unlike border, the outline does not affect the box-model; the shape is only applied visually and does not change the flow of content.

      Save your changes to styles.css and refresh index.html in your web browser. Begin pressing the TAB key until the browser brings focus to the Close Window and this tutorial elements highlight with a gold background. The following image shows what the this tutorial link looks like in the browser when it has focus:

      A paragraph of text with a link. The link text is black color with an underline and a yellow background.

      Next, to apply a similar custom focus style to the .button class elements, begin by creating a .button:focus class and pseudo-class selector. Since the .button element is already using a border, you will use that to indicate focus, and so remove the outline default by setting the property to have a value of none. Like the link before, the color property will be set to black and the background-color property will be set to gold. Last, set the border-color property to have a value of black:

      styles.css

      ...
      .button:hover {
        ...
      }
      
      .button:focus {
        outline: none;
        color: black;
        background-color: gold;
        border-color: black;
      }
      

      Be sure to save your additions to styles.css and then return to you browser to refresh your index.html file. Again, using the TAB key, cycle through the keyboard-focusable elements on the page until you reach the .button elements. They will now light up with a gold background and black text with a black border. The following image demonstrates how the Yes, Please button appears in the browser when focused:

      Two buttons below a paragraph of text. One button is focused with black text and black border with a yellow background. The other button is light blue with dark blue text.

      In this section, you used the :focus pseudo-class to create custom styles that appear when the website visitor uses their keyboard to navigate the page. In the next section, you will use the :active pseudo-class to indicate when a user is interacting with an element via a mouse click or a keypress.

      Applying the :active Pseudo-Class

      The next pseudo-class that you will work with is the :active state of an interactive element. The active state is the state at which an element is interacted with, typically via a mouse down or mouse click action. This provides the opportunity to give the visitor a clear state to indicate a successful mouse click and button press.

      To begin working with the :active pseudo-class, open styles.css in your text editor. Following the group selector block for a:focus, .link:focus, add a new selector block with the group selector a:active, .link:active. Give color a value of #808, which will create a darker pink than the :hover state.

      Note that some browsers will mix the styles of a :focus pseudo-class and an :active pseudo-class. To prevent this, you will need to remove the outline and background-color properties by setting them to none and transparent, respectively:

      styles.css

      ...
      a:focus,
      .link:focus {
        ...
      }
      
      a:active,
      .link:active {
        color: #808;
        outline: none;
        background-color: transparent;
      }
      ...
      

      Save the addition of the :active pseudo-class to your styles.css file, then reload index.html in your web browser. The following animation shows how the :active state changes from the pink to darker pink as the mouse is clicked while over the this tutorial link.

      An animation involving a paragraph of text with a link with a hand cursor icon over the link. The link text is underlined and is alternating between a dark pink and a darker pink.

      Next, to apply an active state to .button, return to styles.css in your text editor. Add a .button:active pseudo-class selector and apply styles that are dark variants of the :hover styles. For the color property, set the value to a light gray with #ddd. For both the background-color and border-color properties, set the value to a dark blue with a value of #127. The highlighted sections of the following code block demonstrate how this is written:

      styles.css

      ...
      .button:focus {
        ...
      }
      
      .button:active {
        color: #ddd;
        background-color: #127;
        border-color: #127;
      }
      

      Be sure to save these changes to styles.css, then return to your browser to refresh index.html. Hover your mouse cursor over one of the two buttons in the bottom of the content and then click down. The button will change from a light blue background with blue text and border to a full blue button with white text when hovered, then to a dark blue button with light gray text when clicked. The following animation demonstrates how this change from the :hover to the :active state appears as the mouse button is clicked:

      An animation of cursor pressing a button turning the button from blue with white text to a dark blue with light gray text.

      You created a visual indicator of a mouse button press event by using the :active pseudo-class to change the styles when that event occurs. In the next section, you will use the :visited pseudo-class to provide an indicator of which <a> elements with a href attribute have visited that link.

      Applying the :visited Pseudo-Class

      The :visited pseudo-class is unlike the previous pseudo-classes covered in this tutorial. Where the previous pseudo-classes involve an active interaction of the user with the element, the :visited pseudo-class indicates that an element was previously interacted with. Specifically, the :visited pseudo-class indicates which <a> with an href attribute are present in the browser history, meaning those links have been visited.

      To create a customized :visited indicator on the text links, open your styles.css file in your text editor. Below the a:active, .link:active group selector, add a new group selector targeting a a:visited, .link:visted group selector. The default :visited link style is commonly a purple color. For the purposes of the demo, the :visited color will be a dark green.

      Add a color property with a value of #080, as shown in the following highlighted code:

      styles.css

      ...
      a:active,
      .link:active {
        ...
      }
      
      a:visited,
      .link:visited {
        color: #080;
      }
      ...
      

      Save this change to the styles.css file and then open index.html in your web browser. If you haven’t, go ahead and click the this tutorial and No, Thank you <a> element links. Both of these links will have a text color of dark green, as shown in the following image:

      Pragraph of text with a visited link underlined and green and two buttons below. One of the button’s text is green instead of dark blue.

      Now, the green text in the button distracts from the purpose of the No, thank you button. Additionally, when the visited links are interacted with again with a :hover or :active state, the dark green remains instead of the defined colors for each respective state.

      To address these scenarios, return to your styles.css file in your text editor. First, append the a:hover, .link:hover group selector with the added scenario of a :visited element that has an active :hover state by adding a:visited:hover, .link:visited:hover. Likewise, expand the a:active, .link:active selector block with a:visited:active, .link:visited:active. Lastly, the desired visited state for the .button element is to be styled the same as the default state. As such, the .button selector needs to become a group selector of .button, .button:visited, so the visited state appears the same as the default state:

      styles.css

      ...
      a:hover,
      .link:hover,
      a:visited:hover,
      .link:visited:hover {
        color: #b1b;
      }
      ...
      a:active,
      .link:active,
      a:visited:active,
      .link:visited:active {
        color: #808;
      }
      
      a:visited,
      .link:visited {
        color: #080;
      }
      ...
      .button,
      .button:visited {
        ...
      }
      
      .button:hover,
      .button:visited:hover {
        color: white;
        background-color: #25a;
        border-color: #25a;
      }
      ...
      

      Save your changes to the styles.css file and reload index.html in the web browser. The text default :visited link now appears in the desired dark green color, while the button-style link retains the button look. The following image demonstrates how this will appear in the browser.

      Paragraph of text with a visited link underlined and green and two similarly-styled buttons below.

      You used the :visited pseudo-class to create styles specific to when a link is present in the browser history, indicating to the user links that have been visited. This section concludes the work on pseudo-classes and using them to define specific styles for a given state. In the final section of this tutorial, you will use the transition property to create a seamless animation between these different pseudo-class states.

      Using transition to Animate Between States

      When working with states of elements, a shift in styles between the states can be abrupt. The transition property is used to blend and animate the styles from one state to the next state to avoid this abruptness. The transition property is a shorthand property that combines the transition-property, transition-duration, and transition-timing-function properties.

      The transition-property can be any property that has a value calculated between two given values. Colors are included in this, as they are numerical values, even when a color name is used. The transition-duration property is a numeric value for how long the transition should occur. The value for the duration is often represented in seconds, with the s unit, or milliseconds, with the ms unit. Lastly, the transition-timing-function controls how the animation will play out over time, enabling you to make subtle changes to enhance the animation.

      To begin working with the transition property, open your styles.css file and go to the a, .link group selector and the .button, .button:visited group selector. Add a transition property with a value of all 0.5s linear. The all is the value for the transition-property, which tells the browser to animate all the properties that change between the states. The 0.5s is the transition-duration value and equates half a second; this can also be represented as 500ms. Lastly, the linear position is the transition-timing-function value, which tells the browser to move from one value to the next in a constant increment throughout the duration:

      styles.css

      ...
      a,
      .link {
        ...
        transition: all 0.5s linear;
      }
      ...
      .button,
      .button:visited {
        ...
        transition: all 0.5s linear;
      }
      

      Save your changes to styles.css and then open index.html in your web browser. Once the page loads, begin interacting with the link and button elements and pay attention to how the styles animate between the different states. The following animation shows the button style transitioning from the default state to the :hover pseudo-class state:

      Animation of a cursor hovering a button and the button transitions styles from a light blue button with blue text to a blue button with white text.

      To make the animations feel more snappy and natural, return to your styles.css file and adjust the transition property values. For the a,.link group selector, change the duration from 0.5s to 250ms, which is half the duration compared to what it was before. Then change the linear timing function value to ease-in-out. This will create an animation that starts off slow, speeds up in the middle, and slows down to the end. Then, for the .button,.button:visited group selector, change the duration to a quicker 180ms and set the timing function to the same ease-in-out value as the link:

      styles.css

      
      ...
      a,
      .link {
        ...
        transition: all 250ms ease-in-out;
      }
      ...
      .button,
      .button:visited {
        ...
        transition: all 180ms ease-in-out;
      }
      

      Save these changes to your styles.css file and then refresh the index.html page in your web browser. The transition animations between states will still animate, but are now much quicker and feel faster, too. With the transition property, it is important to play around with the values to find an animation that fits the design. The following animation demonstrates the faster transition of the button from the default state to the :hover state to the :active state.

      Animation of a cursor hovering a button and the button transitions styles from a light blue button with blue text to a blue button with white text. Then the cursor moves and hovers a green underlined link and link fades to a pink color.

      You have now created an animation between states. The transition property helps make changes between states more engaging and fun.

      Conclusion

      Providing clear differences between interactive element states is a valuable asset to a website. States help communicate important concepts to the website’s visitor by providing visual feedback to an interaction.

      In this tutorial, you have successfully used the primary state pseudo-classes to create multiple styles for different interactive elements. You also learned that there are different purposes behind the <button> and <a> elements, but visually they can communicate similar concepts. Lastly, you used the transition property to provide smooth animations between these states to create more engaging interactive elements. It is important to keep these four states in mind when creating a website so the visitor is given this important interactive feedback.

      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 in React with Redux


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

      Introduction

      Redux is a popular data store for JavaScript and React applications. It follows a central principle that data binding should flow in one direction and should be stored as a single source of truth. Redux gained popularity because of the simplicity of the design concept and the relatively small implementation.

      Redux operates according to a few concepts. First, the store is a single object with fields for each selection of data. You update the data by dispatching an action that says how the data should change. You then interpret actions and update the data using reducers. Reducers are functions that apply actions to data and return a new state, instead of mutating the previous state.

      In small applications, you may not need a global data store. You can use a mix of local state and context to manage state. But as your application scales, you may encounter situations where it would be valuable to store information centrally so that it will persist across routes and components. In that situation, Redux will give you a standard way to store and retrieve data in an organized manner.

      In this tutorial, you’ll use Redux in a React application by building a bird watching test application. Users will be able to add birds they have seen and increment a bird each time they see it again. You’ll build a single data store, and you’ll create actions and reducers to update the store. You’ll then pull data into your components and dispatch new changes to update the data.

      Prerequisites

      • You will need a development environment running Node.js; this tutorial was tested on Node.js version 10.22.0 and npm version 6.14.6. To install this on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.

      • A React development environment set up with Create React App, with the non-essential boilerplate removed. To set this up, follow Step 1 — Creating an Empty Project of the How To Manage State on React Class Components tutorial. This tutorial will use redux-tutorial as the project name.

      • You will be using React components, Hooks, and forms in this tutorial, including the useState Hook and custom Hooks. You can learn about components and Hooks in our tutorials How To Manage State with Hooks on React Components and How To Build Forms in React.

      • You will also need a basic knowledge of JavaScript, HTML, and CSS, which you can find in our How To Build a Website With HTML series, How To Build a Website With CSS series, and in How To Code in JavaScript.

      Step 1 — Setting Up a Store

      In this step, you’ll install Redux and connect it to your root component. You’ll then create a base store and show the information in your component. By the end of this step, you’ll have a working instance of Redux with information displaying in your components.

      To start, install redux and react-redux. The package redux is framework agnostic and will connect your actions and reducers. The package react-redux contains the bindings to run a Redux store in a React project. You’ll use code from react-redux to send actions from your components and to pull data from the store into your components.

      Use npm to install the two packages with the following command:

      • npm install --save redux react-redux

      When the component is finished installing, you’ll receive output like this. Your output may be slightly different:

      Output

      ... + redux@4.0.5 + react-redux@7.2.1 added 2 packages from 1 contributor, updated 1 package and audited 1639 packages in 20.573s

      Now that you have the packages installed, you need to connect Redux to your project. To use Redux, you’ll need to wrap your root components with a Provider to ensure that the store is available to all child components in the tree. This is similar to how you would add a Provider using React’s native context.

      Open src/index.js:

      Import the Provider component from the react-redux package. Add the Provider to your root component around any other components by making the following highlighted changes to your code:

      redux-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';
      import { Provider } from 'react-redux';
      
      ReactDOM.render(
        <React.StrictMode>
          <Provider>
            <App />
          </Provider>
        </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();
      

      Now that you have wrapped your components, it’s time to add a store. The store is your central collection of data. In the next step, you’ll learn to create reducers that will set the default values and update your store, but for now you will hard-code the data.

      Import the createStore function from redux, then pass a function that returns an object. In this case, return an object with a field called birds that points to an array of individual birds. Each bird will have a name and a views count. Save the output of the function to a value called store, then pass the store to a prop called store in the Provider:

      redux-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';
      import { Provider } from 'react-redux';
      import { createStore } from 'redux';
      
      const store = createStore(() => ({
        birds: [
          {
            name: 'robin',
            views: 1
          }
        ]
      }));
      
      ReactDOM.render(
        <React.StrictMode>
          <Provider store={store}>
            <App />
          </Provider>
        </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 close the file. Now that you have some data, you need to be able to display it. Open src/components/App/App.js:

      • nano src/components/App/App.js

      Like with context, every child component will be able to access the store without any additional props. To access items in your Redux store, use a Hook called useSelector from the react-redux package. The useSelector Hook takes a selector function as an argument. The selector function will receive the state of your store as an argument that you will use to return the field you want:

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

      import React from 'react';
      import { useSelector } from 'react-redux';
      import './App.css';
      
      function App() {
        const birds = useSelector(state => state.birds);
      
        return <></>
      }
      
      export default App;
      

      Since useSelector is a custom Hook, the component will re-render whenever the Hook is called. That means that the data—birds—will always be up to date.

      Now that you have the data, you can display it in an unordered list. Create a surrounding <div> with a className of wrapper. Inside, add a <ul> element and loop over the birds array with map(), returning a new <li> item for each. Be sure to use the bird.name as a key:

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

      import React from 'react';
      import { useSelector } from 'react-redux'
      import './App.css';
      
      function App() {
        const birds = useSelector(state => state.birds);
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <ul>
              {birds.map(bird => (
                <li key={bird.name}>
                  <h3>{bird.name}</h3>
                  <div>
                    Views: {bird.views}
                  </div>
                </li>
              ))}
            </ul>
          </div>
        );
      }
      
      export default App;
      

      Save the file. Once the file is saved, the browser will reload and you’ll find your bird list::

      List of birds

      Now that you have a basic list, add in the rest of the components you’ll need for your bird watching app. First, add a button to increment the views after the list of views:

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

      import React from 'react';
      import { useSelector } from 'react-redux'
      import './App.css';
      
      function App() {
        const birds = useSelector(state => state.birds);
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <ul>
              {birds.map(bird => (
                <li key={bird.name}>
                  <h3>{bird.name}</h3>
                  <div>
                    Views: {bird.views}
                    <button><span role="img" aria-label="add">➕</span></button>
                  </div>
                </li>
              ))}
            </ul>
          </div>
        );
      }
      
      export default App;
      

      Next, create a <form> with a single <input> before the bird list so a user can add in a new bird. Be sure to surround the <input> with a <label> and to add a type of submit to the add button to make sure everything is accessible:

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

      import React from 'react';
      import { useSelector } from 'react-redux'
      import './App.css';
      
      function App() {
        const birds = useSelector(state => state.birds);
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <form>
              <label>
                <p>
                  Add Bird
                </p>
                <input type="text" />
              </label>
              <div>
                <button type="submit">Add</button>
              </div>
            </form>
            <ul>
              {birds.map(bird => (
                <li key={bird.name}>
                  <h3>{bird.name}</h3>
                  <div>
                    Views: {bird.views}
                    <button><span role="img" aria-label="add">➕</span></button>
                  </div>
                </li>
              ))}
            </ul>
          </div>
        );
      }
      
      export default App;
      

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

      • nano src/components/App/App.css

      Add some padding to the wrapper class. Then capitalize the h3 element, which holds the bird name. Finally, style the buttons. Remove the default button styles on the add <button> and then add a margin to the form <button>.

      Replace the file’s contents with the following:

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

      
      .wrapper {
          padding: 20px;
      }
      
      .wrapper h3 {
          text-transform: capitalize;
      }
      
      .wrapper form button {
          margin: 10px 0;
          cursor: pointer;
      }
      
      .wrapper ul button {
          background: none;
          border: none;
          cursor: pointer;
      }
      

      Additionally, give each button a cursor of pointer, which will change the cursor when hovering over the button to indicate to the user that the button is clickable.

      Save and close the file. When you do the browser will refresh with your components:

      Bird watching app with form

      The buttons and form are not connected to any actions yet, and so can not interact with the Redux store. You’ll add the actions in Step 2 and connect them in Step 3.

      In this step, you installed Redux and created a new store for your application. You connected the store to your application using Provider and accessed the elements inside your components using the useSelector Hook.

      In the next step, you’ll create actions and reducers to update your store with new information.

      Step 2 — Creating Actions and Reducers

      Next, you’ll create actions to add a bird and to increment a view. You’ll then make a reducer that will update the information depending on the action type. Finally, you’ll use the reducers to create a default store using combineReducers.

      Actions are the message you send to the data store with the intended change. Reducers take those messages and update the shared store by applying the changes depending on the action type. Your components will send the actions they want your store to use, and your reducers will use actions to update the data in the store. You never call reducers directly, and there are cases where one action may impact several reducers.

      There are many different options for organizing your actions and reducers. In this tutorial, you’ll organize by domain. That means your actions and reducers will be defined by the type of feature they will impact.

      Create a directory called store:

      This directory will contain all of your actions and reducers. Some patterns store them alongside components, but the advantage here is that you have a separate point of reference for the shape of the whole store. When a new developer enters the project, they will be able to read the structure of the store at a glance.

      Make a directory called birds inside the store directory. This will contain the actions and reducers specifically for updating your bird data:

      Next, open up a file called birds.js so that you can start to add actions and reducers. If you have a large number of actions and reducers you may want to split them into separate files, such as birds.actions.js and birds.reducers.js, but when there are only a few it can be easier to read when they are in the same location:

      • nano src/store/birds/birds.js

      First, you are going to create actions. Actions are the messages that you send from a component to your store using a method called dispatch, which you’ll use in the next step.

      An action must return an object with a type field. Otherwise, the return object can include any additional information you want to send.

      Create a function called addBirds that takes a bird as an argument and returns an object containing a type of 'ADD_BIRD' and the bird as a field:

      redux-tutorial/src/store/birds/birds.js

      export function addBird(bird) {
        return {
          type: 'ADD_BIRD',
          bird,
        }
      }
      

      Notice that you are exporting the function so that you can later import and dispatch it from your component.

      The type field is important for communicating with reducers, so by convention most Redux stores will save the type to a variable to protect against misspelling.

      Create a const called ADD_BIRD that saves the string 'ADD_BIRD'. Then update the action:

      redux-tutorial/src/store/birds/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      
      export function addBird(bird) {
        return {
          type: ADD_BIRD,
          bird,
        }
      }
      

      Now that you have an action, create a reducer that will respond to the action.

      Reducers are functions that will determine how a state should change based on actions. The actions don’t make changes themselves; the reducers will take the state and make changes based on actions.

      A reducer receives two arguments: the current state and the action. The current state refers to the state for a particular section of the store. Generally, the name of the reducer will match with a field in the store. For example, suppose you had a store shaped like this:

      {
        birds: [
          // collection of bird objects
        ],
        gear: {
          // gear information
        }
      }
      

      You would create two reducers: birds and gear. The state for the birds reducer will be the array of birds. The state for the gear reducer would be the object containing the gear information.

      Inside birds.js create a reducer called birds that takes state and action and returns the state without any changes:

      redux-tutorial/src/store/birds/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      
      export function addBird(bird) {
        return {
          type: ADD_BIRD,
          bird,
        }
      }
      
      function birds(state, action) {
        return state;
      }
      

      Notice that you are not exporting the reducer. You will not use the reducer directly and instead will combine them into a usable collection that you will export and use to create your base store in index.js. Notice also that you need to return the state if there are no changes. Redux will run all the reducers anytime you dispatch an action, so if you don’t return state you risk losing your changes.

      Finally, since Redux returns the state if there are no changes, add a default state using default parameters.

      Create a defaultBirds array that will have the placeholder bird information. Then update the state to include defaultBirds as the default parameter:

      redux-tutorial/src/store/birds/birds

      const ADD_BIRD = 'ADD_BIRD';
      
      export function addBird(bird) {
        return {
          type: ADD_BIRD,
          bird,
        }
      }
      
      const defaultBirds = [
        {
          name: 'robin',
          views: 1,
        }
      ];
      
      function birds(state=defaultBirds, action) {
        return state;
      }
      

      Now that you have a reducer returning your state, you can use the action to apply the changes. The most common pattern is to use a switch on the action.type to apply changes.

      Create a switch statement that will look at the action.type. If the case is ADD_BIRD, spread out the current state into a new array and add the bird with a single view:

      redux-tutorial/src/store/birds/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      
      export function addBird(bird) {
        return {
          type: ADD_BIRD,
          bird,
        }
      }
      
      const defaultBirds = [
        {
          name: 'robin',
          views: 1,
        }
      ];
      
      function birds(state=defaultBirds, action) {
        switch (action.type) {
          case ADD_BIRD:
            return [
              ...state,
              {
                name: action.bird,
                views: 1
              }
            ];
          default:
            return state;
        }
      }
      

      Notice that you are returning the state as the default value. More importantly, you are not mutating state directly. Instead, you are creating a new array by spreading the old array and adding a new value.

      Now that you have one action, you can create an action for incrementing a view.

      Create an action called incrementBird. Like the addBird action, this will take a bird as an argument and return an object with a type and a bird. The only difference is the type will be 'INCREMENT_BIRD':

      redux-tutorial/src/store/birds/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      const INCREMENT_BIRD = 'INCREMENT_BIRD';
      
      export function addBird(bird) {
        return {
          type: ADD_BIRD,
          bird,
        }
      }
      
      export function incrementBird(bird) {
        return {
          type: INCREMENT_BIRD,
          bird
        }
      }
      
      const defaultBirds = [
        {
          name: 'robin',
          views: 1,
        }
      ];
      
      function birds(state=defaultBirds, action) {
        switch (action.type) {
          case ADD_BIRD:
            return [
              ...state,
              {
                name: action.bird,
                views: 1
              }
            ];
          default:
            return state;
        }
      }
      

      This action is separate, but you will use the same reducer. Remember, the actions convey the change you want to make on the data and the reducer applies those changes to return a new state.

      Incrementing a bird involves a bit more than adding a new bird. Inside of birds add a new case for INCREMENT_BIRD. Then pull the bird you need to increment out of the array using find() to compare each name with the action.bird:

      redux-tutorial/src/store/bird/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      ...
      function birds(state=defaultBirds, action) {
        switch (action.type) {
          case ADD_BIRD:
            return [
              ...state,
              {
                name: action.bird,
                views: 1
              }
            ];
          case INCREMENT_BIRD:
            const bird = state.find(b => action.bird === b.name);
            return state;
          default:
            return state;
        }
      }
      

      You have the bird you need to change, but you need to return a new state containing all the unchanged birds as well as the bird you’re updating. Select all remaining birds with state.filter by selecting all birds with a name that does not equal action.name. Then return a new array by spreading the birds array and adding the bird at the end:

      redux-tutorial/src/store/bird/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      ...
      
      function birds(state=defaultBirds, action) {
        switch (action.type) {
          case ADD_BIRD:
            return [
              ...state,
              {
                name: action.bird,
                views: 1
              }
            ];
          case INCREMENT_BIRD:
            const bird = state.find(b => action.bird === b.name);
            const birds = state.filter(b => action.bird !== b.name);
            return [
              ...birds,
              bird,
            ];
          default:
            return state;
        }
      }
      

      Finally, update the bird by creating a new object with an incremented view:

      redux-tutorial/src/store/bird/birds.js

      const ADD_BIRD = 'ADD_BIRD';
      ...
      function birds(state=defaultBirds, action) {
        switch (action.type) {
          case ADD_BIRD:
            return [
              ...state,
              {
                name: action.bird,
                views: 1
              }
            ];
          case INCREMENT_BIRD:
            const bird = state.find(b => action.bird === b.name);
            const birds = state.filter(b => action.bird !== b.name);
            return [
              ...birds,
              {
                ...bird,
                views: bird.views + 1
              }
            ];
          default:
            return state;
        }
      }
      

      Notice that you are not using the reducers to sort the data. Sorting could be considered a view concern since the view displays the information to a user. You could have one view that sorts by name and one view that sorts by view count, so it’s better to let individual components handle the sorting. Instead, keep reducers focused on updating the data, and the component focused on converting the data to a usable view for a user.

      This reducer is also imperfect since you could add birds with the same name. In a production app you would need to either validate before adding or give birds a unique id so that you could select the bird by id instead of name.

      Now you have two complete actions and a reducer. The final step is to export the reducer so that it can initialize the store. In the first step, you created the store by passing a function that returns an object. You will do the same thing in this case. The function will take the store and the action and then pass the specific slice of the store to the reducers along with the action. It would look something like this:

      export function birdApp(store={}, action) {
          return {
              birds: birds(store.birds, action)
          }
      }
      

      To simplify things, Redux has a helper function called combineReducers that combines the reducers for you.

      Inside of birds.js, import combineReducers from redux. Then call the function with birds and export the result:

      redux-tutorial/src/store/bird/birds.js

      import { combineReducers } from 'redux';
      const ADD_BIRD = 'ADD_BIRD';
      const INCREMENT_BIRD = 'INCREMENT_BIRD';
      
      export function addBird(bird) {
        return {
          type: ADD_BIRD,
          bird,
        }
      }
      
      export function incrementBird(bird) {
        return {
          type: INCREMENT_BIRD,
          bird
        }
      }
      
      const defaultBirds = [
        {
          name: 'robin',
          views: 1,
        }
      ];
      
      function birds(state=defaultBirds, action) {
        switch (action.type) {
          case ADD_BIRD:
            return [
              ...state,
              {
                name: action.bird,
                views: 1
              }
            ];
          case INCREMENT_BIRD:
            const bird = state.find(b => action.bird === b.name);
            const birds = state.filter(b => action.bird !== b.name);
            return [
              ...birds,
              {
                ...bird,
                views: bird.views + 1
              }
            ];
          default:
            return state;
        }
      }
      
      const birdApp = combineReducers({
        birds
      });
      
      export default birdApp;
      

      Save and close the file.

      Your actions and reducers are all set up. The final step is to initialize your store using the combined reducers instead of a placeholder function.

      Open src/index.js:

      Import the birdApp from birds.js. Then initialize the store using birdApp:

      redux-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';
      import { Provider } from 'react-redux'
      import { createStore } from 'redux'
      import birdApp from './store/birds/birds';
      
      const store = createStore(birdApp);
      
      ReactDOM.render(
        <React.StrictMode>
          <Provider store={store}>
            <App />
          </Provider>
        </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 close the file. When you do the browser will refresh with your application:

      Bird watching app with form

      In this step you created actions and reducers. You learned how to create actions that return a type and how to build reducers that use the action to build and return a new state based on the action. Finally, you combined the reducers into a function that you used to initialize the store.

      Your Redux store is now all set up and ready for changes. In the next step you’ll dispatch actions from a component to update the data.

      Step 3 — Dispatching Changes in a Component

      In this step, you’ll import and call your actions from your component. You’ll use a method called dispatch to send the action and you’ll dispatch the actions inside of event handlers for the form and the button.

      By the end of this step, you’ll have a working application that combines a Redux store and your custom components. You’ll be able to update the Redux store in real time and will be able to display the information in your component as it changes.

      Now that you have working actions, you need to connect them to your events so that you can update the store. The method you will use is called dispatch and it sends a particular action to the Redux store. When Redux receives an action you have dispatched, it will pass the action to the reducers and they will update the data.

      Open App.js:

      • nano src/components/App/App.js

      Inside of App.js import the Hook useDispath from react-redux. Then call the function to create a new dispatch function:

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

      import React from 'react';
      import { useDispatch, useSelector } from 'react-redux'
      import './App.css';
      
      function App() {
        ...
      }
      
      export default App;
      

      Next you’ll need to import your actions. Remember, actions are functions that return an object. The object is what you will ultimately pass into the dispatch function.

      Import incrementBird from the store. Then create an onClick event on the button. When the user clicks on the button, call incrementBird with bird.name and pass the result to dispatch. To make things more readable, call the incrementBird function inside of dispatch:

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

      import React from 'react';
      import { useDispatch, useSelector } from 'react-redux'
      import { incrementBird } from '../../store/birds/birds';
      import './App.css';
      
      function App() {
        const birds = useSelector(state => state.birds);
        const dispatch = useDispatch();
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <form>
              <label>
                <p>
                  Add Bird
                </p>
                <input type="text" />
              </label>
              <div>
                <button type="submit">Add</button>
              </div>
            </form>
            <ul>
              {birds.map(bird => (
                <li key={bird.name}>
                  <h3>{bird.name}</h3>
                  <div>
                    Views: {bird.views}
                    <button onClick={() => dispatch(incrementBird(bird.name))}><span role="img" aria-label="add">➕</span></button>
                  </div>
                </li>
              ))}
            </ul>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, you’ll be able to increment the robin count:

      Increment a bird

      Next, you need to dispatch the addBird action. This will take two steps: saving the input to an internal state and triggering the dispatch with onSubmit.

      Use the useState Hook to save the input value. Be sure to convert the input to a controlled component by setting the value on the input. Check out the tutorial How To Build Forms in React for a more in-depth look at controlled components.

      Make the following changes to your code:

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

      import React, { useState } from 'react';
      import { useDispatch, useSelector } from 'react-redux'
      import { incrementBird } from '../../store/birds/birds';
      import './App.css';
      
      function App() {
        const [birdName, setBird] = useState('');
        const birds = useSelector(state => state.birds);
        const dispatch = useDispatch();
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <form>
              <label>
                <p>
                  Add Bird
                </p>
                <input
                  type="text"
                  onChange={e => setBird(e.target.value)}
                  value={birdName}
                />
              </label>
              <div>
                <button type="submit">Add</button>
              </div>
            </form>
            <ul>
              ...
            </ul>
          </div>
        );
      }
      
      export default App;
      

      Next, import addBird from birds.js, then create a function called handleSubmit. Inside the handleSubmit function, prevent the page form submission with event.preventDefault, then dispatch the addBird action with the birdName as an argument. After dispatching the action, call setBird('') to clear the input. Finally, pass handleSubmit to the onSubmit event handler on the form:

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

      import React, { useState } from 'react';
      import { useDispatch, useSelector } from 'react-redux'
      import { addBird, incrementBird } from '../../store/birds/birds';
      import './App.css';
      
      function App() {
        const [birdName, setBird] = useState('');
        const birds = useSelector(state => state.birds);
        const dispatch = useDispatch();
      
        const handleSubmit = event => {
          event.preventDefault();
          dispatch(addBird(birdName))
          setBird('');
        };
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <form onSubmit={handleSubmit}>
              <label>
                <p>
                  Add Bird
                </p>
                <input
                  type="text"
                  onChange={e => setBird(e.target.value)}
                  value={birdName}
                />
              </label>
              <div>
                <button type="submit">Add</button>
              </div>
            </form>
            <ul>
              {birds.map(bird => (
                <li key={bird.name}>
                  <h3>{bird.name}</h3>
                  <div>
                    Views: {bird.views}
                    <button onClick={() => dispatch(incrementBird(bird.name))}><span role="img" aria-label="add">➕</span></button>
                  </div>
                </li>
              ))}
            </ul>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, the browser will reload and you’ll be able to add a bird:

      Save new bird

      You are now calling your actions and updating your birds list in the store. Notice that when your application refreshed you lost the previous information. The store is all contained in memory and so a page refresh will wipe the data.

      This list order will also change if you increment a bird higher in the list.

      Robin goes to the bottom on reorder

      As you saw in Step 2, your reducer is not concerned with sorting the data. To prevent an unexpected change in the components, you can sort the data in your component. Add a sort() function to the birds array. Remember that sorting will mutate the array and you never want to mutate the store. Be sure to create a new array by spreading the data before sorting:

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

      import React, { useState } from 'react';
      import { useDispatch, useSelector } from 'react-redux'
      import { addBird, incrementBird } from '../../store/birds/birds';
      import './App.css';
      
      function App() {
        const [birdName, setBird] = useState('');
        const birds = [...useSelector(state => state.birds)].sort((a, b) => {
          return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
        });
        const dispatch = useDispatch();
      
        const handleSubmit = event => {
          event.preventDefault();
          dispatch(addBird(birdName))
          setBird('');
        };
      
        return (
          <div className="wrapper">
            <h1>Bird List</h1>
            <form onSubmit={handleSubmit}>
              <label>
                <p>
                  Add Bird
                </p>
                <input
                  type="text"
                  onChange={e => setBird(e.target.value)}
                  value={birdName}
                />
              </label>
              <div>
                <button type="submit">Add</button>
              </div>
            </form>
            <ul>
              {birds.map(bird => (
                <li key={bird.name}>
                  <h3>{bird.name}</h3>
                  <div>
                    Views: {bird.views}
                    <button onClick={() => dispatch(incrementBird(bird.name))}><span role="img" aria-label="add">➕</span></button>
                  </div>
                </li>
              ))}
            </ul>
          </div>
        );
      }
      
      export default App;
      

      Save the file. When you do, the components will stay in alphabetical order as you increment birds.

      Cardinal stays on top

      It’s important to not try and do too much in your Redux store. Keep the reducers focused on maintaining up-to-date information then pull and manipulate the data for your users inside the component.

      Note: In this tutorial, notice that there is a fair amount of code for each action and reducer. Fortunately, there is an officially supported project called Redux Toolkit that can help you reduce the amount of boilerplate code. The Redux Toolkit provides an opinionated set of utilities to quickly create actions and reducers, and will also let you create and configure your store with less code.

      In this step, you dispatched your actions from a component. You learned how to call actions and how to send the result to a dispatch function, and you connected them to event handlers on your components to create a fully interactive store. Finally, you learned how to maintain a consistent user experience by sorting the data without directly mutating the store.

      Conclusion

      Redux is a popular single store. It can be advantageous when working with components that need a common source of information. However, it is not always the right choice in all projects. Smaller projects or projects with isolated components will be able to use built-in state management and context. But as your applications grow in complexity, you may find that central storage is critical to maintaining data integrity. In such cases, Redux is an excellent tool to create a single unified data store that you can use across your components with minimal effort.

      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

      How To Share State Across React Components with Context


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

      Introduction

      In this tutorial, you’ll share state across multiple components using React context. React context is an interface for sharing information with other components without explicitly passing the data as props. This means that you can share information between a parent component and a deeply nested child component, or store site-wide data in a single place and access them anywhere in the application. You can even update data from nested components by providing update functions along with the data.

      React context is flexible enough to use as a centralized state management system for your project, or you can scope it to smaller sections of your application. With context, you can share data across the application without any additional third-party tools and with a small amount of configuration. This provides a lighter weight alternative to tools like Redux, which can help with larger applications but may require too much setup for medium-sized projects.

      Throughout this tutorial, you’ll use context to build an application that use common data sets across different components. To illustrate this, you’ll create a website where users can build custom salads. The website will use context to store customer information, favorite items, and custom salads. You’ll then access that data and update it throughout the application without passing the data via props. By the end of this tutorial, you’ll learn how to use context to store data at different levels of the project and how to access and update the data in nested components.

      Prerequisites

      Step 1 — Building the Basis for Your Application

      In this step, you’ll build the general structure of your custom salad builder. You’ll create components to display possible toppings, a list of selected toppings, and customer information. As you build the application with static data, you’ll find how different pieces of information are used in a variety of components and how to identify pieces of data that would be helpful in a context.

      Here’s an example of the application you will build:

      Salad Builder Site

      Notice how there is information that you might need to use across components. For example, the username (which for this sample is Kwame) displays user data in a navigation area, but you may also need user information to identify favorite items or for a checkout page. The user information will need to be accessible by any component in the application. Looking at the salad builder itself, each salad ingredient will need to be able to update the Your Salad list at the bottom of the screen, so you’ll need to store and update that data from a location that is accessible to each component as well.

      Start by hard-coding all the data so that you can work out the structure of your app. Later, you’ll add in the context starting in the next step. Context provides the most value as applications start to grow, so in this step you’ll build several components to show how context works across a component tree. For smaller components or libraries, you can often use wrapping components and lower level state management techniques, like React Hooks and class-based management.

      Since you are building a small app with multiple components, install JSS to make sure there won’t be any class name conflicts and so that you can add styles in the same file as a component. For more on JSS, see Styling React Components.

      Run the following command:

      npm will install the component, and when it completes you’ll see a message like this:

      Output

      + react-jss@10.3.0 added 27 packages from 10 contributors, removed 10 packages andaudited 1973 packages in 15.507s

      Now that you have JSS installed, consider the different components you’ll need. At the top of the page, you’ll have a Navigation component to store the welcome message. The next component will be the SaladMaker itself. This will hold the title along with the builder and the Your Salad list at the bottom. The section with ingredients will be a separate component called the SaladBuilder, nested inside SaladMaker. Each ingredient will be an instance of a SaladItem component. Finally, the bottom list will be a component called SaladSummary.

      Note: The components do not need to be divided this way. As you work on your applications, your structure will change and evolve as you add more functionality. This example is meant to give you a structure to explore how context affects different components in the tree.

      Now that you have an idea of the components you’ll need, make a directory for each one:

      • mkdir src/components/Navigation
      • mkdir src/components/SaladMaker
      • mkdir src/components/SaladItem
      • mkdir src/components/SaladBuilder
      • mkdir src/components/SaladSummary

      Next, build the components from the top down starting with Navigation. First, open the component file in a text editor:

      • nano src/components/Navigation/Navigation.js

      Create a component called Navigation and add some styling to give the Navigation a border and padding:

      [state-context-tutorial/src/components/Navigation/Navigation.js]
      import React from 'react';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        wrapper: {
          borderBottom: 'black solid 1px',
          padding: [15, 10],
          textAlign: 'right',
        }
      });
      
      export default function Navigation() {
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            Welcome, Kwame
          </div>
        )
      }
      

      Since you are using JSS, you can create style objects directly in the component rather than a CSS file. The wrapper div will have a padding, a solid black border, and align the text to the right with textAlign.

      Save and close the file. Next, open App.js, which is the root of the project:

      • nano src/components/App/App.js

      Import the Navigation component and render it inside empty tags by adding the highlighted lines:

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

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

      Save and close the file. When you do, the browser will refresh and you’ll see the navigation bar:

      Navigation Bar

      Think of the navigation bar as a global component, since in this example it’s serving as a template component that will be reused on every page.

      The next component will be the SaladMaker itself. This is a component that will only render on certain pages or in certain states.

      Open SaladMaker.js in your text editor:

      • nano src/components/SaladMaker/SaladMaker.js

      Create a component that has an <h1> tag with the heading:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
          </>
        )
      }
      

      In this code, you are using textAlign to center the component on the page. The role and aria-label attributes of the span element will help with accessibility using Accessible Rich Internet Applications (ARIA).

      Save and close the file. Open App.js to render the component:

      • nano src/components/App/App.js

      Import SaladMaker and render after the Navigation component:

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

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

      Save and close the file. When you do, the page will reload and you’ll see the heading:

      Salad Maker Page

      Next, create a component called SaladItem. This will be a card for each individual ingredient.

      Open the file in your text editor:

      • nano src/components/SaladItem/SaladItem.js

      This component will have three parts: the name of the item, an icon showing if the item is a favorite of the user, and an emoji placed inside a button that will add the item to the salad on click. Add the following lines to SaladItem.js:

      state-context-tutorial/src/components/SaladItem/SaladItem.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        add: {
          background: 'none',
          border: 'none',
          cursor: 'pointer',
        },
        favorite: {
          fontSize: 20,
          position: 'absolute',
          top: 10,
          right: 10,
        },
        image: {
          fontSize: 80
        },
        wrapper: {
          border: 'lightgrey solid 1px',
          margin: 20,
          padding: 25,
          position: 'relative',
          textAlign: 'center',
          textTransform: 'capitalize',
          width: 200,
        }
      });
      
      export default function SaladItem({ image, name }) {
        const classes = useStyles();
        const favorite = true;
        return(
          <div className={classes.wrapper}>
              <h3>
                {name}
              </h3>
              <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
                {favorite ? '😋' : ''}
              </span>
              <button className={classes.add}>
                <span className={classes.image} role="img" aria-label={name}>{image}</span>
              </button>
          </div>
        )
      }
      
      SaladItem.propTypes = {
        image: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
      }
      

      The image and name will be props. The code uses the favorite variable and ternary operators to conditionally determine if the favorite icon appears or not. The favorite variable will later be determined with context as part of the user’s profile. For now, set it to true. The styling will place the favorite icon in the upper right corner of the card and remove the default border and background on the button. The wrapper class will add a small border and transform some of the text. Finally, PropTypes adds a weak typing system to provide some enforcement to make sure the wrong prop type is not passed.

      Save and close the file. Now, you’ll need to render the different items. You’ll do that with a component called SaladBuilder, which will contain a list of items that it will convert to a series of SaladItem components:

      Open SaladBuilder:

      • nano src/components/SaladBuilder/SaladBuilder.js

      If this were a production app, this data would often come from an Application Programming Interface (API). But for now, use a hard-coded list of ingredients:

      state-context-tutorial/src/components/SaladBuilder/SaladBuilder.js

      import React from 'react';
      import SaladItem from '../SaladItem/SaladItem';
      
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        wrapper: {
          display: 'flex',
          flexWrap: 'wrap',
          padding: [10, 50],
          justifyContent: 'center',
        }
      });
      
      const ingredients = [
        {
          image: '🍎',
          name: 'apple',
        },
        {
          image: '🥑',
          name: 'avocado',
        },
        {
          image: '🥦',
          name: 'broccoli',
        },
        {
          image: '🥕',
          name: 'carrot',
        },
        {
          image: '🍷',
          name: 'red wine dressing',
        },
        {
          image: '🍚',
          name: 'seasoned rice',
        },
      ];
      
      export default function SaladBuilder() {
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            {
              ingredients.map(ingredient => (
                <SaladItem
                  key={ingredient.name}
                  image={ingredient.image}
                  name={ingredient.name}
                />
              ))
            }
          </div>
        )
      }
      

      This snippet uses the map() array method to map over each item in the list, passing the name and image as props to a SaladItem component. Be sure to add a key to each item as you map. The styling for this component adds a display of flex for the flexbox layout, wraps the components, and centers them.

      Save and close the file.

      Finally, render the component in SaladMaker so it will appear in the page.

      Open SaladMaker:

      • nano src/components/SaladMaker/SaladMaker.js

      Then import SaladBuilder and render after the heading:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
          </>
        )
      }
      

      Save and close the file. When you do the page will reload and you’ll find the content:

      Salad Builder with Items

      The last step is to add the summary of the salad in progress. This component will show a list of items a user has selected. For now, you’ll hard-code the items. You’ll update them with context in Step 3.

      Open SaladSummary in your text editor:

      • nano src/components/SaladSummary/SaladSummary.js

      The component will be a heading and an unsorted list of items. You’ll use flexbox to make them wrap:

      state-context-tutorial/src/components/SaladSummary/SaladSummary.jss

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      
      const useStyles = createUseStyles({
        list: {
          display: 'flex',
          flexDirection: 'column',
          flexWrap: 'wrap',
          maxHeight: 50,
          '& li': {
            width: 100
          }
        },
        wrapper: {
          borderTop: 'black solid 1px',
          display: 'flex',
          padding: 25,
        }
      });
      
      export default function SaladSummary() {
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            <h2>Your Salad</h2>
            <ul className={classes.list}>
              <li>Apple</li>
              <li>Avocado</li>
              <li>Carrots</li>
            </ul>
          </div>
        )
      }
      

      Save the file. Then open SaladMaker to render the item:

      • nano src/components/SaladMaker/SaladMaker.js

      Import and add SaladSummary after the SaladBuilder:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </>
        )
      }
      

      Save and close the file. When you do, the page will refresh and you’ll find the full application:

      Salad Builder Site

      There is shared data throughout the application. The Navigation component and the SaladItem component both need to know something about the user: their name and their list of favorites. The SaladItem also needs to update data that is accessible in the SaladSummary component. The components share common ancestors, but passing the data down through the tree would be difficult and error prone.

      That’s where context comes in. You can declare the data in a common parent and then access later without explicitly passing it down the hierarchy of components.

      In this step, you created an application to allow the user to build a salad from a list of options. You created a set of components that need to access or update data that is controlled by other components. In the next step, you’ll use context to store data and access it in child components.

      Step 2 — Providing Data from a Root Component

      In this step, you’ll use context to store the customer information at the root of the component. You’ll create a custom context, then use a special wrapping component called a Provider that will store the information at the root of the project. You’ll then use the useContext Hook to connect with the provider in nested components so you can display the static information. By the end of this step, you’ll be able to provide centralized stores of information and use information stored in a context in many different components.

      Context at its most basic is an interface for sharing information. Many applications have some universal information they need to share across the application, such as user preferences, theming information, and site-wide application changes. With context, you can store that information at the root level then access it anywhere. Since you set the information in a parent, you know it will always be available and it will always be up-to-date.

      To add a context, create a new directory called User:

      • mkdir src/components/User

      User isn’t going to be a traditional component, in that you are going to use it both as a component and as a piece of data for a special Hook called useContext. For now, keep the flat file structure, but if you use a lot of contexts, it might be worth moving them to a different directory structure.

      Next, open up User.js in your text editor:

      • nano src/components/User/User.js

      Inside the file, import the createContext function from React, then execute the function and export the result:

      state-context-tutorial/src/components/User/User.js

      import { createContext } from 'react';
      
      const UserContext = createContext();
      export default UserContext;
      

      By executing the function, you have registered the context. The result, UserContext, is what you will use in your components.

      Save and close the file.

      The next step is to apply the context to a set of elements. To do that, you will use a component called a Provider. The Provider is a component that sets the data and then wraps some child components. Any wrapped child components will have access to data from the Provider with the useContext Hook.

      Since the user data will be constant across the project, put it as high up the component tree as you can. In this application, you will put it at the root level in the App component:

      Open up App:

      • nano src/components/App/App.js

      Add in the following highlighted lines of code to import the context and pass along the data:

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

      import React from 'react';
      import Navigation from '../Navigation/Navigation';
      import SaladMaker from '../SaladMaker/SaladMaker';
      import UserContext from '../User/User';
      
      const user = {
        name: 'Kwame',
        favorites: [
          'avocado',
          'carrot'
        ]
      }
      
      function App() {
        return (
          <UserContext.Provider value={user}>
            <Navigation />
            <SaladMaker />
          </UserContext.Provider>
        );
      }
      
      export default App;
      

      In a typical application, you would fetch the user data or have it stored during a server-side render. In this case, you hard-coded some data that you might receive from an API. You created an object called user that holds the username as a string and an array of favorite ingredients.

      Next, you imported the UserContext, then wrapped Navigation and SaladMaker with a component called the UserContext.Provider. Notice how in this case UserContext is acting as a standard React component. This component will take a single prop called value. That prop will be the data you want to share, which in this case is the user object.

      Save and close the file. Now the data is available throughout the application. However, to use the data, you’ll need to once again import and access the context.

      Now that you have set context, you can start replacing hard-coded data in your component with dynamic values. Start by replacing the hard-coded name in Navigation with the user data you set with UserContext.Provider.

      Open Navigation.js:

      • nano src/components/Navigation/Navigation.js

      Inside of Navigation, import the useContext Hook from React and UserContext from the component directory. Then call useContext using UserContext as an argument. Unlike the UserContext.Provider, you do not need to render UserContext in the JSX. The Hook will return the data that you provided in the value prop. Save the data to a new variable called user, which is an object containing name and favorites. You can then replace the hard-coded name with user.name:

      state-context-tutorial/src/components/Navigation/Navigation.js

      import React, { useContext } from 'react';
      import { createUseStyles } from 'react-jss';
      
      import UserContext from '../User/User';
      
      const useStyles = createUseStyles({
        wrapper: {
          outline: 'black solid 1px',
          padding: [15, 10],
          textAlign: 'right',
        }
      });
      
      export default function Navigation() {
        const user = useContext(UserContext);
        const classes = useStyles();
        return(
          <div className={classes.wrapper}>
            Welcome, {user.name}
          </div>
        )
      }
      

      UserContext worked as a component in App.js, but here you are using it more as a piece of data. However, it can still act as a component if you would like. You can access the same data by using a Consumer that is part of the UserContext. You retrieve the data by adding UserContext.Consumer to your JSX, then use a function as a child to access the data.

      While it’s possible to use the Consumer component, using Hooks can often be shorter and easier to read, while still providing the same up-to-date information. This is why this tutorial uses the Hooks approach.

      Save and close the file. When you do, the page will refresh and you’ll see the same name. But this time it has updated dynamically:

      Salad Builder Site

      In this case the data didn’t travel across many components. The component tree that represents the path that the data traveled would look like this:

      | UserContext.Provider
        | Navigation
      

      You could pass this username as a prop, and at this scale that could be an effective strategy. But as the application grows, there’s a chance that the Navigation component will move. There may be a component called Header that wraps the Navigation component and another component such as a TitleBar, or maybe you’ll create a Template component and then nest the Navigation in there. By using context, you won’t have to refactor Navigation as long as the Provider is up the tree, making refactoring easier.

      The next component that needs user data is the SaladItem component. In the SaladItem component, you’ll need the user’s array of favorites. You’ll conditionally display the emoji if the ingredient is a favorite of the user.

      Open SaladItem.js:

      • nano src/components/SaladItem/SaladItem.js

      Import useContext and UserContext, then call useContext with UserContext. After that, check to see if the ingredient is in the favorites array using the includes method:

      state-context-tutorial/src/components/SaladItem/SaladItem.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { createUseStyles } from 'react-jss';
      
      import UserContext from '../User/User';
      
      const useStyles = createUseStyles({
      ...
      });
      
      export default function SaladItem({ image, name }) {
        const classes = useStyles();
        const user = useContext(UserContext);
        const favorite = user.favorites.includes(name);
        return(
          <div className={classes.wrapper}>
              <h3>
                {name}
              </h3>
              <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
                {favorite ? '😋' : ''}
              </span>
              <button className={classes.add}>
                <span className={classes.image} role="img" aria-label={name}>{image}</span>
              </button>
          </div>
        )
      }
      
      SaladItem.propTypes = {
        image: PropTypes.string.isRequired,
        name: PropTypes.string.isRequired,
      }
      

      Save and close the file. When you do, the browser will refresh and you’ll see that only the favorite items have the emoji:

      Salad Maker with Avocado and Carrot favorited

      Unlike Navigation, the context is traveling much farther. The component tree would look something like this:

      | User.Provider
        | SaladMaker
          | SaladBuilder
            | SaladItem
      

      The information skipped over two intermediary components without any props. If you had to pass the data as a prop all the way through the tree, it would be a lot of work and you’d risk having a future developer refactor the code and forget to pass the prop down. With context, you can be confident the code will work as the application grows and evolves.

      In this step, you created a context and used a Provider to set the data in the component tree. You also accessed context with the useContext Hook and used context across multiple components. This data was static and thus never changed after the initial set up, but there are going to be times when you need to share data and also modify the data across multiple components. In the next step, you’ll update nested data using context.

      Step 3 — Updating Data from Nested Components

      In this step, you’ll use context and the useReducer Hook to create dynamic data that nested components can consume and update. You’ll update your SaladItem components to set data that the SaladSummary will use and display. You’ll also set context providers outside of the root component. By the end of this step, you’ll have an application that can use and update data across several components and you’ll be able to add multiple context providers at different levels of an application.

      At this point, your application is displaying user data across multiple components, but it lacks any user interaction. In the previous step, you used context to share a single piece of data, but you can also share a collection of data, including functions. That means you can share data and also share the function to update the data.

      In your application, each SaladItem needs to update a shared list. Then your SaladSummary component will display the items the user has selected and add it to the list. The problem is that these components are not direct descendants, so you can’t pass the data and the update functions as props. But they do share a common parent: SaladMaker.

      One of the big differences between context and other state management solutions such as Redux is that context is not intended to be a central store. You can use it multiple times throughout an application and initiate it at the root level or deep in a component tree. In other words, you can spread your contexts throughout the application, creating focused data collections without worrying about conflicts.

      To keep context focused, create Providers that wrap the nearest shared parent when possible. In this case, that means, rather than adding another context in App, you will add the context in the SaladMaker component.

      Open SaladMaker:

      • nano src/components/SaladMaker/SaladMaker.js

      Then create and export a new context called SaladContext:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React, { createContext } from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export const SaladContext = createContext();
      
      export default function SaladMaker() {
        const classes = useStyles();
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </>
        )
      }
      

      In the previous step, you made a separate component for your context, but in this case you are creating it in the same file that you are using it. Since User does not seem related directly to the App, it might make more sense to keep them separate. However, since the SaladContext is tied closely to the SaladMaker component, keeping them together will create more readable code.

      In addition, you could create a more generic context called OrderContext, which you could reuse across multiple components. In that case, you’d want to make a separate component. For now, keep them together. You can always refactor later if you decide to shift to another pattern.

      Before you add the Provider think about the data that you want to share. You’ll need an array of items and a function for adding the items. Unlike other centralized state management tools, context does not handle updates to your data. It merely holds the data for use later. To update data, you’ll need to use other state management tools such as Hooks. If you were collecting data for the same component, you’d use either the useState or useReducer Hooks. If you are new to these Hooks, check out How To Manage State with Hooks on React Components.

      The useReducer Hook is a good fit since you’ll need to update the most recent state on every action.

      Create a reducer function that adds a new item to a state array, then use the useReducer Hook to create a salad array and a setSalad function:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React, { useReducer, createContext } from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export const SaladContext = createContext();
      
      function reducer(state, item) {
        return [...state, item]
      }
      
      export default function SaladMaker() {
        const classes = useStyles();
        const [salad, setSalad] = useReducer(reducer, []);
        return(
          <>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </>
        )
      }
      

      Now you have a component that contains the salad data you want to share, a function called setSalad to update the data, and the SaladContext to share the data in the same component. At this point, you need to combine them together.

      To combine, you’ll need to create a Provider. The problem is that the Provider takes a single value as a prop. Since you can’t pass salad and setSalad individually, you’ll need to combine them into an object and pass the object as the value:

      state-context-tutorial/src/components/SaladMaker/SaladMaker.js

      import React, { useReducer, createContext } from 'react';
      import { createUseStyles } from 'react-jss';
      import SaladBuilder from '../SaladBuilder/SaladBuilder';
      import SaladSummary from '../SaladSummary/SaladSummary';
      
      const useStyles = createUseStyles({
        wrapper: {
          textAlign: 'center',
        }
      });
      
      export const SaladContext = createContext();
      
      function reducer(state, item) {
        return [...state, item]
      }
      
      export default function SaladMaker() {
        const classes = useStyles();
        const [salad, setSalad] = useReducer(reducer, []);
        return(
          <SaladContext.Provider value={{ salad, setSalad }}>
            <h1 className={classes.wrapper}>
              <span role="img" aria-label="salad">🥗 </span>
                Build Your Custom Salad!
                <span role="img" aria-label="salad"> 🥗</span>
            </h1>
            <SaladBuilder />
            <SaladSummary />
          </SaladContext.Provider>
        )
      }
      

      Save and close the file. As with Navigation, it may seem unnecessary to create a context when the SaladSummary is in the same component as the context. Passing salad as a prop is perfectly reasonable, but you may end up refactoring it later. Using context here keeps the information together in a single place.

      Next, go into the SaladItem component and pull the setSalad function out of the context.

      Open the component in a text editor:

      • nano src/components/SaladItem/SaladItem.js

      Inside SaladItem, import the context from SaladMaker, then pull out the setSalad function using destructuring. Add a click event to the button that will call the setSalad function. Since you want a user to be able to add an item multiple times, you’ll also need to create a unique id for each item so that the map function will be able to assign a unique key:

      state-context-tutorial/src/components/SaladItem/SaladItem.js

      import React, { useReducer, useContext } from 'react';
      import PropTypes from 'prop-types';
      import { createUseStyles } from 'react-jss';
      
      import UserContext from '../User/User';
      import { SaladContext } from '../SaladMaker/SaladMaker';
      
      const useStyles = createUseStyles({
      ...
      });
      
      const reducer = key => key + 1;
      export default function SaladItem({ image, name }) {
        const classes = useStyles();
        const { setSalad } = useContext(SaladContext)
        const user = useContext(UserContext);
        const favorite = user.favorites.includes(name);
        const [id, updateId] = useReducer(reducer, 0);
        function update() {
          setSalad({
            name,
            id: `${name}-${id}`
          })
          updateId();
        };
        return(
          <div className={classes.wrapper}>
              <h3>
                {name}
              </h3>
              <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
                {favorite ? '😋' : ''}
              </span>
              <button className={classes.add} onClick={update}>
                <span className={classes.image} role="img" aria-label={name}>{image}</span>
              </button>
          </div>
        )
      }
      ...
      

      To make the unique id, you’ll use the useReducer Hook to increment a value on every click. For the first click, the id will be 0; the second will be 1, and so on. You’ll never display this value to the user; this will just create a unique value for the mapping function later.

      After creating the unique id, you created a function called update to increment the id and to call setSalad. Finally, you attached the function to the button with the onClick prop.

      Save and close the file. The last step is to pull the dynamic data from the context in the SaladSummary.

      Open SaladSummary:

      • nano src/components/SaladSummary/SaladSummary.js

      Import the SaladContext component, then pull out the salad data using destructuring. Replace the hard-coded list items with a function that maps over salad, converting the objects to <li> elements. Be sure to use the id as the key:

      state-context-tutorial/src/components/SaladSummary/SaladSummary.js

      import React, { useContext } from 'react';
      import { createUseStyles } from 'react-jss';
      
      import { SaladContext } from '../SaladMaker/SaladMaker';
      
      const useStyles = createUseStyles({
      ...
      });
      
      export default function SaladSummary() {
        const classes = useStyles();
        const { salad } = useContext(SaladContext);
        return(
          <div className={classes.wrapper}>
            <h2>Your Salad</h2>
            <ul className={classes.list}>
              {salad.map(({ name, id }) => (<li key={id}>{name}</li>))}
            </ul>
          </div>
        )
      }
      

      Save and close the file. When you do, you will be able to click on items and it will update the summary:

      Adding salad items

      Notice how the context gave you the ability to share and update data in different components. The context didn’t update the items itself, but it gave you a way to use the useReducer Hook across multiple components. In addition, you also had the freedom to put the context lower in the tree. It may seem like it’s best to always keep the context at the root, but by keeping the context lower, you don’t have to worry about unused state sticking around in a central store. As soon as you unmount a component, the data disappears. That can be a problem if you ever want to save the data, but in that case, you just need to raise the context up to a higher parent.

      Another advantage of using context lower in your application tree is that you can reuse a context without worrying about conflicts. Suppose you had a larger app that had a sandwich maker and a salad maker. You could create a generic context called OrderContext and then you could use it at multiple points in your component without worrying about data or name conflicts. If you had a SaladMaker and a SandwichMaker, the tree would look something like this:

      | App
        | Salads
          | OrderContext
            | SaladMaker
        | Sandwiches
          | OrderContext
            | SandwichMaker
      

      Notice that OrderContext is there twice. That’s fine, since the useContext Hook will look for the nearest provider.

      In this step you shared and updated data using context. You also placed the context outside the root element so it’s close to the components that need the information without cluttering a root component. Finally, you combined context with state management Hooks to create data that is dynamic and accessible across several components.

      Conclusion

      Context is a powerful and flexible tool that gives you the ability to store and use data across an application. It gives you the ability to handle distributed data with built-in tools that do not require any additional third party installation or configuration.

      Creating reusable contexts is important across a variety of common components such as forms that need to access data across elements or tab views that need a common context for both the tab and the display. You can store many types of information in contexts including themes, form data, alert messages, and more. Context gives you the freedom to build components that can access data without worrying about how to pass data through intermediary components or how to store data in a centralized store without making the store too large.

      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