Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

JavaScript Displaying the Search Results

Daniel Rebolledo
Daniel Rebolledo
3,157 Points

How can I order the response from axios to its original array list?

I have an external JSON file which I'm calling with axios, when the data gets render it does not display it in the same order as I specified in the original array list, I have tried with async/await function but it still not in order.

This is my code

    import React from 'react';
    import axios from 'axios';
    import _ from 'lodash';

    // COMONENTE DE ELEMENTO

    const ContenidoUno = (props) => (
    <div className="contentBox outerBox-first hidden-xs hidden-sm" >
        <a href={props.prodsLink}>
        <img src={props.prodsImg} alt="bloque 01" className="img-wrap" />
        </a>
        <h3>{props.catName}</h3>
        {props.prodsIcon}
        <span>{props.prodsName}</span>
        <strong>$ {props.prodsNormal}</strong>
        {props.prodsAntes}
        <div className="containerBotonRow">
        <a href={props.prodsLink}><button className="botonRow">¡Lo quiero!</button></a>
        </div>
    </div>
    )

    // END

    // LLAMADA ASINCRONA A JSON

    const getProductDetailAPI = (productsIds, storeId) => ({
    method: 'GET',
    baseURL: `/rest/getProductDetailsJson?{ "productId" : "${productsIds}","storeId":"${storeId}"}`,
    auth: {
        username: 'XXXXXXX',
        password: 'XXXXXXX',
    },
    headers: {
        'Content-Type': 'application/json',
    },
    data: {},
    });

    // END

    // ALMACENAMIENTO DE DATA Y CONSTRUCCION DE ELEMENTO

    class ClassContenidoUno extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
        products: [],
        productsAndMarca: [],
        ids: []
        };
    };

    getWebServiceResponse = (currentList, storeId) => (
        axios(getProductDetailAPI(currentList, storeId))
        .then(response => response.data)
        .then(newData => {
            this.setState({
            products: newData.productDetailsJson,
            productsAndMarca: _.merge(newData.productDetailsJson, this.state.ids)
            })
        })
        .catch(e => e)
    );

    componentDidMount() {

        axios.get('https://www.xxxxx.cl/build/siteContent/jsonRawData.json')
        .then(response => response.data)
        .then(result => {
            this.setState({
            active: result.productosDestacadosStatus,
            ids: result.productosDestacados
            });
        })
        .catch(e => e)

    };

    render() {

        return (

        this.state.active === true &&
        <div className="row" style={{ width: '89%', margin: '0 auto' }}>
            <div className="blockCatPriceSlide">
            {this.state.productsAndMarca.map(skuData =>
                window.innerWidth > 440 && skuData.status === 'OK' ?
                <ContenidoUno
                    prodsName={skuData.name.substr(0, 30) + '...'}
                    prodsId={skuData.productId}
                    prodsStatus={skuData.status}
                    prodsPublished={skuData.published}
                    prodsNormal={skuData.NORMAL.toLocaleString().replace(',', '.')}
                    prodsCMR={skuData.CMR}
                    prodsCombo={skuData.combo}
                    prodsAhorro={skuData.savings}
                    prodsStock={skuData.stockLevel}
                    prodsAntes={skuData.NORMAL + skuData.savings > skuData.NORMAL ? <small> Antes: $ {(skuData.NORMAL + skuData.savings).toLocaleString().replace(',', '.')} </small> : ''}
                    prodsLink={'https://www.sodimac.cl/sodimac-homy/product/' + skuData.productId + '/' + skuData.marca}
                    prodsImg={'https://picsum.photos/g/570/250'}
                    prodsIcon={(skuData.combo === true &&
                    <img src='https://via.placeholder.com/100x50/f41435/ffffff?text=combo' className="iconic" alt="producto" />) ||
                    (skuData.CMR !== undefined && <img src='https://via.placeholder.com/100x50/f41435/ffffff?text=CMR' className="iconic" alt="producto" />)}
                    catName={skuData.webCategoryName}
                /> :
                <ContenidoUno
                    prodsName={'Producto sin información...'}
                    prodsId=''
                    prodsStatus=''
                    prodsPublished=''
                    prodsNormal=''
                    prodsCMR=''
                    prodsCombo=''
                    prodsAhorro=''
                    prodsStock=''
                    prodsAntes=''
                    prodsLink=''
                    prodsImg={'https://picsum.photos/g/570/250'}
                    prodsIcon=''
                    catName=''
                />

            )
            }
            </div>
        </div>
        )
    }
    }

    export default ClassContenidoUno;
