How to Reuse Components with Custom Props in React
Welcome back. Now in the last video we hooked up our detail component to the grid, and we threw in a couple of components. Now this introduces a couple problems because no matter which newsletter we click on in the archive it displays the same data. We're going to refactor this.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

Now the reason it's doing this is that our latest component is clearly fetching the latest newsletter. Now the way we can fix this is by basically getting rid of the redux in it and making it so it takes in a prop that is one of our newsletter components using the spread operator. So pretty easy fix all we have to do is get rid of redux in this newsletter latest component.

newsletterLatest.js

import React, { Component } from 'react';

import Button from '../button';

class NewsletterLatest extends Component {

    handleEdit = () => {
        this.props.history.push(`/newsletter/edit/${this.props._id}`);
    }

    render() {
        const { title, imageUrl, body } = this.props;
        return (
            <div className='newsletter-latest'>
                <h1 className='newsletter-latest__title'>{title}</h1>
                <img className='newsletter-latest__image' src={imageUrl}/>
                <Button className='newsletter-latest__button' callback={() => this.handleEdit()} icon='fas fa-pencil-alt'/>
                <div className='newsletter-latest__body'>
                    <p>{body}</p>
                </div>
            </div>
        )
    }
}

// function mapStateToProps(state) {
//     const { newsletters } = state.newsletters;
//     const latestNewsletter = newsletters[0];
//     return {
//         ...latestNewsletter
//     }
// }

export default NewsletterLatest;

Now if you go to the browser and log in you'll see that we have nothing in here now.

large

And when we click on this item we have nothing in here. Inside of the latest component we're still trying to get these items out of props so you can imagine all we really need to do is somehow get our newsletter into this component in order to have it be whatever newsletter we want. We don't want to only get the latest. We want to either get whatever one we're fetching or the latest. So really simple.

All that means is we have to provide it to this component in the component it's being displayed in. All we have to do is go into newsletterGrid.js and instead of just providing a history let's use the spread operator on the latest components.

Now we don't have access to this latest newsletter yet because we're not using mapStateToProps So let's go and take this function from latest and paste it in our newsletter grid.

newsletterGrid.js

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

import NewsletterBox from './newsletterBox';
import NewsletterArchive from './newsletterArchive';
import NewsletterLatest from './newsletterLatest';
import Button from '../button';

class NewsletterGrid extends Component {

    handleAddNewsletter = () => {
        this.props.history.push('/newsletter/new');
    }

    componentDidMount() {
        setTimeout(() => {
            this.props.fetchNewsletters();
        }, 1000);
    }

    render() {
        return (
            <div className='newsletter-grid'>
                <Button className='newsletter-grid__button' icon='fas fa-plus' callback={() => this.handleAddNewsletter()}/>
                <NewsletterBox/>
                <NewsletterArchive/>
                <NewsletterLatest {...this.props.latestNewsletter} history={this.props.history}/>
            </div>
        )
    }
}

function mapStateToProps(state) {
    const { newsletters } = state.newsletters;
    const latestNewsletter = newsletters[0];
    return {
        latestNewsletter
    }
}

export default connect(mapStateToProps, actions)(NewsletterGrid);

Inside of our mapStateToProps, we have it spreading out latestNewsletter, which isn't what we want it to do because we want to just pass in the whole newsletter into our latest component. So we changed that to just latestNewsletter.

Now when we go into our application, we're good to go.

large

Okay cool, that's great. So the only problem now is when we click on one of our newsletter items, it's still blank, but that's a really easy fix, too because as you know, and as we have done we are already getting this object when we click on it. Now we get it printed out in our console. Okay. So what we need to do is simply go into the detail component and provide it with the newsletter literally just with the spread operator.

newsletterDetail.js

    render() {
        console.log(this.props.newsletterToEdit);
        return (
            <div className='newsletter-detail'>
                <NewsletterBox/>
                <NewsletterLatest {...this.props.newsletterToEdit}/>
            </div>
        )
    }
}

Now let's go check it out.

large

That's all great. It's displaying the one we clicked on. All right cool. So the reason it's displaying this one is because we're taking the id out of our route and then we're fetching it and then it's mapping overall of the items that our reducer is fetching with the id.

Based on that id that we passed into this url, it's getting the right one and returning it to newsletterToEdit which is in our redux store now. Then we're simply taking that out of our store and we are using it in this newsletter latest component.

So it doesn't make sense anymore to be calling this newsletter component newsletterLatest, so what we need to do is just name it newsletter and then like detail-view, or something, but we already have a detail. I'm just going to leave it because we get the idea here, and we have all these classNames so we don't really want to change it right now.

It's basically a small detail view in multiple places. We named a newsletter latest because it originally was only the latest one, but we can apply it in different places because it's the same thing we need. So really nice to be able to reuse components.

Now there's one more thing I wanted to do before we move on. And it's basically the same exact thing but with this box here now it's not I'm not going to go through it as as slowly because you get the idea with the latest newsletters. Let's just go quickly change this so it's receiving the right date.

It's not really going to look any different. But we know it's going to be fetching the right data, so let's go into our newsletter detail and let's do the same thing with the box.

newsletterDetail.js

    render() {
        console.log(this.props.newsletterToEdit);
        return (
            <div className='newsletter-detail'>
                <NewsletterBox date={...this.props.newsletterToEdit.date}/>
                <NewsletterLatest {...this.props.newsletterToEdit}/>
            </div>
        )
    }
}

Now let's go into newsletterBox.js real quick and you'll see that we're fetching the latest newsletter still, and we don't want to do that, so let's just get rid of this mapStateToProps and let's just export it. Now, what we want to do is let's leave this fetching newsletter because we still want to display that if we're fetching them.

newsletterBox.js

import React, { Component } from 'react';

const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

class NewsletterBox extends Component {
    render() {
        const { date } = this.props;
        if(!date) {
            return <div>...fetching newsletters</div>
        }
        return (
            <div className='newsletter-box'>
                <div className='newsletter-box__day'>{date.getDate()}</div>
                <div className='newsletter-box__month-year'>{months[date.getMonth()]} {date.getFullYear()}</div>
                <div className='newsletter-box__point'></div>
            </div>
        )
    }
}

export default NewsletterBox;

Let's make sure we're passing in a date in our newsletter grid component or it's not going to work.

newsletterGrid.js

    render() {
        return (
            <div className='newsletter-grid'>
                <Button className='newsletter-grid__button' icon='fas fa-plus' callback={() => this.handleAddNewsletter()}/>
                <NewsletterBox {...this.props.latestNewsletter}/>
                <NewsletterArchive/>
                <NewsletterLatest {...this.props.latestNewsletter}/>
            </div>
        )
    }
}

That should fix it and we're good to go. Now one more problem that's pretty easy to fix is that in this detail component, when we click on our edit button, we get an error in the console that says that push is not defined. All you need to do is provide the history of that component.

You'll see in newsletter grid, we're providing the history. We can either do that or there is another way of doing it, if you remember. What we can do is in detail, we can just import it into our latest component.

newsletterDetail.js

large

That way we can use this in any place we put the component. And it makes more sense to import it in here than to provide it as a prop because we're always going to need this on this component. So we want it to always be in there. We don't want to make it just a prop that we're passing in.

And it works. We just built that really quickly in, like, two videos. Now there's one more thing we need to do after this video and it's basically aline it all on the grid properly. We don't actually have a grid for this component yet. We want to make this look more like our normal component, and then add in this title.

So let's finish off this component in the next video and then move on.

git status
git add .
git commit -m "refactored newsletter components to take props for custom cases"

All right I'll see you in the next video.

Resources

Code at this stage