One place for hosting & domains

      Manage

      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 Manage Infrastructure Data with Terraform Outputs


      [*]

      Introduction

      Terraform outputs are used to extract information about the infrastructure resources from the project state. Using other features of the Hashicorp Configuration Language (HCL), which Terraform uses, resource information can be queried and transformed into more complex data structures, such as lists and maps. Outputs are useful for providing information to external software, which can operate on the created infrastructure resources.

      In this tutorial, you’ll learn about Terraform output syntax and its parameters by creating a simple infrastructure that deploys Droplets. You’ll also parse the outputs programmatically by converting them to JSON.

      Prerequisites

      Note: This tutorial has specifically been tested with Terraform 0.13.

      Defining Outputs

      In this section, you’ll declare a Droplet, deploy it to the cloud, and learn about outputs by defining one that will show the Droplet’s IP address.

      Assuming you are in the terraform-outputs directory, create and open the droplets.tf file for editing:

      Add the following Droplet resource and output definition:

      terraform-outputs/droplets.tf

      resource "digitalocean_droplet" "web" {
        image  = "ubuntu-18-04-x64"
        name   = "test-droplet"
        region = "fra1"
        size   = "s-1vcpu-1gb"
      }
      
      output "droplet_ip_address" {
        value = digitalocean_droplet.web.ipv4_address
      }
      

      You first declare a Droplet resource, called web. Its actual name in the cloud will be test-droplet, in the region fra1, running Ubuntu 18.04.

      Then, you declare an output called droplet_ip_address. In Terraform, outputs are used to export and show internal and computed values and information about the resources. Here, you set the value parameter, which accepts the data to output, to the IP address of the declared Droplet. At declare time, it’s unknown, but it will become available once the Droplet is deployed. Outputs are shown and accessible after each deployment.

      Save and close the file, then deploy the project by running the following command:

      • terraform apply -var "do_token=${DO_PAT}"

      Enter yes to apply when prompted. The end of the output you’ll see will be similar to this:

      Output

      ... digitalocean_droplet.web: Creating... ... digitalocean_droplet.web: Creation complete after 32s [id=207631771] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: droplet_ip_address = ip_address

      The highlighted IP address belongs to your newly deployed Droplet. Applying the project deploys the resources to the cloud and shows the outputs at the end, when all resource attributes are available. Without the droplet_ip_address output, Terraform would show no further information about the Droplet, except that it’s deployed.

      Outputs can also be shown using the output command:

      The output will list all outputs in the project:

      Output

      droplet_ip_address = ip_address

      You can also query a specific output by name by specifying it as an argument:

      • terraform output output_name

      For droplet_ip_address, the output will consist of the IP address only:

      Output

      ip_address

      Except for specifying the mandatory value, outputs have a few optional parameters:

      • description: embeds short documentation detailing what the output shows.
      • sensitive: accepts a boolean value, which prevents the content of the output from being shown after deploying if set to true.
      • depends_on: a meta parameter available at each resource that allows you to explicitly specify resources the output depends on, that Terraform is not able to automatically deduce during planning.

      The sensitive parameter is useful when the logs of the Terraform deployment will be publicly available, but the output contents should be kept hidden. You’ll now add it to your Droplet resource definition.

      Open droplets.tf for editing and add the highlighted line:

      terraform-outputs/droplets.tf

      resource "digitalocean_droplet" "web" {
        image  = "ubuntu-18-04-x64"
        name   = "test-droplet"
        region = "fra1"
        size   = "s-1vcpu-1gb"
      }
      
      output "droplet_ip_address" {
        value      = digitalocean_droplet.web.ipv4_address
        sensitive = true
      }
      

      Save and close the file when you’re done. You can try deploying the project again by running:

      • terraform apply -var "do_token=${DO_PAT}"

      You’ll see that the output is redacted:

      Output

      digitalocean_droplet.web: Refreshing state... [id=207631771] Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: droplet_ip_address = <sensitive>

      Even if it’s marked as sensitive, the output and its contents will still be available through other channels, such as viewing the Terraform state or querying the outputs directly.

      In the next step, you’ll create a different Droplet and output structure, so destroy the currently deployed ones by running:

      • terraform destroy -var "do_token=${DO_PAT}"

      The output at the very end will be:

      Output

      ... Destroy complete! Resources: 1 destroyed.

      You’ve declared and deployed a Droplet and created an output that shows its IP address. You’ll now learn about using outputs to show more complex structures, such as lists and maps.

      Outputting Complex Structures

      In this section, you’ll deploy multiple Droplets from the same definition using the count keyword, and output their IP addresses in various formats.

      Using the for loop

      You’ll need to modify the Droplet resource definition, so open it for editing:

      Modify it to look like this:

      terraform-outputs/droplets.tf

      resource "digitalocean_droplet" "web" {
        count  = 3
        image  = "ubuntu-18-04-x64"
        name   = "test-droplet-${count.index}"
        region = "fra1"
        size   = "s-1vcpu-1gb"
      }
      

      You’ve specified that three Droplets should be created using the count key and added the current index to the Droplet name, so that you’ll be able to later discern between them. When you’re done, save and close the file.

      Apply the code by running:

      • terraform apply -var "do_token=${DO_PAT}"

      Terraform will plan the creation of three numbered Droplets, called test-droplet-0, test-droplet-1, and test-droplet-2. Enter yes when prompted to finish the process. You’ll see the following output in the end:

      Output

      ... Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

      This means that all three Droplets are successfully deployed and that all information about them is stored in the project state.

      The easiest way to access their resource attributes is to use outputs, though creating one for each of the Droplets is not scalable. The solution is to use the for loop to traverse through the list of Droplets and gather their attributes, or to alternatively use splat expressions. You’ll learn about them later in this step.

      You’ll first define an output that will output the IP addresses of the three Droplets, paired with their names. Open droplets.tf for editing:

      Add the following lines:

      terraform-outputs/droplets.tf

      resource "digitalocean_droplet" "web" {
        count  = 3
        image  = "ubuntu-18-04-x64"
        name   = "test-droplet-${count.index}"
        region = "fra1"
        size   = "s-1vcpu-1gb"
      }
      
      output "droplet_ip_addresses" {
        value = {
          for droplet in digitalocean_droplet.web:
          droplet.name => droplet.ipv4_address
        }
      }
      

      The output value of droplet_ip_addresses is constructed using a for loop. Because it’s surrounded by braces, the resulting type will be a map. The loop traverses the list of Droplets, and for each instance, pairs its name with its IP address and appends it to the resulting map.

      Save and close the file, then try applying the project again:

      • terraform apply -var "do_token=${DO_PAT}"

      Enter yes when prompted and you’ll receive the output contents at the end:

      Output

      Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: droplet_ip_addresses = { "test-droplet-0" = "ip_address" "test-droplet-1" = "ip_address" "test-droplet-2" = "ip_address" }

      The droplet_ip_addresses output details the IP addresses of the three deployed droplets.

      Using the Terraform output command, you can get the contents of the output as JSON using its command argument:

      • terraform output -json droplet_ip_addresses

      The result will be similar to the following:

      Output

      {"test-droplet-0":"ip_address","test-droplet-1":"ip_address","test-droplet-2":"ip_address"}

      JSON parsing is widely used and supported in many programming languages. This way, you can programmatically parse the information about the deployed Droplet resources.

      Using Splat Expressions

      Splat expressions offer a compact way of iterating over all elements of a list, and collecting contents of an attribute from each of them, resulting in a list. A splat expression that would extract the IP addresses of the three deployed droplets would have the following syntax:

      digitalocean_droplet.web[*].ipv4_address
      

      The [*] symbol traverses the list on its left and for each of the elements, takes the contents of its attribute specified on the right. If the reference on the left is not a list by itself, it will be converted to one where it will be the sole element.

      You can open droplets.tf for editing and modify the following lines to implement this:

      terraform-outputs/droplets.tf

      resource "digitalocean_droplet" "web" {
        count  = 3
        image  = "ubuntu-18-04-x64"
        name   = "test-droplet-${count.index}"
        region = "fra1"
        size   = "s-1vcpu-1gb"
      }
      
      output "droplet_ip_addresses" {
        value = digitalocean_droplet.web[*].ipv4_address
      }
      

      After saving the file, apply the project by running the following command:

      • terraform apply -var "do_token=${DO_PAT}"

      You’ll receive output that is now a list, and contains only the IP addresses of the Droplets:

      Output

      Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Outputs: droplet_ip_addresses = [ "ip_address", "ip_address", "ip_address", ]

      To receive the output as JSON, run the following command:

      • terraform output -json droplet_ip_addresses

      The output will be a single array:

      Output

      ["ip_address","ip_address","ip_address"]

      You’ve used outputs together with splat expressions and for loops to export IP addresses of the deployed Droplets. You’ve also received the output contents as JSON, and you’ll now use jq—a tool for dynamically filtering JSON according to given expressions—to parse them.

      Parsing Outputs Using jq

      In this step, you’ll install and learn the basics of jq, a tool for manipulating JSON documents. You’ll use it to parse the outputs of your Terraform project.

      If you’re on Ubuntu, run the following command to install jq:

      On macOS, you can use Homebrew to install it:

      jq applies the provided processing expression on given input, which can be piped in. The easiest task in jq is to pretty print the input:

      • terraform output -json droplet_ip_addresses | jq '.'

      Passing in the identity operator (.) means that the whole JSON document parsed from the input should be outputted without modifications:

      Output

      [ "first_ip_address", "second_ip_address", "third_ip_address" ]

      You can request just the second IP address using the array bracket notation, counting from zero:

      • terraform output -json droplet_ip_addresses | jq '.[1]'

      The output will be:

      Output

      "second_ip_address"

      To make the result of the processing an array, wrap the expression in brackets:

      • terraform output -json droplet_ip_addresses | jq '[.[1]]'

      You’ll get a pretty printed JSON array:

      Output

      [ "second_ip_address" ]

      You can retrieve parts of arrays instead of single elements by specifying a range of indexes inside the brackets:

      • terraform output -json droplet_ip_addresses | jq '.[0:2]'

      The output will be:

      Output

      [ "first_ip_address", "second_ip_address" ]

      The range 0:2 returns the first two elements—the upper part of the range (2) is not inclusive, so only elements at positions 0 and 1 are fetched.

      You can now destroy the deployed resources by running:

      • terraform destroy -var "do_token=${DO_PAT}"

      In this step, you have installed jq and used it to parse and manipulate the output of your Terraform project, which deploys three Droplets.

      Conclusion

      You have learned about Terraform outputs, how they are used to show details about the deployed resources, and how they can be used to export data structures for later external processing. You’ve also seen how to use outputs to show attributes of a single resource, as well as for showing constructed maps and lists containing resource attributes.

      For more detailed information about the features of jq, visit the official docs.

      To learn more about Terraform check out the series: How To Manage Infrastructure with Terraform.

      [*]
      [*]Source link

      How To Create and Manage Tables in SQL


      Introduction

      Tables are the primary organizational structure in SQL databases. They comprise a number of columns that reflect individual attributes of each row, or record, in the table. Being such a fundamental aspect of data organization, it’s important for anyone who works with relational databases to understand how to create, change, and delete tables as needed.

      In this guide, we’ll go over how to create tables in SQL, as well as how to modify and delete existing tables.

      Prerequisites

      In order to follow this guide, you will need a computer running some type of relational database management system (RDBMS) that uses SQL. The instructions and examples in this guide were validated using the following environment:

      Note: Please note that many RDBMSs use their own unique implementations of SQL. Although the commands outlined in this tutorial will work on most RDBMSs, the exact syntax or output may differ if you test them on a system other than MySQL.

      You’ll also need a database and table loaded with some sample data with which you can practice using wildcards. If you don’t have these, you can read the following Connecting to MySQL and Setting up a Sample Database section for details on how to create a database and table which this guide will use in examples throughout.

      Connecting To MySQL and Setting Up a Sample Database

      If your SQL database system runs on a remote server, SSH into your server from your local machine:

      Then open up the MySQL server prompt, replacing sammy with the name of your MySQL user account:

      Create a database named tablesDB:

      • CREATE DATABASE tablesDB;

      If the database was created successfully, you’ll receive output like this:

      Output

      Query OK, 1 row affected (0.01 sec)

      To select the tablesDB database, run the following USE statement:

      Output

      Database changed

      With that, you’re ready to follow the rest of the guide and begin learning about how to create and manage tables in SQL.

      Creating Tables

      To create a table in SQL, use the CREATE TABLE command, followed by your desired name for the table:

      Be aware that, as with every SQL statement, CREATE TABLE statements must end with a semicolon (;).

      This example syntax will create an empty table that doesn’t have any columns. To create a table with columns, follow the table name with a list of column names and their corresponding data types and constraints, bracketed by parentheses and separated by commas:

      • CREATE TABLE table_name (
      • column1_name column1_data_type,
      • column2_name column2_data_type,
      • . . .
      • columnN_name columnN_data_type
      • );

      As an example, say you wanted to create a table to record some information about your favorite parks in New York City. After deciding what attributes you’d like to record about each park, you would then decide on column names for each of those attributes as well as the appropriate data type for each one:

      • parkName: The name of each park. There is a wide variance in the length of park names, so the varchar data type with a maximum length of 30 characters would be appropriate.
      • yearBuilt: The year the park was built. Although MySQL has the year data type, this only allows values from 1901 to 2155. New York City has several parks built before 1901, so you might instead use the int data type.
      • firstVisit: The date of your first visit to each park. MySQL has the date data type which you might use for this column. It stores data in the format of YYYY-MM-DD.
      • lastVisit: The date of your most recent visit to each park. Again, you could use the date type for this.

      To create a table named faveParks with columns that have these names and data types, you would run the following command:

      • CREATE TABLE faveParks (
      • parkName varchar(30),
      • yearBuilt int,
      • firstVisit date,
      • lastVisit date
      • );

      Output

      Query OK, 0 rows affected (0.01 sec)

      Keep in mind that this only creates the table’s structure, as you haven’t added any data to the table.

      You can also create new tables out of existing ones with the CREATE TABLE AS syntax:

      • CREATE TABLE new_table_name AS (
      • SELECT column1, column2, . . . columnN
      • FROM old_table_name
      • );

      Instead of following the new table’s name with a list of columns and their data types, you follow it with AS and then, in parentheses, a SELECT statement that returns whatever columns and data from the original table you’d like to copy over to the new table.

      Note that if the original table’s columns hold any data, all that data will be copied into the new table as well. Also, for clarity, this example syntax includes a SELECT query that only has the requisite FROM clause. However, any valid SELECT statement will work in this place.

      To illustrate, the following command creates a table named parkInfo from two columns in the faveParks table created previously:

      • CREATE TABLE parkInfo AS (
      • SELECT parkName, yearBuilt
      • FROM faveParks
      • );

      Output

      Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0

      If the faveParks table had held any data, the data from its parkName and yearBuilt columns would have been copied to the parkInfo table as well, but in this case both tables will be empty.

      If you try creating a table using the name of an existing table, it will cause an error:

      • CREATE TABLE parkInfo (
      • name varchar(30),
      • squareFootage int,
      • designer varchar(30)
      • );

      Output

      ERROR 1050 (42S01): Table 'parkInfo' already exists

      To avoid this error, you can include the IF NOT EXISTS option in your CREATE TABLE command. This will tell the database to check whether a database with the specified name already exists and, if so, to issue a warning instead of an error:

      • CREATE TABLE IF NOT EXISTS parkInfo (
      • name varchar(30),
      • squareFootage int,
      • designer varchar(30)
      • );

      Output

      Query OK, 0 rows affected, 1 warning (0.00 sec)

      This command will still fail to create a new table, since the table named parkInfo still exists. Notice, though, that this output indicates that the CREATE TABLE statement led to a warning. To view the warning message, run the diagnostic SHOW WARNINGS statement:

      Output

      | Level | Code | Message | +-------+------+---------------------------------+ | Note | 1050 | Table 'parkInfo' already exists | +-------+------+---------------------------------+ 1 row in set (0.00 sec)

      As this output indicates, the same error you received previously has been registered as a warning because you included the IF NOT EXISTS option. This can be useful in certain cases, like when running transactions; an error will cause the entire transaction to fail, while a warning will mean only the statement that caused it will fail.

      Altering Tables

      There are times when you may need to change a table’s definition. This is different from updating the data within the table; instead, it involves changing the structure of the table itself. To do this, you would use the ALTER TABLE syntax:

      • ALTER TABLE table_name ALTER_OPTION sub_options . . . ;

      After beginning the ALTER TABLE statement, you specify the name of the table you want to change. Then, you pass whichever options are available in your RDBMS to perform the alteration you have in mind.

      For example, you may want to rename the table, add a new column, drop an old one, or change a column’s definition. You can continue reading to practice these examples on the faveParks table created previously in the Creating Tables section.

      To change the name of the faveParks table, you could use the RENAME TO syntax. This example changes the faveParks table’s name to faveNYCParks:

      Warning: Be careful when renaming a table. Doing so can cause problems if an application uses the table or other tables in the database reference it.

      • ALTER TABLE faveParks RENAME TO faveNYCParks;

      Output

      Query OK, 0 rows affected (0.01 sec)

      To add a new column, you’d pass the ADD COLUMN option. The following example adds a column named borough, which holds data of the varchar type, but with a maximum length of 20 characters, to the faveNYCParks table:

      • ALTER TABLE faveNYCParks ADD COLUMN borough varchar(20);

      Output

      Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0

      To delete a column and any data it holds from a table, you could use the DROP TABLE syntax. This example command drops the borough column:

      • ALTER TABLE faveNYCParks DROP COLUMN borough;

      Many SQL implementations allow you to change a column’s definition with ALTER TABLE. The following example uses MySQL’s MODIFY COLUMN clause, changing the yearBuilt column to use the smallint data type rather than the original int type:

      • ALTER TABLE faveNYCParks MODIFY COLUMN yearBuilt smallint;

      Be aware that every RDBMS has different options for what you can change with an ALTER TABLE statement. To understand the full scope of what you can do with ALTER TABLE, you should consult your RDBMS’s official documentation to learn what ALTER TABLE options are available for it.

      Here’s the official documentation on the subject for a few popular open-source databases:

      Deleting Tables

      To delete a table and all of its data, use the DROP TABLE syntax:

      Warning: Be careful when running the DROP TABLE command, as it will delete your table and all its data permanently.

      You can delete multiple tables with a single DROP statement by separating their names with a comma and a space, like this:

      • DROP TABLE table1, table2, table3;

      To illustrate, the following command will delete the faveNYCParks and parkInfo tables created earlier in this guide:

      • DROP TABLE IF EXISTS faveNYCParks, parkInfo;

      Note that this example includes the IF EXISTS option. This has the opposite function of the IF NOT EXISTS option available for CREATE TABLE. In this context, IF EXISTS will cause the DROP TABLE statement to return a warning instead of an error message if one of the specified tables doesn’t exist.

      Conclusion

      By reading this guide, you learned how to create, change, and delete tables in SQL-based databases. The commands outlined here should work on any database management system that uses SQL. Keep in mind that every SQL database uses its own unique implementation of the language, so you should consult your DBMS’s official documentation for a more complete description of each command and their full sets of options.

      If you’d like to learn more about working with SQL, we encourage you to check out the other tutorials in this series on How To Use SQL.



      Source link