Slight Navbar Refactor for eCommerce App
All right welcome back, let's go ahead and continue building out this application.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

We are fetching our products properly right. We can see them in our redux DevTools quite nicely right. And let's see, you can see the individual actions or the individual state changes in inspector.

large

It's like set shop products shows shop and products. I prefer to look at the chart, feel free to look at it the way you want. I'm more visual, I like to look at things in are more visual way.

large

So products selected, we basically need to get this whenever we click on one of these, we want the ID to go into selected category id. And who knows maybe we'll discover a more efficient way going on, but right now off the top of my head I think this is a good way to do it. Okay, so we need to create an action type called set, we need to call it said shop, let's say SET_SELECTED_CATEGORY_ID.

So copy this and let's create a case for it and let's say re turn make you put a colon here, ...state and then we'll override selectedCategoryId. As a matter of fact what we could even do is we don't even have to set the ID, we can just get the product selected immediately. So let's get rid of SET_SELECTED_CATEGORY_ID, I guess we already found a more efficient way of doing it. And let's call this type set products, we'll say FILTER_PRODUCTS_WITH_CATEGORY_ID it's a bit long but whatever, set filter products with category id.

And what we're going to do is we are going to basically filter them out okay. So what I want to do is just get the action in there and the type and then come back and do this, because it takes a little bit of thinking. So go to actions types.js and let's say export const FILTER_PRODUCTS_WITH_CATEGORY_ID = 'FILTER_PRODUCTS_WITH_CATEGORY_ID';

Now we have that in our types so we can go into our shop.js in our actions and we can say, we can import it for one thing at the top here, FILTER_PRODUCTS_WITH_CATEGORY_ID.

And then what we can do is at the top here sense its a smaller one, let's just right here let's say export function FILTER_PRODUCTS_WITH_CATEGORY_ID and we will say it takes in an idea obviously and we're going to return an action object with a type of FILTER_PRODUCTS_WITH_CATEGORY_ID and a payload of _id because that's what we're taking in.

shop.js

import {
    //SET_SHOP_CATEGORIES
    SET_NAVBAR_LINKS,
    SET_SHOP_PRODUCTS,
    FILTER_PRODUCTS_WITH_CATEGORY_ID
} from './types';

export function filterProductsWithCategoryId(_id) {
    return ({
        type: FILTER_PRODUCTS_WITH_CATEGORY_ID,
        payload: _id
    })
}

Now what we need to do is get out of here, we've written it, and go into index.js in our actions folder and let's import it. So filter products category will auto complete if you're using the same set up as me and then let's export it.

index.js

import {
    fetchShopCategories,
    fetchShopProducts,
    filterProductsWithCategoryId
} from './shop';

export {
    setHeaderLinks,
    setNavbarLinks,
    changeNavbarActive,

    fetchUserPurchases,
    setPurchaseDetail,

    fetchShopCategories,
    fetchShopProducts,
    filterProductsWithCategoryId
};

So now we just need to write the code in here to filter them, but before we do that let's go call it and just make sure we're hitting this. So what I want to do is say console.log and then I'm going to say action.payload to make sure we're getting the ID. And then I'm just going to return state as it is. This is the same thing as doing that, we're just mapping it out.

shopReducer.js

case FILTER_PRODUCTS_WITH_CATEGORY_ID:
    console.log(action.payload);
    return {
        ...state,
        // selectedCategoryId
    }

So we want to see this in the console. So let's go ahead and let's go to where we want to call this, so this is where a lot of the thinking comes in other than filtering it, we have to find out how we can basically make it so when we click this it doesn't only do this but it also throws in the id and also calls that method right, the FILTER_PRODUCTS_WITH_CATEGORY_ID. So all we need to do is we need to go to our shop.js and basically what we want to do is we need to pass it in with our navbarLinks but we haven't really made it so we can do that.

So what we need to do is we need to go to, where is it? shop.js in our actions and we need to put that method call in here and then do something with it but the only problem is we don't have access to our actions in here, or do we? We have it right here, wow I didn't even think about that. So maybe you don't even need to export it, I've never actually tried this so I don't even know if this is valid in react I don't know if you're allowed to do this or if it works.

But let's give it a shot okay, we want to save onClick so we're going to have an additional click method. So we want to say onClick, and let's call filterProductsWithCategoryId and let's pass in the id of this object okay, so let's say zero. The only thing I really don't like about this way of doing it is that we kind of have to hard code this all in, so I don't think I want to do it this way, this is kind of nasty.

So what I want to do is I want to go into our navbar.js and let's think of how we can do this okay. We could provide another callback, but that's exactly what I was just doing. I think that's the only way to do it because we didn't do anything with this data we just directly set it.

