Caleb
Caleb

Reputation: 489

Mapping twice to sort by category

I am currently mapping some data from my database, my Mongoose schema is:

const itemSchema = new mongoose.Schema({
    category: String,
    name: String,
    image: String


})

Here is my server.js:

app.post('/api', async (req, res) => {
    Item.find(function (err, result) {
        if (err) {
            res.send(err)
        } else {
            res.send(result)
        }
    })
})

In my React Home.js component I have the following code which displays all the information from my database correctly:

<div style={{ display: 'flex' }}>
            {getBasic.data.map(({ category, image, name, _id }, i) => {

                return (
                    <div>
                        <p>{i}</p>
                        <Link to={`/details/${_id}`} state={{ image: image, name: { name }, _id: { _id } }}>
                            <p>{name}</p>
                            <img src={image} />
                            <p>{_id}</p>
                            <p>{category}</p>
                        </Link>
                    </div>

                );
            })}

        </div>

Now the issue I am running into, is I wanted to sort each of the mapped contents by their category, as in all of the Hats category will go into the same div being flexed, left to right, and all the Pants category will go into a new div underneath, etc.

One way I thought of doing this was just by mapping twice, like mapping for a category, then mapping for everything else. I have it set up generally but not sure exactly how to put it in code, this is what I have:

 {getBasic.data.map(({ category }, j) => {


                    return (


                        <div style={{ display: 'flex' }}>
                            {getBasic.data.map(({image, name, _id }, i) => {


                                return (
                                    <div>
                                        <p>{i}</p>
                                        <Link to={`/details/${_id}`} state={{ image: image, name: { name }, _id: { _id } }}>
                                            <p>{name}</p>
                                            <img src={image} />
                                            <p>{_id}</p>
                                        </Link>
                                    </div>

                                );
                            })}

                        </div>
                    )

                })}

I do not know what to add to the above code so that regardless of how many categories there are it will work. The only way I could think of is adding like if (category == 'Hats') however I would have to do this for every single category this wouldn't be optimal nor scalable. I feel like there should be an easier and better way to implement this but I have been staring at it for 1.5 days and am still not sure.

Here is the full code for Home.js:

function Home() {

    const [getBasic, setGetBasic] = useState()
    const [sampleState, setSampleState] = useState(21)

    const [Item, setItem] = useState({
        name: '',
        data: '',
        category: ''
    })


    function handleChange(event) {
        let { name, value } = event.target;
        setItem(prevInput => {
            return (
                {
                    ...prevInput,
                    [name]: value,
                }
            );
        });
        // console.log(Item);
    }


    const getBasicFunc = async () => {


        axios.post('http://localhost:3001/api')
            .catch(function (err) {
                if (err) {
                    console.log(err)
                }
            })
            .then(result => setGetBasic(result))
    }

    useEffect(() => {
        getBasicFunc()
    }, [])

    if (getBasic) {

        return (



            <>
                <Header />

                <div style={{ display: 'flex', justifyContent: 'center' }}>
                    {console.log(getBasic)}
                    <h1>Home</h1>
                    <input onChange={handleChange} name="name" value={Item.name} placeholder="name" />
                    <input onChange={handleChange} name="data" value={Item.data} placeholder="data" />
                    <input onChange={handleChange} name="category" value={Item.category} placeholder="category" />
                    <button onClick={addItem}>Submit</button>
                </div>

                {getBasic.data.map(({ category }, j) => {


                    return (


                        <div style={{ display: 'flex' }}>
                            {getBasic.data.map(({ category, image, name, _id }, i) => {


                                return (
                                    <div>
                                        <p>{i}</p>
                                        <Link to={`/details/${_id}`} state={{ image: image, name: { name }, _id: { _id } }}>
                                            <p>{name}</p>
                                            <img src={image} />
                                            <p>{_id}</p>
                                            <p>{category}</p>
                                        </Link>
                                    </div>

                                );
                            })}

                        </div>
                    )

                })}

            </>


        )
    }
    else {
        return (
            <>
                <Header />
                <div style={{ display: 'flex', justifyContent: 'center' }}>
                    <h1>Home</h1>
                    <input onChange={handleChange} name="name" value={Item.name} placeholder="name" />
                    <input onChange={handleChange} name="data" value={Item.data} placeholder="data" />
                    <input onChange={handleChange} name="category" value={Item.category} placeholder="category" />
                    <button onClick={addItem}>Submit</button>
                </div>
            </>
        )
    }
}

