Module 7: Workout Edit

In this module, we'll create a component that allows us to edit our workout.

Step 1. Setting Up Our Component

Inside of WorkoutEdit.js start it off with the following code:

import React from 'react';
import { Button, Form, FormGroup, Label, Input, Modal, ModalHeader, ModalBody } from 'reactstrap';

class WorkoutEdit extends React.Component {

    constructor(props) {
        super(props)

        this.state = {
            id: '', //1
            result: '',
            description: '',
            definition: ''
        };
    }
    render() {
        return (
            <div>
                WorkoutEdit Component
            </div>
        )
    }
}

export default WorkoutEdit;

Analysis

  1. This should look very familiar to the start for the WorkoutCreate component, except that we have an extra field in our state id. This is because when we need to edit something, we're going to have to include the id of it, in order to be able to edit it.

Step 2. Adding in the Ability to Edit to WorkoutIndex.js

Before we work on fleshing out our WorkoutEdit component, we need to create a way to update a workout in our WorkoutIndex.js. We have the majority of our logic in this file right now, so we just need to add the capability of editing. Create the following method in WorkoutIndex.js:

  constructor(props) {
    super(props)
    this.state = {
      workouts: [],
      updatePressed: false, // added this line
      workoutToUpdate: {} // added this line
    }
  }
workoutUpdate = (event, workout) => {
    fetch(`http://localhost:3000/api/log/${workout.id}`, {
      method: 'PUT',
      body: JSON.stringify({ log: workout }),
      headers: new Headers({
        'Content-Type': 'application/json',
        'Authorization': this.props.token
      })
    })
    .then((res) => {
      this.setState({ updatePressed: false })
      this.fetchWorkouts();
    })
  }

Things to note here: 1. we're passing in a workout in addition to an event 2. we're using the PUT method 3. again, just like all of the other fetches, we're passing the Authorization token 4. we're setting the state of updatePressed to false (we originally make this true when we press the update button) 5. we're also calling this.fetchWorkouts() after we've edited our workout, to grab all of the workouts

And then we need to create a function that will set our updated workout in our state so that we can use it.

setUpdatedWorkout = (event, workout) => {
    this.setState({
        workoutToUpdate: workout, //2
        updatePressed: true //1
    })
}

So that when the user presses the "update" button this will run, and trigger the ability to update things.

Analysis

  1. We're setting up this updatePressed boolean, so that we can check in our render whether or not to render the edit component based on whether or not someone has pressed the update button.

  2. We're also saving the workout we want to update to the state so that we can use it. Still in WorkoutIndex.js add the following function:

Then we update our render with our <WorkoutEdit> component. Make your WorkoutIndex.js render return look like this. Really, you're just adding the bottom <Col md="12"> code.

    return (
      <Container>
        <Row>
          <Col md="3">
            <WorkoutCreate token={this.props.token} updateWorkoutsArray={this.fetchWorkouts} />
          </Col>
          <Col md="9">
            {workouts}
          </Col>
        </Row>
        {/* adding edit */}
        <Col md="12">  
          {
              //1
            this.state.updatePressed ? <WorkoutEdit t={this.state.updatePressed} update={this.workoutUpdate} workout={this.state.workoutToUpdate} /> //2
            : <div></div>
          }
        </Col>
      </Container>
    )

Things to note about the above code (under the adding edit comment): 1. We're checking to see if update has been pressed, based on the boolean in our state (remember this can be changed by the function we wrote above), if it has been we're going to display <WorkoutEdit>, if not we'll display an empty div. We're also passing <WorkoutEdit/> some props, like the workoutUpdate function, and the workout information to update as workout. Refer back to these props later when you're creating WorkoutEdit if you need to remember what has been passed. 2. pay attention to all of the props you are sending to WorkoutEdit here. We've made our update API calls here in WorkoutIndex, so we can just pass them to the children to use. So that way we know if we need to make any changes to anything relating to API calls and that logic with workouts, this is the file we need to look at.

Step 3. Handle Change and Submit

Let's go back to WorkoutEdit now. Just like in our WorkoutCreate component we need functions to handle when a user types something into an input. We also need a method to handle when the form is submitted. Let's create those functions, put them below your constructor but above the render:

handleChange = (event) => { //1
    this.setState({
        [event.target.name]: event.target.value
    })
}

handleSubmit = (event) => { //2
    event.preventDefault();
    this.props.update(event, this.state)
}

Analysis

  1. Our handleChange is exactly the same as in WorkoutCreate

  2. handleSubmit is a bit different. Notice that it calls an update function passed down through props, and passes it our state. This way we don't have to deal with the token and things in this component, but can just do it in the parent.