Another problem with doing it this way with onClick in here is that you're not going to have that on the backend. You don't want to say hey I want to call this front end method and put that data in the back end that just doesn't make sense that's really not good. Okay, so what we need to do is let's go back to navbar.js, we need to do this on this, this.props.changeNavbarActive(link._id).

Really what we should do, is we should be setting it outside out of here so we can do something like that. But again that would require that we use SET_SHOP_CATEGORIES again and then we have to set it back. So here's what we will do, let's uncomment SET_SHOP_CATEGORIES put a comma,

import {
    SET_SHOP_CATEGORIES,
    SET_SHOP_PRODUCTS,
    FILTER_PRODUCTS_WITH_CATEGORY_ID
} from './types';

then let's go into types.js, we have SET_SHOP_CATEGORIES. Okay let's instead of saying SET_NAVBAR_LINKS, I mean we could set both to be honest. And then by doing that we could say hey this is the data we receive from the backend and here's something we want to do on the frontend and dispatch two actions, interesting. But we're just going to do this, SET_SHOP_CATEGORIES, okay.

shop.js

export function fetchShopCategories() {
    return ({
        type: SET_SHOP_CATEGORIES,
        payload: [
            {
                _id: 0,
                title: 'All'
            },

and then what we're going to do is we're going to go into our reducers, our shopReducer.js and we're going to say SET_SHOP_PRODUCTS okay so SET_SHOP_CATEGORIES we need to say case SET_SHOP_CATEGORIES make sure you import that. So import SET_SHOP_CATEGORIES from actions/types.

shopReducer.js

import {
    SET_SHOP_CATEGORIES,
    SET_SHOP_PRODUCTS,
    FILTER_PRODUCTS_WITH_CATEGORY_ID
} from '../actions/types';

Now what we need to do is modify this, so let's say return ...state and let's say categories and we'll say action.payload

return {
    ...state,
    categories: action.payload
}

Okay, so we're setting the shop categories, okay let's go check that out in the browser just so we know everything's working. There's probably an error somewhere. Okay, so login, shop, redux to the left and basically all we're doing now is we're saying hey let's put the categories in our shop reducer now. So our navbarLinks are still purchase and account. So what we need to do is, we need to get this data back and set it and then pass in a callback with each one of those objects.

Okay so let's go into, shop.js in our shop component and let's say fetchShopProducts and then let's have a callback. Okay so we're going to call it done and is going to be an arrow function. Not in products sorry, but in categories okay so we have an arrow function here, and then what we're going to do is we're going to set them now in here, we're going to say this.props.setNavbarLinks and what we're going to do is we're going to modify this method okay, we're going to modify it to take in another optional parameter.

The first one is obviously going to be an array of data, so what we need to do is we need to fetch that from the backend all right, we need to get it from our state. So let's say return and what we're going to do is we're going to say categories. Then we're going to pull categories out of our state so const categories, let's put some braces around that and let's say is equal to state.shop so we're going to pull it out of our shop.

shop.js

function mapStateToProps(state) {
    const { categories } = state.shop;
    return {
        categories
    }
}

Okay this should make sense, we're just basically pulling out our categories from the shop. Okay, we're pulling the categories out of here, we're saying state.shop pull categories out of there so we have access to them in this.props.categories. So now in here we can say this.props.categories and the reason we're doing a callback is because we want to make sure this is asynchronous. We want to make sure that we fetch the categories before we try and do anything with them, because these aren't going to exist immediately after we say fetch because it's asynchronous, okay, we want to make sure these are fetched okay.

So let's go into fetchShopCategories in our shop.js and we need to provide a callback so let's call this done, and then what we'll do is after we... the only problem is we're dispatching it this way. So what we need to do is we need to pass done in with this payload okay. So what we want to do is we want to make this an object and then let's do this, let's pull out the entire payload right, so the entire object there, and let's say category's.

Let's paste it back in and now we have categories in there still and we're going to have to modify our reducer a bit for this to work. And then what we're going to have to do is say callback or we'll say done right and we'll say done so we're passing done in with it. And then what we're doing is we need to go into shopReducers.js and instead of just saying action.payload on categories we need to say action.payload.categories.

Okay, we could even say const categories done is equal to action.payload and that's what we should do. Now what we can do is we can just say categories and then we can say done. Now the only problem with this is that we're returning so we can't call done after it, so that's a problem. So this probably won't work but let's try it.

export default function(state = INITIAL_STATE, action) {
    switch (action.type) {
        case SET_SHOP_CATEGORIES;
            const { categories, done } = action.payload;
            done();
            return {
                ...state,
                categories 
            }

Now what we need to do is we need to see what's happening when we do this. So let's go into Chrome and it says there's an error, okay it says reducers may not dispatch actions.

large

Okay, why is that happening? Okay, so probably because it's saying done is an action. Let's see what happens when we get rid of that done call, looks like it works.

export default function(state = INITIAL_STATE, action) {
    switch (action.type) {
        case SET_SHOP_CATEGORIES;
            const { categories, done } = action.payload;

            return {
                ...state,
                categories 
            }

What I want to do is let's go back, let's call this let's say categories is action.payload and then we'll go into shop.js and actions and we'll get rid of done and payload and we'll rename this to payload, then we'll get rid of this bracket. Okay so basically we're left with what we basically had at the beginning of this video with the payload at least.

Now what we're going to do is we are going to... let's see happens when we say done right here? Okay, it seems like it could be working, the only problem is it's not setting our navbar links. Okay so let's go into here, let's go into shop.js and let's say console.log('done');

Well I guess what we can do is get rid of the done entirely, and we can just get rid of this entire done callback. The thing is this would work perfectly if we were actually making the call, but we're not, okay so let's do it in render this.props.setNavbarLinks(this.props.categories); the only problem here is that you have to make categories a required Prop okay.

So what we want to do is we want to call it immediately after here, let's say this.props.setNavbarLinks(this.props.categories); Okay, this isn't going to work immediately, see it says done is not a callback,

large

let's get rid of that. So let's go into shop.js in our actions, get rid of done. All right, it didn't work right? That's because when we're initially calling this.props.setNavbarLinks we don't have the categories. So what we need to do is we need to get rid of this and implement another component lifecycle method and say componentWillReceiveProps right. And then you'll see if you wait a second, it'll say it takes in, okay so it's deprecated, I didn't know they updated that. Okay, componentDidUpdate

large

Okay, this is not deprecated and it takes in previous props, why don't they have one with new props? Okay you can't even access it with vs code anymore. previousState, previousProps, does it have next props? Huh okay, shouldComponentUpdate is not deprecated has next props.

So we want to use this one, we want to take in nextProps and then we want to say this, and say nextProps and the next thing we want to do is, this might run even if we don't receive new props. So let's say if this.props does not equal nextProps so we're only wanting to call this if the props have updated because obviously we've already set it with the non updated props.

shop.js

shouldComponentUpdate(nextProps) {
    if(this.props != nextProps ) {
        this.props.setNavbarLinks(nextProps);
    }
}

Okay so let's go ahead and let's call this and it says return undefined.

large

So we have to return a boolean right here, we have to say return True okay.

shouldComponentUpdate(nextProps) {
    if(this.props != nextProps ) {
        this.props.setNavbarLinks(nextProps);
    }
    return true
}

Now let's try this, I swear if this doesn't work I'm going to re-record it, okay, this.props.navbarLinks.map is not a function.

large

Because I'm passing in nextProps let's say nextProps.categories there we go, this should work.

shouldComponentUpdate(nextProps) {
    if(this.props != nextProps ) {
        this.props.setNavbarLinks(nextProps.categories);
    }
    return true
}

Let's go ahead and go to Chrome and you see that we have our categories.

large

So that was a huge, it was like a big debugged session right, that took a second I didn't really know what was going on the whole time. I'm not going to edit it out though, even though I could rerecord that in a few minutes because I think it's important that you see that you're going to run into problems like that. It's never going to be just a continuous flow of beautiful coding, it's hard, so let's go ahead and see chart and see what we are doing.

I don't know what we did before that, I forgot. Let's see setNavbarLinks we're setting the links now, let's see what we have. Product selected, okay so we're probably hitting it. Okay so we're selecting it, so setNavbarLink. Okay, literally all that was, was just making it so it doesn't set straight from the reducer which means, that's ridiculous okay, we need to pass in another prop into this or another parameter so we can basically say here's an additional onClick we want to implement so this is going to be for every link.

So let's say this.props.filterProductsWithCategoryId, now we want to call this and then we want to pass in the id of the deal okay. So what we're going to do is we are going to have it be an arrow function and this arrow function is going to take an ID and then this arrow function is going to call this.props.filterProductsWithCategoryId. And the reason we're doing it this way is so that we can take this function and later inside the navbar we can say Hey take the current one and throw the ID in with this function.

Okay, so let's go into setNavbarLinks in our headernavbar.js in the actions, and let's have it take in this additional onClick and what we'll do is we will have it take the links in with the payload and we will have it take in the onClick. The only thing that isn't good about this is we're going to have to modify some of our other code. Okay, we have links and we have onClick, so we need to pass in this as an object. Okay, so links and we need onClick, so an additional onClick and what we want to do is say if or we need to say onClick with a question mark then return onClick else we don't want anything there okay then we'll call it onClick.

headernavbar.js

export function setNavbarLinks(links, onClick) {
    return ({
        type: SET_NAVBAR_LINKS,
        payload: {
            links,
            onClick: onClick ? onClick : ''
        }
    })
}

Kind of a hassle, and it might not even work but whatever, we'll find a way to do it. Okay, so now we need to go into vs code and we need to go to our reducer for this and modify it, because now our payload is more than just links it's got another object in it. This object is no longer links, this object now contains links and an on click if there is a click. Okay let's go into our headernavbarReducer.js and let's say navbarLinks is action.payload.links right. So we need const links and onClick is equal to action.payload navbarLinks is now going to be links and we need to basically pass in the onClick with it.

So we need to say navbarLinks is links, this is a messy way of doing this to be honest but it doesn't matter. And then we'll say onClick, then we'll pass in an additional onClick. Okay, we can just say onClick because it's going to be an empty string if it's null. Okay, now what we can do is we can into navbar.js and we can do something with this onClick after pulling it out of our state.

So let's say onClick and then what we want to do is we want to go in here and we want to make a function for this. So we want to take this arrow function, I mean we could theoretically write it all on one line but let's not do that. Let's say this .handleOnClick and then what we'll do is we'll write a function in here.

We'll say handleOnClick and then what we'll do is make that an arrow function and we will say this.props.changeNavbarActive(link._id). So we need to pass in the link so lets go in here and let's pass in the link. All right, now what we need to do is we need to say okay if link.onClick then link.onClick all right and then we need to pass in the link._id. Okay let's try this out if it works great if it doesn't I'm re-recording, okay so onClick is not defined, headernavbarReducer.js:10.

large

So what we want to do is we want to say, okay so it's not a part of link that's the thing, we want to say if this.props.onClick then this.props.onClick.

navbar.js

class Navbar extends Component {

    handleOnClick = (link) => {
        this.props.changeNavbarActive(link._id)
        if(this.props.onClick) {
            this.props.onClick(link._id);
        }
    }

    render() {
        return (
            <div className='navbar'>
                {
                    this.props.navbarLinks.map((link, index) => {
                        return (
                            <a className={`navbar__link ${link.active ? 'green-text' : ''}`} key={index} onClick={() => this.handleOnClick(link)}>
                                {link.title}
                            </a>
                        )
                    })
                }
            </div>
        )
    }
}

Okay, let's try this now, okay so onClick is not defined, headernavbarReducer.js:10. Let's go to our reducer, let's go to headernavbarReducer.js, it says that the link onClick is not defined so what we want to do is say, onClick ? and then we want to say onClick ? onClick, okay let's try this.

onClick: onClick ? onClick : ''

Okay onClick is not defined js:10, okay I think that was the line it was on anyway. Okay, we're saying onClick is an empty string and then we can just change this back to onClick.

headernavbarReducer.js

const INITIAL_STATE = {
    headerLinks: [],
    navbarLinks: [],
    onClick: ''
}
return {
    ...state,
    navbarLinks: links,
    onClick: onClick
}

Okay, let's try this now, hey it looks like something's working, and let's see if it set our products selected, let's see what were we even trying to do, I can't remember? onClick, I really can't even remember, what were we calling? SET_NAVBAR_LINKS with the onClick, what was the onClick? The onClick was okay, it is this.props.filterProductsWithCategoryId. So basically we want to be hitting filter products with category id, okay we should be hitting this, I'm not seeing a console.log so let's just say console.log(id).

Okay, we're not getting the id. I meant to type in hello but jello is cool too, okay let's try this out, still nothing in our app. Alright, lets see filterProductsWithCategoryId shop.js, okay, that's because we're trying to get the id out of thin air. So what we need to do, okay never mind, filterProductsWithCategoryId this.props.filterProductsWithCategoryId we need to go to our headernavbar.

Let's go into our headernavbarReducer.js onClick, let's go into our navbar.js okay let's go ahead and let's actually return onClick in here in our mapStateToProps that's the problem.

Okay, let's go to our application, let's hit Linux and you'll see it says 3, python says jello.

large

All right, they say the id's they are, so now that we've got that working what we need to do is we need to go in the next video and we need to actually filter these items. We need to first display them and then we need to actually filter them alright. So let's go and commit our code. I'm going to get rid of that console log and then we'll commit our code.

Terminal

git status
git add .
git commit -m "refactored navbar to take additional onclick"

Okay we did a bit more then just refactored our navbar but what ever. I'll see you in the next guide.

Resources

Source code