DEV Community

Lukie Kang
Lukie Kang

Posted on

React Learnings from 2018 - Part 5: All things State

This is part of my React Learning series. Using knowledge gleaned from Wes Bos' React for Beginners. Last time we explored:

  • Events
  • Binding Methods
  • Form submissions
  • Changing URLs in React Router

Before we delve further into user input we need to look at State.

State is a really fundemental concept in React. At its core, it is a simple thing, lets say it outloud together:

State is an object that holds data for a component that is availible to itself and child components

This acts as the single source of truth for all parts of the application to refer to. Whenever State changes, React reacts to the change and updates components that use that information. In this post we will use a User Profile creation as our example data we want to put into state.

The process for adding things to state can be broken down into a few steps:

  1. Generate some data we will want to put into State. This will come from a form submission event in our example.
  2. Set up the state in the App component. An object where we want to put the required data.
  3. Build a method that inputs into the state. This is done on the same component that holds the state
  4. Pass that method to the component that will produce the data we want to input

Building a profile form to submit

This is essentially a recap of our work in using references in my previous post.

  1. The form we build should be a component for easy reuse so lets do that. You can copy an existing component and tweak. Remember to export it, and import and add the form to the component(s) that require it.
  2. The component will return a form, build that according the data you wish to capture. Don't forget a button to submit! Some example fields are:
    • name
    • price
    • status
  3. Add an onSubmit method: onSubmit={this.createProfile}
  4. Add a createProfile property on the component, passing through event : createProfile = (event) => {...}. You can use a method but you must bind it.
  5. In the property. Add the preventDefault method to stop the refresh on submit: event.preventDefault()
  6. Add refs props to your form fields: ref={this.nameRef} for example.
  7. Add the refs to the component: nameRef = React.createRef();. Note that there will be a bunch of references youll need to create..
  8. In the createProfile property we want the refs to be passed to state, so you might want to combine the refs into an object:

    const profile = {
    name: this.nameRef.value.value;
    etc.
    }
    (Note that everything will be a string when plucking values in this way, so you might need to convert depending on what you are capturing)

So if you have done that right, you will have a form that spits out an object containing the values that were set... but were should they go? They should go into the State

Setting up State in the root App Component

The profile object the form creates needs to be sent into State. As this particular information will be used in several components, we have to add it to the App Component. The app component is the parent of all components so state can be shared with its child components. You can use local State for local data.

So on the app.js, we define what we want the empty state to look like. We can either:

  1. Use the constructor() method and define it.
  2. Use a property to set it up.

Let's go with number 2 and our empty state in App will look like this:

state = {
    profiles: {}
}
Enter fullscreen mode Exit fullscreen mode

Building a method to Add to state

Now in order to update state, we first need to have a method that must live in the same component. This method must do three things:

  1. Make a copy of the existing state to avoid mutations.. We can make a variable and spread for this: const profiles = {...this.state.person}
  2. Add the profile to the profiles object: people[profile${Date.now()}] = profile
  3. Add the new profiles object into state using a method call setState:

this.setState({profile: profile})

The finished method should look like this:

addProfile = person => {
    const profiles = {...this.state.profiles}
    profiles[`profile${Date.now()}`] = profile; //Profile here is the output of the form
    this.setState({profiles}) // this setsState for the profiles property.
};
Enter fullscreen mode Exit fullscreen mode

Passing the method to where the data is produced

Now we have a method, how does our form,which is several levels down have access to this method? See the ....'diagram' below

  • App (Method Source)
    • Profiles
      • Add Profile form (Needs to use method)

Well thats a job for our old friend props! We add these props to each component

<Profiles addProfile={this.addProfile}> - In App.js it is a component property

<AddProfileForm addProfile={this.props.addProfile}> - In Profile.js, its part of props.

Use the method we passed down through props

In the AddProfileForm component, in the createProfile Property we add the following line:

this.props.addProfile(person) - This will run the property from App.js.

And there you go, the data in the submitted form should appear in the App.js State Object!

Finishing Touches and Conclusion

Once submitted, since the page doesnt refresh, you can reset the form doing event.currentTarget.reset()

There are quite a few steps in updating a central state from an individual form component but by breaking the job into small pieces you can avoid panic:

  1. The App component needs a state object created.
  2. The App component needs a method created that updates a part of its state with a given input.
  3. A form component is created to get the data with which to update state with.
  4. The inputs are given refs which are set up in the component.
  5. The User fills out inputs and clicks submit button.
  6. The submission triggers the onSubmit event which calls a property on the component
  7. The property gets the forms inputs via the ref and creates an object bundling them together
  8. The property runs the App Component's updateState property method that was passed from the App Component via props.

Just take each little bit at a time and you'll be fine. Of course it doesn't just apply to forms but thats for another post...

CHAPTER 23 - Displaying our State.