export default Home

Update

In the comments it was suggested to sort on the backend thus my server.js now looks like:

   Item.find({}).sort('category').exec(function (err, result) {
        if (err) {
            res.send('error')
            console.log(err)
        } else {
            res.send(result)
        }
    })

Now just trying to get the one div full of Hats category to end once the sort gets to Pants category as then they would be structured so that each category is a separate row.

Update 2

I am still stuck on having different flexed divs for each category. The problems seems to be the place where I define the display: flex is above where my getBasic.data.map starts, thus this is the div with the correct flex. I almost want to do the following:

<div style={{ display: 'flex' }} className={category}>
                    {getBasic.data.map(({ category, image, name, _id }, i) => {


                        return (
                            <div>
                                <div>
                                    <p>{i}</p>
                                    <Link to={`/details/${_id}`} state={{ image: image, name: { name }, _id: { _id } }}>
                                        <p>{name}</p>
                                        <img src={image} />
                                        <p>{_id}</p>
                                        <p>{category}</p>
                                    </Link>
                                </div>

                            </div>

                        )
                    })}

                </div>

Of course with the above the className={category} is above where category gets defined, but I think if I could rearrange this somehow it would give the desired outcome of each horizontal div being belonging to only one category.

Update 3

To be as clear as possible I am looking for an outcome that would give the same result as the following :

                <div style={{ display: 'flex' }}>
                    {getBasic.data.map(({ category, image, name, _id }, i) => {

                        if (category == 'Pants') {
                            return (
                                <div>
                                    <div>
                                        <p>{i}</p>
                                        <Link to={`/details/${_id}`} state={{ image: image, name: { name }, _id: { _id } }}>
                                            <p>{name}</p>
                                            <img src={image} />
                                            <p>{_id}</p>
                                            <p>{category}</p>
                                        </Link>
                                    </div>
                                </div>

                            )
                        }
                    })}

                </div>

                <div style={{ display: 'flex' }}>
                    {getBasic.data.map(({ category, image, name, _id }, i) => {


                        if (category == 'Hats') {
                            return (
                                <div>
                                    <div>
                                        <p>{i}</p>
                                        <Link to={`/details/${_id}`} state={{ image: image, name: { name }, _id: { _id } }}>
                                            <p>{name}</p>
                                            <img src={image} />
                                            <p>{_id}</p>
                                            <p>{category}</p>
                                        </Link>
                                    </div>
                                </div>

                            )
                        }
                    })}

                </div>

The difference is I am looking for a scalable solution so that if I have more than two categories, say for example I add coats as a category, then all the coats would be in the next div without me adding another div with an if statement of if (category == coats).

Upvotes: 1

Views: 182

Answers (1)

Lars Hendriks
Lars Hendriks

Reputation: 1058

  var array = [
    {
      category: "hats",
      name: "hat1"
    },
    {
      category: "Pants",
      name: "pants2"
    },
    {
      category: "hats",
      name: "hat2"
    },
    {
      category: "Pants",
      name: "pants1"
    }
  ];

  var arrayofarray = Object.values(
    array.reduce((acc, cur) => {
      acc[cur["category"]] = [...(acc[cur["category"]] || []), cur];
      return acc;
    }, {})
  );

  return (
    <>
      {arrayofarray.map((category) => {
        return (
          <div>
            {category.map(({ category, image, name, _id }, i) => {
              return (
                <div>
                  <p>
                    {i} {name} {category}
                  </p>
                </div>
              );
            })}
          </div>
        );
      })}
    </>
  );
}

So i created a test project see sandbox here: https://codesandbox.io/s/stoic-lovelace-uw7hen?file=/src/App.js

I think it does what you require, so what I did was create an array of arrays based on the category and then map over the array of arrays, this will get you the result you need.

Upvotes: 1

Related Questions