Rendering the Description Based on the Arrow Status
Welcome back to the course, in the last video we got our line styles in a little bit better and we got in the arrow animation put in.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

Welcome back to the course, in the last video we got our line styles in a little bit better and we got in the arrow animation put in.

Well, there's a problem with the arrows where it only animates the first arrow when you click it, but there's a simple fix for that. We just need to give each arrow a unique id, because right now we only have one id. Let's open up our libraryCourse.js, where we will pass in a unique id that can be accessed within the arrow class. we'll add to line 18 id={this.props.id}

libraryCourse.js

import React, { Component } from "react";
import { connect } from 'react-redux';
import * as actions from '../../actions';

import Icon from "../icon";
import Arrow from "../arrow";
import Action from "../action";

class LibraryCourse extends Component {
  render() {
    return (
      <div className="library-course">
        <div className="library-course__title-check">
          <label className="library-course__title">{ this.props.title }</label>
          {Icon("fas fa-check", "library-course__icon")}
        </div>
        <div className="library-course__line"></div>
        <Arrow id={this.props.id} className="library-course__arrow" />
        <Action onClick={() => this.props.toggleEnrolled(this.props.id)} className="library-course__action"/>
        <div className="library-course__description">
          <label>Course Description</label>
          <p>
            { this.props.description }
          </p>
        </div>
      </div>
    );
  }
}

export default connect(null, actions)(LibraryCourse);

We just provided the arrow with a prop. To remind you where this id is coming from let's open up our library.js.

library.js

large

You can see that we're taking our course data and spreading it out with the spread operator and if we look into our actions/index.js file, all of our courses have a unique id already, so we're just grabbing that through our library.

index.js

large

Now, instead of id, we could use a title, description, or an enrolled property. Technically, we could have used description since each of the descriptions are unique, but that would add in a lot of unneccessary data that is un related to the arrow, where as the id is a simple, unique identifier, especially if it's data from a server.

We're assuming that we'll be getting our data from a database, and 100 times out of 100, it's going to have a unique id.

Now let's go to our arrow.js file and use the id that we passed in. it will be a little confusing because we are mentioning id a lot of times, and we have to remember that we have an id for the arrows, as well as the id for our classes.

Here's what we'll put in our render function.

arrow.js

    render() {
        this.id = `arrow-${this.props.id}`
        return (
            <a id="arrow" onClick={() => this.toggleArrow()}className={`${this.props.className} arrow`}></a>
        )
    }
}

This should tell it to set the id to 'arrow-1', 'arrow-2' and so on. Let's update the calls.

arrow.js

    toggleArrow = function() {
        if(this.state.status) {
            // close it
            document.getElementById(this.id).classList.remove('arrow-closed');
        } else {
            // open it
            document.getElementById(this.id).classList.add('arrow-closed');
        }

        this.setState({ status: !this.state.status })
    }.bind(this);

We might have to change something else for state to work, but let's check the browser to see if it worked.

large

And it looks like it's working just fine. I thought there would be a problem with state needing to reload, but I guess not.

With that working. the next thing for us to work on is to hide our description with the arrows. You may be wondering why we didn't just make our arrow inside of libraryCourse.js where it seems like it would be easier to hide the description, but the reason I built it as a separate component is scalablility. We might want to use this arrow in a different application, and it's easy for us to package up the component to put into a framework.

Now, to be able to hide our description we need to provide a callback on our arrow function so that it will register when it is clicked. Line 18 should look like this:

libraryCourse.js

        <Arrow callback={() => console.log('trying to handle callback')} id={this.props.id} className="library-course__arrow" />

Now we need to go to back to arrow.js and call the callback.

arrow.js

    toggleArrow = function() {
        this.props.callback()
        if(this.state.status) {
            document.getElementById(this.id).classList.remove('arrow-closed');
        } else {
            document.getElementById(this.id).classList.add('arrow-closed');
        }

        this.setState({ status: !this.state.status })
    }.bind(this);

Callback is a function that calls a function, but right now we're just going to be logging this to the console instead of calling a function. So if we go to our app, we should see the message being logged to the console when we click on the arrows.

large

As you can see, it works. Now we can do something with this callback. We could set up a separate status call in here like we did for our arrow, but that would be terribly ineffecient, since we've already done that. Instead what we can do is just pass this.state.status through our callback.

arrow.js

    toggleArrow = function() {
        this.props.callback(this.state.status)
        if(this.state.status) {
            document.getElementById(this.id).classList.remove('arrow-closed');
        } else {
            document.getElementById(this.id).classList.add('arrow-closed');
        }

        this.setState({ status: !this.state.status })
    }.bind(this);

Now we can add that in our libraryCourse.js callback.

libraryCourse.js

        <Arrow callback={(status) => this.status = status} id={this.props.id} className="library-course__arrow" />