This is part 7 of my React Learning series. Using knowledge gleaned from Wes Bos' React for Beginners. Last time we explored:

  • Setting up a State Object
  • Building a form with refs that submits data we wanted to put into State
  • Created a method in the App component that updated the State
  • Passed the method down via Props to allow the submission function to use it.

So in the last post te put things into state, lets look at how to get data from an input into the State. We probably want to display that somewhere so lets get the data from State to our Eyeballs.

This follows these basic steps.

  1. Build a component which will be a template to display our data
  2. Set up a loop to make multiple components for each entry in an object or array.
  3. Add a unique key for each entry to ensure React can reference it correctly.
  4. Use props to pass data from the state to the display component
  5. Use the props in the display component to populate the render method.

The setup example

Let's assume we have an object in our state which is called profiles and contains user profiles objects called user321 or someother unique id. We want to show all of them on a page.

Building the component with placeholder data

Displaying a bunch of profiles sounds like a job for multiple Divs but we should be smarter than just building them on the original page. We should build a component for the profiles each containing a div, makes it nice and reusable.

Let's call it Profile.js. We can copy an existing component and make the class and export default 'Profile'

In the render method we can return a div for each profile, lets do that with just a placeholder inside:

<div classname="single-profile"> PROFILE GOES HERE </div>

Looping through our Profiles Object

Remember to Import the component. I forget that a lot.

Beore we get into loops, Its a good idea to add a profile tag: <Profile/> and we should see our profile placeholder if all goes well.

Now we need to loop through our Profiles Object and render out each one, JSX does not have any native logic capability so we can use JS.

As it is an object we don't get to use the Array helpers of ES6 normally but we can use Object.keys(this.state.profiles) which makes an array of the keys.

So we can do something like this: Object.keys(this.state.profiles).map(key => <Profile</>)
which will return each profile key....almost there! But there is a little niggle...

Adding a unique key

However you will probably notice an error in the console...

Each child in an array or iterator should have a unique 'key' prop.

React wants each element to be uniquely identifiable, this helps it be very performant as it uses the key to find things quick. As our keys are unique, we can add a key property to each Profile tag like so <Profile key={key}/>

This need for unique names is something to be considered when constructing our objects.

Passing data from State to the Profile component

Now we have a component rendering for each object, we need that component to use information from the State. To do this we use.... yup, props. So that profile component now looks like this: <Profile key={key} details={this.state.profiles[key]}/>. The details tag is an abitary one, we can choose anything as long as we are consistant.

That will make each component have a prop called details where all the information from the object lives. From there we can populate the render method of the Profile component with whatever we would like. There are some gotchas though with JSX:

To use the img tag you lose the quotes: <img src={this.props.details.image}>
Same goes for the alt property.

Otherwise its just a case of populating the template with the props. A pro tip is to create a variable or two as this.props.details.xxxx is quite long thing to type each time!

const image = this.props.details.image;
const name = this.props.details.name;
Enter fullscreen mode Exit fullscreen mode

Or we can be extra smart and use destructuring:

const { image, name } = this.props.details;

Conclusion

The process for placing state content into a component that renders it is pretty much all about props, however we need to make sure when we loop through items we do it in a way that allows React to see it as unique. Nothing too taxing, but obviously there is alot of different scenarios this could apply to. But here is what we did:

  1. Build the component
  2. In the containing component, map through the Object.Keys if its an object to make multiple components
  3. Give each component a unique key.
  4. Pass in State data via props
  5. Populate the component's render method with whatever data you want to give it.

Now that we have gone through one way of adding and retriving from state, in my next post we can look at another way of doing so.

October 22 this is where we are at.

CHAPTER 24 - Adding more things to state and Displaying

This is part 8 of my React Learning series. Using knowledge gleaned from Wes Bos' React for Beginners. Last time we:

  • Built a component to display data
  • In the containing component, mapped through the array (Object.Keys if its an object) to make multiple components
  • Gave each component a unique key.
  • Passed in State data via props
  • Populate the component's render method with whatever data you want to give it.

Let's look again at how we add things to state, this time via a onClick event.

The setup

This time we are using the example of a shopping basket, with a number of grocery items that has be added to it. We have three specific components involved:

  1. Grocery - A div for each item, Where we also have an 'Add' button for each item
  2. Order - Where we can see what we have ordered with total for that order.
  3. App - The parent component with a state that contains two objects: - grocery which displays a specific item on sale - order which will contain the order for the user. Currently blank.

So the general idea is to:

  1. Create a function to add the associated grocery to the order, multiple times if needed.
  2. Display the order on the Order component, totting up the prices as required.
  3. On the Groceries component, configure a button with an event method to handle the click to add to the order.

Making an Add to Order function

As we are updating the state on the app component, we want to have the method here as well.
The method must do three things:

  1. Take a copy of the relevent part of state
  2. In this copy, add a grocery to the order or increment the current grocery by 1;
  3. Update the state

Taking a copy of state

This just relies on using the spread operator:

