Creating variables inside map with react

Unless someone can show me a better way, I need to create a couple of variables that sum up data so I can output them later. I have tried various things, but nothing works. It’s the place.totals line failure. :slight_smile:

I am sure there is a better way, but since I am very new to React, I am not sure.

import React from "react";

const availabilities = (props) => {
    // if (!availabilities || availabilities.length === 0) return null;
    console.log("avail props: ", props);
    let totalSlots = 0;
    let freeBikes = 0;
    return (
        <React.Fragment>

            {props.firstLoad && "Please select a Country from the left."}

            {props.cnetworks && props.cnetworks.map((place, index) => (
                <div className="card" key={place.network + "-" + index}>
                    <div className="card-body">
                        <h1 className={"display-4 text-center"}>{place.network.location.city}</h1>
                        {place.network.stations.map((x, i) =>
                            place.totals = {
                                "totalSlots": totalSlots += (x.empty_slots + x.free_bikes),
                                "freeBikes": freeBikes += x.free_bikes
                            }
                        )}
                        <p className={"text-center"}>Availability: <span className={"h3 " + "f"}>0%</span></p>
                        <div>
                            <p>Companies: {place.freeBikes} bikes / {place.totalSlots} spots</p>
                            {place.network.company.map((comp) =>
                                <p>{comp}</p>
                            )}
                        </div>
                    </div>
                </div>
            ))}

        </React.Fragment>
    );
};

export default availabilities;
1 Like

@dbinott Welcome to the forum!

Can you post an example of what props.cnetworks looks like on a given call? Just want to confirm the structure and test some code before replying.

Hey Randell…sorry for late reply. I didn’t get notified even though I am watching.
Is this good enough?

Funny thing is, I see my additions to the object (can’t post 2 images), but I get this big fat error.

Objects are not valid as a React child (found: object with keys {totalSlots, freeBikes}). If you meant to render a collection of children, use an array instead.
    in div (at availabilities.js:15)
    in div (at availabilities.js:14)
    in availabilities (at App.js:102)
    in div (at App.js:101)
    in div (at App.js:90)
    in App (at src/index.js:7)

I would prefer you post the full JSON code of cnetworks. To do this, add the following line

console.log(JSON.stringify(cnetworks, null, 2)); 

as the first line of your availabilities function and then post here.

Note that you’re trying to render an object, not a JSX element here (and you’re trying to assign to an object in a map, which isn’t how map works):

{place.network.stations.map((x, i) =>
                            place.totals = {
                                "totalSlots": totalSlots += (x.empty_slots + x.free_bikes),
                                "freeBikes": freeBikes += x.free_bikes
                            }
                        )}

And a string instead of a JSX element here:

{props.firstLoad && "Please select a Country from the left."}

Both of these will cause errors because React has no idea what to do with them

1 Like

This does not really makes sense to me. place.network is not a string.

1 Like

It’s pretty big, so here is a link https://jsonblob.com/0665c397-4ae5-11ea-9378-736494ee6bc0

Your right, oversight. fixed.

The last one doesn’t error. It actually works. If it’s firstLoad, nothing is selected yet, so that message shows. But if the conditional can be done better, please, let me know. That’s why I am here.

The first part, yes, it is not correct. This is why I need help. Although it doesn’t error, it errors when trying to access those items outside of the map.

1 Like

I made some modifications to your original function. There were some null values for some of the empty_slot properties, so I convert them to 0.

const availabilities = (props) => {
  return (
    <React.Fragment>
      {props.firstLoad && "Please select a Country from the left."}

      {props.cnetworks && props.cnetworks.map((place, index) => {
        const stationStats = place.network.stations
          .reduce((stats, { free_bikes, empty_slots }) => ({
            free_bikes: stats.free_bikes + (free_bikes || 0),
            empty_slots: stats.empty_slots + (empty_slots || 0)
          }), { free_bikes: 0, empty_slots: 0 });

        return (
          <div className="card" key={index}>
            <div className="card-body">
              <h1 className={"display-4 text-center"}>{place.network.location.city}</h1>
              <p className={"text-center"}>
                Availability: <span className="h3 f">0%</span>
              </p>
              <div>
                <p>Companies: {stationStats.free_bikes} bikes / {stationStats.free_bikes + stationStats.empty_slots} spots</p>
                {place.network.company.join(", ")}
              </div>
            </div>
          </div>
        );
      })}
    </React.Fragment>
  );
};

Nice! I was just going to post that I figured out some of it on my own with reduce. Would yours have performance benefits over mine? Assuming it would since you are only performing 1 reduce?

EDIT: I just noticed you have 2 returns in there. What is the reasoning behind that?

But unlike mine, I can now get the percentage available easily by doing. I hope.
{stationStats.free_bikes / (stationStats.free_bikes + stationStats.empty_slots)}

import React from "react";

const availabilities = (props) => {
    // if (!availabilities || availabilities.length === 0) return null;
    console.log("avail props: ", props);
    return (
        <React.Fragment>

            {props.firstLoad && "Please select a Country from the left."}

            {props.cnetworks && props.cnetworks.map((place, index) => (
                <div className="card mb-3" key={place.network.name + "-" + index}>
                    <div className="card-body">
                        <h1 className={"display-4 text-center"}>{place.network.location.city}</h1>
                        {/*            place.totals.percentage = place.totals.freeBikes / place.totals.totalSlots*/}
                        <p className={"text-center"}>Availability: <span
                            className={"h3 "}>%</span></p>
                        <div>
                            <p>
                                Companies: {place.network.stations.reduce((a, b) => a + b.free_bikes, 0)} bikes
                                / {place.network.stations.reduce((a, b) => a + (b.free_bikes + b.empty_slots), 0)} spots
                            </p>
                            {place.network.company.map((comp) =>
                                <div key={comp}>{comp}</div>
                            )}
                        </div>
                    </div>
                </div>
            ))}

        </React.Fragment>
    );
};

export default availabilities;
1 Like

Yes, one reduce is typically better than 2 reduce methods, since it is over the same array.

The first return (the same one you have) is to return the fragment. The second return is the reduce’s return.

FYI - Another way to write the reduce is:

const stationStats = place.network.stations
  .reduce((stats, { free_bikes, empty_slots }) => {
    stats.free_bikes += free_bikes || 0;
    stats.empty_slots += empty_slots || 0;
    return stats;
  }, { free_bikes: 0, empty_slots: 0 });
1 Like

Thank you @RandellDawson I really appreciate the help.