Now let's hid the description. I'm not going to animate it yet, though, I'm just going to have it hide and unhide when I click for now.

Let's take our description call and wrap it all up in a function named renderDescription and then bind it. Then we'll add in our jsx code in a conditional. Here's the code:

libraryCourse.js

import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../../actions";

import Icon from "../icon";
import Arrow from "../arrow";
import Action from "../action";

class LibraryCourse extends Component {

  renderDescription = function() {
    if(this.status) {
      <div className="library-course__description">
          <label>Course Description</label>
          <p>
            { this.props.description }
          </p>
        </div>
    }
  }.bind(this);

  render() {
    return (
      <div className="library-course">
        <div className="library-course__title-check">
          <label className="library-course__title">{ this.props.title }</label>
          {Icon("fas fa-check", "library-course__icon")}
        </div>
        <div className="library-course__line"></div>
        <Arrow callback={(status) => (this.status = status)} id={this.props.id} className="library-course__arrow" />
        <Action onClick={() => this.props.toggleEnrolled(this.props.id)} className="library-course__action"/>
        { this.renderDescription() }
      </div>
    );
  }
}

export default connect(null, actions)(LibraryCourse);

Now this.status is goign to be null at first, so we need to specify that everytime the page loads this.status is false.

libraryCourse.js

render() {
    this.status = false
    return (
      <div className="library-course">
        <div className="library-course__title-check">
          <label className="library-course__title">{ this.props.title }</label>
          {Icon("fas fa-check", "library-course__icon")}
        </div>

Let's check it out in the browser. It looks like it isn't working, so let's try something else.

We'll change our callback from just receiveing status immediately, and have it work through a function called handleCallback

libraryCourse.js

import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../../actions";

import Icon from "../icon";
import Arrow from "../arrow";
import Action from "../action";

class LibraryCourse extends Component {

  renderDescription = function() {
    if(this.status) {
      <div className="library-course__description">
          <label>Course Description</label>
          <p>
            { this.props.description }
          </p>
        </div>
    }
  }.bind(this);

  handleCallback = function(status) {
    this.status = status
  }.bind(this)

  render() {
    this.status = false
    return (
      <div className="library-course">
        <div className="library-course__title-check">
          <label className="library-course__title">{ this.props.title }</label>
          {Icon("fas fa-check", "library-course__icon")}
        </div>
        <div className="library-course__line"></div>
        <Arrow callback={status => this.handleCallback(status)} id={this.props.id} className="library-course__arrow" />
        <Action onClick={() => this.props.toggleEnrolled(this.props.id)} className="library-course__action"/>
        { this.renderDescription() }
      </div>
    );
  }
}

export default connect(null, actions)(LibraryCourse);

It looks like it's not working because we're only calling status, and we're not using state which is part of the lifecycle. We'll need to make status a part of state, so we need to build a constructor. With that, we'll also need to change our calls to say state.status instead of just status.

Here's the file:

libraryCourse.js

import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../../actions";

import Icon from "../icon";
import Arrow from "../arrow";
import Action from "../action";

class LibraryCourse extends Component {

  constructor(props) {
    super(props)

    this.state = {
      status: false
    }
  }

  renderDescription = function() {
    if(this.state.status) {
      <div className="library-course__description">
          <label>Course Description</label>
          <p>
            { this.props.description }
          </p>
        </div>
    }
  }.bind(this);

  handleCallback = function(status) {
    this.setState({ status })
  }.bind(this)

  render() {
    return (
      <div className="library-course">
        <div className="library-course__title-check">
          <label className="library-course__title">{ this.props.title }</label>
          {Icon("fas fa-check", "library-course__icon")}
        </div>
        <div className="library-course__line"></div>
        <Arrow callback={status => this.handleCallback(status)} id={this.props.id} className="library-course__arrow" />
        <Action onClick={() => this.props.toggleEnrolled(this.props.id)} className="library-course__action"/>
        { this.renderDescription() }
      </div>
    );
  }
}

export default connect(null, actions)(LibraryCourse);

If we open up our browser we can see that it works some of the time, and it's not opening back up. It seems like status is having problems. we can even console.log it to see if it's calling right as the app refreshes, but it isn't. Let's change our code around to say if(!this.state.status).

libraryCourse.js

  renderDescription = function() {
    if(!this.state.status) {
      <div className="library-course__description">
          <label>Course Description</label>
          <p>
            { this.props.description }
          </p>
        </div>
    }
  }.bind(this);

and this to say true

libraryCourse.js

  constructor(props) {
    super(props)

    this.state = {
      status: true
    }
  }

Okay, it looks like it's working. Obviously we need to work on some styles, but we'll take care of that in the next video.

Let's commit our code.

git status
git add .
git commit -m "rendering description based on arrow status"

See you in the next video

Resources

Code at this stage