Daniel Rebolledo
Daniel Rebolledo
3,157 Points

I'm using axios for a personal project...

2 Answers

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

A few observations:

1) It seems like you’re using both fetch and axios. I found an article about the trade-offs here. But I think you should pick one or the other to be consistent.

2) It also seems like your trying to make an API request in two different places, one using axios in componentDidMount, and another using fetch inside the render method. How are these two requests related? Is one of them the real request, and the other request something left over from a different strategy? Do you need both requests in order for it to work? If so, look into Promise.all. I think doing the API request in componentDidMount is the better option. The promise you fire off in the render method doesn’t seem to have anything listening to it, and doing anything asynchronous inside render isn’t a good idea.

3) You’ve made getWebServiceResponse an async function, but you’re not using the main benefits of async/await: not having to using .then and .catch. You can refactor like this:

async getWebServiceResponse(currentList, storeId) {
  const responseData = (await axios(getProductDetailAPI(currentList, storeId))).data;
  this.setState({
     // do your work using the variable responseData, which will be the JSON data unwrapped from the response
  })
};

4) FYI you can make React lifecycle methods, such as componentDidMount, async as well

5) As far as I can tell, you’re using the map method to return a <ContenidoUno> element for each item. But React usually recommends that you add a key property to each item to help it order the items properly. I don’t see one of those (is it giving you a warning in the console?).

Let me know how you do with that. I don’t know what the structure of the data looks like coming back from those API's. But if you’re still having issues, we can dig in further and investigate what happens with the _.merge method that's trying to knit the values and ids together.

Daniel Rebolledo
Daniel Rebolledo
3,157 Points

I've applied some of the recommendations you made, but now I'm having an issue with "getWebServiceResponse" function which I've moved into componentDidMount and now is not rendering the data from that API, if I move it into the render it shows up all the data just fine, but still not in order.

I'm using two API's because one of them is an external data that I need, and the other is a local JSON file with data that I need to add to the original external data, I'm doing this by creating a new state call "productsAndMarca" and doing a merge with this two JSON's in this state.

I'd forgot to set the key property, sorry for that...

I still don't understand completely the lifecycles in react.

PD: Sorry for my English

Daniel Rebolledo
Daniel Rebolledo
3,157 Points

I've just updated my code...

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

Would you mind also posting the contents of the JSON both from the local json file and also what you expect to get back? If not the exact data some mock data that has the same structure.

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

In the current version of the code, it doesn’t look like the function getWebServiceResponse ever gets called.

This is how I might suggest refactoring. I’m making getWebServiceResponse and getJSONRawData each a function that fires off an axios request and returns a promise that resolves with the data property. Then, in componentDidMount, I’m passing both those requests into Promise.all, which will wait for both promises to resolve - this is crucial because you need the ids from one request in order to be able to process the response from the other. I’m using handy array destructuring with Promise.all and await to pull out both responses. Then I’m just doing one call to setState because I know that I have all the data at this point.

    getWebServiceResponse = (currentList, storeId) => {
        return axios(getProductDetailAPI(currentList, storeId))
            .then(res => res.data);
    };

    getJSONRawData = () => {
        return axios.get('https://www.xxxxx.cl/build/siteContent/jsonRawData.json')
            .then(res => res.data);
    };

    async componentDidMount() {
        const [webServiceData, JSONRawData] = await Promise.all([
            this.getJSONRawData(),
            this.getWebServiceResponse( /*  pass in currenList and storeId arguments */)
        ]);

        this.setState({
            products: webServiceData.productDetailsJson,
            productsAndMarca: _.merge(webServiceData.productDetailsJson, JSONRawData.productosDestacados),
            active: JSONRawData.productosDestacadosStatus,
            ids: JSONRawData.productosDestacados
        });
    };