Step 4. ComponentWillMount

In this component, when it is active, we want it to have the information of the workout we are editing pre-populated into our form. In order to do this, we need to have the existing workout information saved in our state. So, once we get our props, we can then use those props to set our state. If you google which lifecycle method is best to do this in, you'll find that it is componentWillMount, so we'll go ahead and use that method to do this. Write the following code above your handleChange and below the constructor:

componentWillMount() {
    this.setState({ //1
        id: this.props.workout.id, //2
        result: this.props.workout.result,
        description: this.props.workout.description,
        definition: this.props.workout.definition
    })
}

Anaylsis

  1. In this method, we're just setting the state based on the workout we received as a prop. Now, when we have our form, we'll start with these values, so it'll be friendly for our users to use.

  2. Especially important is the id because we can't do a PUT request without that information.

Step 5. Render

In our render, we'll create something very similar to what we did in WorkoutCreate, because we need to capture the same information. The main difference is that instead of a div, we're going to use a modal here instead! Check out the below code:

render() {
    return (
        <div>
            <Modal isOpen={true} > //1
                <ModalHeader >Log a Workout</ModalHeader>
                <ModalBody>
                    <Form onSubmit={this.handleSubmit} >
                        <FormGroup>
                            <Label for="result">Result</Label>
                            <Input id="result" type="text" name="result" value={this.state.result} //2
                            placeholder="enter result" onChange={this.handleChange} />
                        </FormGroup>
                        <FormGroup>
                            <Label for="def">Type</Label>
                            <Input type="select" name="definition" id="definition" value={this.state.definition} onChange={this.handleChange} placeholder="Type">
                                <option></option>
                                <option value="Time">Time</option>
                                <option value="Weight">Weight</option>
                                <option value="Distance">Distance</option>
                            </Input>
                        </FormGroup>
                        <FormGroup>
                            <Label for="description">Notes</Label>
                            <Input id="description" type="text" name="description" value={this.state.description} placeholder="enter description" onChange={this.handleChange} />
                        </FormGroup>
                        <Button type="submit" color="primary"> Submit </Button>
                    </Form>
                </ModalBody>

            </Modal>

        </div>
    )
}

Things to Note: 1. isOpen = true this will keep the modal open so that we can see it 2. the value of the Inputs is set to corresponding field in the state so that when we load it up, it'll show the existing information.

This is What it Should Look Like

Completed Code

import React from 'react';
import { Button, Form, FormGroup, Label, Input, Modal, ModalHeader, ModalBody } from 'reactstrap';

class WorkoutEdit extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            id: '',
            result: '',
            description: '',
            def: ''
        };
    }

    componentWillMount() {
        this.setState({
            id: this.props.workout.id,
            result: this.props.workout.result,
            description: this.props.workout.description,
            definition: this.props.workout.definition
        })
    }

    handleChange = (event) => {
        this.setState({
            [event.target.name]: event.target.value
        })
    }

    handleSubmit = (event) => {
        event.preventDefault();
        this.props.update(event, this.state)
    }

    render() {
        return (
            <div>
                <Modal isOpen={true} >
                    <ModalHeader >Log a Workout</ModalHeader>
                    <ModalBody>
                        <Form onSubmit={this.handleSubmit} >
                            <FormGroup>
                                <Label for="result">Result</Label>
                                <Input id="result" type="text" name="result" value={this.state.result} placeholder="enter result" onChange={this.handleChange} />
                            </FormGroup>
                            <FormGroup>
                                <Label for="definition">Type</Label>
                                <Input type="select" name="definition" id="definition" value={this.state.definition} onChange={this.handleChange} placeholder="Type">
                                    <option></option>
                                    <option value="Time">Time</option>
                                    <option value="Weight">Weight</option>
                                    <option value="Distance">Distance</option>
                                </Input>
                            </FormGroup>
                            <FormGroup>
                                <Label for="description">Notes</Label>
                                <Input id="description" type="text" name="description" value={this.state.description} placeholder="enter description" onChange={this.handleChange} />
                            </FormGroup>
                            <Button type="submit" color="primary"> Submit </Button>
                        </Form>
                    </ModalBody>

                </Modal>

            </div>
        )
    }
}

export default WorkoutEdit;

Now, our app is all done! Make sure you understand all of the code and how the data and functions are being passed between each component.

Step 6. Project Assignment

Here is a link to the project assignment for your PERN application for Blue Badge.

Use your workout log as a template for your application. Do not keep your functions and variables named after workout. The instructors will be displeased if you include the word "workout" in applications that are about anything but workouts.

Last updated