const order = {...this.state.order}

Add to Order or Increment By 1

This uses a little trick where the OR statement runs the first condition if the item exists or else makes it exist by adding a 1.

order[key] = order[key] +1 || 1;

Use setState to Update the State

this.setState({ order })

Note that order is used to update order in the State, thats order:order which shortens in ES6 to order

Here is the method in it's entirety:

addToOrder = key => {
    const order = {...this.state.order}
    order[key] = order[key] +1 || 1;
    this.setState({order});
}
Enter fullscreen mode Exit fullscreen mode

Passing the Method and Key to the Button's Component

The button we want to add lives in the grocery component which shows details about the particular grocery (name, price.. etc)

As app is the parent of the grocery component we need to first pass the method into props: addToOrder={this.addToOrder}

Now we have a slight quandry, the method needs the object key passed into it. As the component is created without visabiity of its own key, how do we get that? The answer is that we need to pass it through as a prop as well...

index={key}

Setting the Event on the Button

First lets create the event on the button: <button onClick={this.handleClick}>

Second, lets create that handleClick function property on the component:

handleClick = () => {this.props.addToOrder(this.props.index)}

Some people might like to put the function on the method inline on the button, but I like seperating it out for clarity.

Cool, if everything has gone to plan we should be able to see items being added to the App state when we click that button. Now for the other side of the equation, displaying what we have just placed into state.

Displaying the State object on our Order form

Now that we have the state getting updated when we hit our button, we probably should show the result of that somewhere. Much of this going to involve dealing with edge cases that may or not apply in your scenario. Essentially we need to pass in the relevent prop and display the data as we loop through each key.

Our order form in this example, needs to show the following things:

  1. The item(s) we have ordered
  2. How many of each item we have ordered
  3. The price of that item mutiplied by the amount.
  4. The whole total.
  5. Check if the grocery item becomes sold out and react accordingly.

Getting the Props

So before we do anything else, in App.js we need a prop to pull in the groceries and order objects:

  • The groceries object contains the name, price and status of each grocery
  • The order object contains how many of each item has been ordered.

We need both to figure out things so pull them into the Order component like so:

<Order groceries={this.state.groceries} order={this.state.order}/>

You might be tempted to add all of the state as thats the only objects in it, but in the sprit of modularity, we cannot be sure that will always be the case so its good practise to be explicit in what you are taking as props.

Working with the Prop Data

In the Order.js file we can build some variables we could use:

An array of Object Keys
const orderIds = Object.keys(this.props.order) - gives us an array of each key in the order object. If we display that using {orderIds} we should each order item appear.

A total of all items
We have to check a few things as we figure out the total via the Reduce array helper method:

    const total = orderIds.reduce((prevTotal, key) => {
        const grocery = this.props.groceries[key] // What
        const count = this.props.order[key];      // How many
        const isAvailable = grocery && grocery.status === "available" // Makes sure it exists and isnt sold out.
        if(isAvailable){
            return prevTotal + (grocery.price * count);
        }
        return prevTotal
    },0 )
Enter fullscreen mode Exit fullscreen mode

Remember to actually display, and style the total somewhere: {total}

Loop over the OrderIds

  1. In the JSX we are returning, make a
      to contain the OrderIds we will display.
  2. In the UL, map over the orderIds to return an list item for each item ordered: OrderIds.map(key => <li>{key} </li>)

The JSX will look something like this:

<ul>
    {orderIds.map(key => <li>{key}</li>)}
</ul>
Enter fullscreen mode Exit fullscreen mode

Render Functions

By now your render function is starting to get a little long, which is a sign too much is going on in your component. We could make a component for the code but if its not quite big enough we can shunt some of the code to a function inside the component, removing it from render

For example we can take the logic for each list item above and put it into a function on the component like so:

renderOrder(key) => {
    return <li>{key}</li>
}
Enter fullscreen mode Exit fullscreen mode

Now the unordered list can look like this: <ul>{orderIds.map(this.renderOrder)}</ul>

It's a fairly basic example but its something to bear in mind as a middle ground between putting everything in render and making another component.

Presumably you will want to show more than just the key for each order, but that's a detail you can figure out for your own needs! Some extra details may include:

  • The grocery name
  • The count of orders for that grocery
  • The price of (grocery.price * count)
  • Check if the grocery is still availible and change the list item should it become unavailible during the order.

Once you start adding that complexity, using a Render method starts making more sense.

Add keys to each List item

As we have created multiple similar list items, React will get a little upset and warn us about the lack of unique keys for the list items.

This is fairly straightforward to sort since our Grocery Object keys are unique:

<li key={key}> {key} </li>

Conclusion

As long as we set up the props and manage the amount of code in our render function there is little React specific knowledge required for displaying State data. Having a good grasp of JS though will make life easier when it comes to getting the right bit of data from our props and displaying it effeciently. the rest is up to what exactly you want to display with your State data.

Top comments (0)