Also, it seems like there’s a component <ContenidoUno> for an individual item, and there’s a component <ClassContenidoUno> which is a kind of container for the items, is that correct? My Spanish isn’t that good, but I wonder if there’s a better name for this component as opposed to ClassContenidoUno since it represents a container or collection.

Daniel Rebolledo
Daniel Rebolledo
3,157 Points

I've refactored my app as you suggested, but still have some issues, I have a good response from my local API in getJSONRawData but there is no response from getWebServiceResponse, I do not know why.

This is my local JSON file structure:

    {
        "productosDestacadosStatus": true,
        "productosDestacados": [
            {
                "sku": "2751879",
                "marca": "lamarca01"
            },
            {
                "sku": "3102300",
                "marca": "lamarca02"
            },
            {
                "sku": "2824213",
                "marca": "lamarca03"
            },
            {
                "sku": "3502694",
                "marca": "lamarca04"
            }
        ],
        "productosCategorias_01_Status": true,
        "productosCategorias_01_": [
            {
                "sku": "2751879"
            }
        ],
        "productosCategorias_02_Status": true,
        "productosCategorias_02_": [
            {
                "sku": "2751879"
            }
        ],
        "categoriasStatus": true,
        "categorias": [
            {},
            {},
            {}
        ],
        "carouselProductosStatus": true,
        "carouselProductos": [
            {
                "sku": "2751879"
            },
            {
                "sku": "3102300"
            },
            {
                "sku": "2824213"
            },
            {
                "sku": "3502694"
            }
        ],
        "estilosStatus": true,
        "estilos": [
            {},
            {},
            {},
            {}
        ],
        "blogStatus": true,
        "blog": [
            {},
            {},
            {},
            {}
        ]
    }

And this is a mockup from the external JSON:

    {
        "QPOmniture": [],
        "productDetailsJson": [
            {
                "status": "OK",
                "productId": "3102300",
                "name": "Sofá 142x76x86 cm gris",
                "brand": "Just Home Collection",
                "combo": false,
                "published": true,
                "webCategoryName": "Nuevos Productos",
                "webCategory": "cat7800015",
                "backEndCategoryName": "SOFAS",
                "backEndCategory": "0417020103",
                "PriceFormat": "C/U",
                "savings": 76990,
                "COMPANY": 139990,
                "INTERNET": 139990,
                "NORMAL": 139990,
                "EVENTO": 63000,
                "EventStock": 4,
                "EventStockFrom": "2018-11-16T12:20:00GMT-03:00",
                "EventStockValidity": "2018-11-25T23:59:59GMT-03:00",
                "stockLevel": "{3102300=0}",
                "pickupInStore": "{3102300=true}",
                "A+BFrom": "2018-11-16T00:00:00GMT-03:00",
                "MASBAJO": 63000,
                "MASBAJOValidity": "2018-11-26T23:59:59GMT-03:00"
            }
        ]
    }

PD: I need to merge this two in a state where the param "marca:" from productosDestacados in jsonRawData.json and the rest of the params are together so I can use them in the render function...

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

For each marca/sku in the productosDestacados list, do you want to send a request to your external API for that productDetailsJson data, or is there only one request to the external API?

Where do you get the currentList and storeId parameters to use for the request to the external API?

Daniel Rebolledo
Daniel Rebolledo
3,157 Points

The external and local API requests are made once, and the data of both is stored in productsAndMarca state so I can use "marca" value and "productId" value in the render function and map it. Like this:

render() {

    return (

    this.state.active === true &&
    <div className="row" style={{ width: '89%', margin: '0 auto' }}>
        <div className="blockCatPriceSlide">
        {this.state.productsAndMarca.map(skuData =>
            window.innerWidth > 440 && skuData.status === 'OK' ?

            <ContenidoUno
                prodsLink={'https://www.sodimac.cl/sodimac-homy/product/' + skuData.productId + '/' + skuData.marca}
            /> :

            <ContenidoUno
                prodsLink={'https://www.sodimac.cl/sodimac-homy/product/no-product'}
            />

        )
        }
        </div>
    </div>
    )
}

These are the parameters that I use for currentList and storeId:

this.getWebServiceResponse(this.state.ids.map(e => e.sku).join('-'), 96)
Daniel Rebolledo
Daniel Rebolledo
3,157 Points

These values come from my local JSON file, which takes "sku" values and is joined like this "4444444-555555-666666-777777" and then goes to:

this.getWebServiceResponse("4444444-555555-666666-777777", 96)

This is how I make the request to the external API...

Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

Got it. One more question, the external API response above, it has an array with only one object in it (productDetailsJson). Do you expect this to have multiple objects, one for each item requested? How do you retrieve the values for each product from the API?

Daniel Rebolledo
Daniel Rebolledo
3,157 Points

Yes, every ID separated by a dash should retrieve an object inside productDetailsJson

Example:

"IDone-IDtwo-IDthree-IDfour" 

{
"productDetailsJson": [
    {
        "status": "OK",
        "productId": "IDone",
        "name": "Product name..."
    },
    {
        "status": "OK",
        "productId": "IDtwo",
        "name": "Product name..."
    },
    {
        "status": "OK",
        "productId": "IDthree",
        "name": "Product name..."
    },
    {
        "status": "OK",
        "productId": "IDfour",
        "name": "Product name..."
    }
]
}
Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points

Okay I think I understand now. Here's a version that I have rendering the list, although for now it's just a list with all the same mock data except the SKU ids.

Basically what I'm doing is taking care of all the API calls inside componentDidMount, which is set as an async function. First, I'm getting the local JSON file, then I'm using the SKU values from the list in the JSON file to send the query to the external API. Then I'm rendering this list with setState. I'm also adding a key property to each <ContenidoUno> item so that React can manage the order properly.

import React from 'react';
import axios from 'axios';
import _ from 'lodash';

// COMONENTE DE ELEMENTO

const ContenidoUno = (props) => (
    <div className="contentBox outerBox-first hidden-xs hidden-sm" >
        <a href={props.prodsLink}>
            <img src={props.prodsImg} alt="bloque 01" className="img-wrap" />
        </a>
        <h3>{props.catName}</h3>
        {props.prodsIcon}
        <span>{props.prodsName}</span>
        <strong>$ {props.prodsNormal}</strong>
        {props.prodsAntes}
        <div className="containerBotonRow">
            <a href={props.prodsLink}><button className="botonRow">¡Lo quiero!</button></a>
        </div>
    </div>
);

// END

// LLAMADA ASINCRONA A JSON

const mergeArrayOfObjects = (localItemsData, externalItemsData) => {
    let mergedData = [];

    for (let i = 0; i < localItemsData.length; i++) {
        const localData = localItemsData[i];
        const externalData = externalItemsData[i];
        mergedData.push({ ...localData, ...externalData })
    }
    return mergedData;
};

const getProductDetailAPI = (productsIds, storeId) => ({
    method: 'GET',
    baseURL: `/rest/getProductDetailsJson?{ "productId" : "${productsIds}","storeId":"${storeId}"}`,
    auth: {
        username: 'XXXXXXX',
        password: 'XXXXXXX',
    },
    headers: {
        'Content-Type': 'application/json',
    },
    data: {},
});

// END

// ALMACENAMIENTO DE DATA Y CONSTRUCCION DE ELEMENTO

class ClassContenidoUno extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            products: [],
            productsAndMarca: [],
            ids: []
        };
    };

    getWebServiceResponse = (currentList, storeId) => {
        // for now it ignores the parameters and just returns the default JSON
        return axios.get('/externalJSON.json')
            .then(res => res.data);
    };

    getJSONRawData =  () => {
        return axios.get('/localJSON.json')
            .then(res => res.data);
    };


    async componentDidMount() {
        const localJSON = await this.getJSONRawData();
        const joinedSKUs = localJSON.productosDestacados.map(product => product.sku).join('-');

        const externalJSON = (await this.getWebServiceResponse(joinedSKUs, 96)).productDetailsJson;
        const mergedData = mergeArrayOfObjects(localJSON.productosDestacados, externalJSON);

        this.setState({
            products: externalJSON,
            productsAndMarca: mergedData,
            active: localJSON.productosDestacadosStatus,
            ids: localJSON.productosDestacados
        });
    };

    render() {

        return (

            this.state.active === true &&
            <div className="row" style={{ width: '89%', margin: '0 auto' }}>
                <div className="blockCatPriceSlide">
                    {this.state.productsAndMarca.map(skuData =>
                        window.innerWidth > 440 && skuData.status === 'OK' ?
                            <ContenidoUno
                                key={skuData.sku}
                                prodsName={skuData.name.substr(0, 30) + '...'}
                                prodsId={skuData.productId}
                                prodsStatus={skuData.status}
                                prodsPublished={skuData.published}
                                prodsNormal={skuData.NORMAL.toLocaleString().replace(',', '.')}
                                prodsCMR={skuData.CMR}
                                prodsCombo={skuData.combo}
                                prodsAhorro={skuData.savings}
                                prodsStock={skuData.stockLevel}
                                prodsAntes={skuData.NORMAL + skuData.savings > skuData.NORMAL ? <small> Antes: $ {(skuData.NORMAL + skuData.savings).toLocaleString().replace(',', '.')} </small> : ''}
                                prodsLink={'https://www.sodimac.cl/sodimac-homy/product/' + skuData.productId + '/' + skuData.marca}
                                prodsImg={'https://picsum.photos/g/570/250'}
                                prodsIcon={(skuData.combo === true &&
                                    <img src='https://via.placeholder.com/100x50/f41435/ffffff?text=combo' className="iconic" alt="producto" />) ||
                                (skuData.CMR !== undefined && <img src='https://via.placeholder.com/100x50/f41435/ffffff?text=CMR' className="iconic" alt="producto" />)}
                                catName={skuData.webCategoryName}
                            /> :
                            <ContenidoUno
                                key={skuData.sku}
                                prodsName={'Producto sin información...'}
                                prodsId=''
                                prodsStatus=''
                                prodsPublished=''
                                prodsNormal=''
                                prodsCMR=''
                                prodsCombo=''
                                prodsAhorro=''
                                prodsStock=''
                                prodsAntes=''
                                prodsLink=''
                                prodsImg={'https://picsum.photos/g/570/250'}
                                prodsIcon=''
                                catName=''
                            />
                    )
                    }
                </div>
            </div>
        )
    }
}

export default ClassContenidoUno;
Daniel Rebolledo
Daniel Rebolledo
3,157 Points

Thank you very much... I've tried this but it shows this error

Uncaught (in promise) TypeError: n.getProductDetailAPI is not a function
Daniel Rebolledo
Daniel Rebolledo
3,157 Points

When I do "npm run build" shows this warning

./src/components/Seccion_uno_contenido.js
  Line 3:   '_' is defined but never used                             no-unused-vars
  Line 38:  'getProductDetailAPI' is assigned a value but never used  no-unused-vars

Wish is this piece of the code:

const getProductDetailAPI = (productsIds, storeId) => ({
    method: 'GET',
    baseURL: `/XXXXXXX/getProductDetailsJson?{ "productId" : "${productsIds}","storeId":"${storeId}"}`,
    auth: {
        username: 'XXXXXXX',
        password: 'XXXXXXX',
    },
    headers: {
        'Content-Type': 'application/json',
    },
    data: {},
});
Brendan Whiting
seal-mask
.a{fill-rule:evenodd;}techdegree seal-36
Brendan Whiting
Front End Web Development Techdegree Graduate 84,736 Points
Line 3:   '_' is defined but never used                             no-unused-vars

This is just a warning that you're defining something that you're not using. I built a helper function to merge the two lists of objects together. I called it mergeArrayOfObjects. But this also means that maybe now you're not using lodash at all. all anymore. That's a good thing, because lodash is a pretty big dependency, so if you don't have to use it in your project, take it out.

Line 38:  'getProductDetailAPI' is assigned a value but never used  no-unused-vars

This is the method that builds the query for the external API. I was just using the mock JSON data instead of the real API, but you can swap that out. So:

return axios(getProductDetailAPI(currentList, storeId))
            .then(res => res.data);

instead of

return axios.get('/externalJSON.json')
            .then(res => res.data);