22

I have a Map object:

let dateJobMap = new Map();

for (let jobInArray of this.state.jobs) {
  let deliveryDate: Date = new Date(jobInArray.DeliveryDate);
  let deliveryDateString: string = deliveryDate.toLocaleDateString("en-US");

  if (dateJobMap.has(deliveryDateString)) {
    let jobsForDate: IDeliveryJob[] = dateJobMap.get(deliveryDateString);
    jobsForDate.push(jobInArray);
  }
  else {
    let jobsForDate: IDeliveryJob[] = [jobInArray];
    dateJobMap.set(deliveryDateString, jobsForDate);
  }
}

In my render method, I want to call a TruckJobComp object for each delivery job in the value's array to display it:

        <div className={ styles.column }>
          <p className={ styles.description }>{escape(this.props.description)}</p>

          {
            dateJobMap.forEach(function(jobsForDate, dateString) {
              jobsForDate.map(job => (
                <TruckJobComp job = { job } />
              ))
            })
          }
        </div>

This seems like it should work but doesn't. It never creates a TruckJobComp. I do a .forEach iteration on my Map, and for each value's array, I use .map to get the individual job object to send to TruckJobComp object.

When I create a temp array to grab the jobs from the last loop:

let tempJobs: IDeliveryJob[];

and in the loop add in:

  if (dateJobMap.has(deliveryDateString)) {
    let jobsForDate: IDeliveryJob[] = dateJobMap.get(deliveryDateString);
    jobsForDate.push(jobInArray);

    tempJobs = jobsForDate;
  }

and then use that array in the render:

          <div className={ styles.column }>
            <p className={ styles.description }>{escape(this.props.description)}</p>
            {
              tempJobs.map(job => (
                <TruckJobComp job = { job }/>
              ))
            }
          </div>

It displays as expected.

I do have a warnings in Visual Studio Code:

Warning - tslint - ...\TruckDeliverySchedule.tsx(104,38): error no-function-expression: Use arrow function instead of function expression

I don't know enough to understand. Line 104 corresponds with:

dateJobMap.forEach(function(jobsForDate, dateString) {

I am very new to this so I'm not 100% sure how most of this works. Just trying to put pieces I've learned together to get things to work.

Second Edit:

{escape(this.props.description)}

{
  [...dateJobMap.keys()].map(jobsForDate => // line 154
    jobsForDate.map(job => (
      <TruckJobComp job = { job } />
    ))
  )
}

Produces error:

[09:06:56] Error - typescript - src\...\TruckDeliverySchedule.tsx(154,27): error TS2461: Type 'IterableIterator<any>' is not an array type.
Holden1515
  • 659
  • 2
  • 7
  • 20

3 Answers3

27

dateJobMap.forEach(...) returns undefined, so it cannot be mapped to a collection of elements.

ES6 maps have forEach method for compatibility purposes (generally for..of is preferred to iterate over iterables) and don't have map method. A map should be converted to array first, then it could be mapped to an element. Since values aren't used, only keys need to be retrieved:

  {
    [...dateJobMap.keys()].map(jobsForDate =>
      jobsForDate.map(job => (
        <TruckJobComp job = { job } />
      ))
    )
  }
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    I am fairly new to typescript/javascript development (I've done 7 years of iOS) and I apologize, I don't completely understand the sample code you provided. What are the `...`? And why is the `...dateJobMap.keys()` bracketed like that? And I'm not beholden to use `.forEach`, was just the first one I found. – Holden1515 Aug 02 '18 at 13:45
  • 2
    As it's mentioned, it converts a map to an array. `dateJobMap.keys()` is `Map` method that returns [an iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol), and [... spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) converts an iterable to an array when used with `[]` array literal.s – Estus Flask Aug 02 '18 at 13:57
  • Thanks for the links. Much still to learn. I tried the block of code and I get an error; I updated the question. That's why I asked for clarification, I though it might have been shorthand for something I should already know. – Holden1515 Aug 02 '18 at 14:13
  • 5
    I didn't notice typescript tag on the question. The problem is specific to TS. See https://stackoverflow.com/a/46605880/3731501 and https://stackoverflow.com/a/43964577/3731501 . You should either enable `downlevelIteration` compiler option or replace `[...dateJobMap.keys()]` with `Array.from(dateJobMap.keys())`. – Estus Flask Aug 02 '18 at 14:18
  • Yep, that cleared the error. I had to change it to `.values()` to get the array I needed. The TruckJobComp is now being rendered!. I think this is taking me in the right direction, thank you very much! – Holden1515 Aug 02 '18 at 14:26
  • Had a similar issue and was able to solve it using this approach but with the use of `.entries()` instead, thanks @EstusFlask ! – Juani Condina Jun 09 '21 at 16:47
  • 1
    @JuaniCondina Set/map iterator iterates over things that make sense in that context, `[...map]` is the same as `[...map.entries()]`, and `[...set]` is the same as `[...set.values()]`. – Estus Flask Jun 09 '21 at 16:55
2

All this warning is saying is that instead of using the syntax function(jobsForDate, dateString) {} you should use the syntax (jobsForDate, dateString) => {}.

The reason could be the way this is scoped in arrow functions versus function expressions. See this post.

My guess as to the reason your first approach didn't work but your second one did is that forEach doesn't actually return an array, and if it did, calling map within forEach would return an array of arrays (but, again, it doesn't). Not sure how React would handle that, but React does know how to handle a single array, which is what your last approach returns.

jaredkwright
  • 1,390
  • 2
  • 11
  • 27
  • `.forEach` doesn't return an array in of itself, but it should return the `Map`'s `value` which is an array. I've confirmed this outside of the render method by console.loging it. – Holden1515 Aug 01 '18 at 17:01
  • If you check out the documentation of the `.forEach` method you will see that it returns `undefined`. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/forEach – jaredkwright Aug 01 '18 at 17:04
  • You can see this confirmed in this trivial example that I ran in the REPL. Notice when you check the value of `a` you get the Map, but when you check `b` you get nothing, i.e. undefined. https://imgur.com/SjDtUIc – jaredkwright Aug 01 '18 at 17:11
  • Jared, your example is true. When I do the `.forEach' loop fore the render and console.log the variables, they have values. JobsForDate logs as a array. I'll update the question. – Holden1515 Aug 01 '18 at 18:47
  • 1
    After more thought and playing with, I understand what you were saying. While the array I need is avaible in the loop scope, the `.forEach` doesn't return anything to the `
    ` to display. Thank you for your help.
    – Holden1515 Aug 02 '18 at 13:04
0

To iterate over a Map, we can use for..of and forEach() loop constructs. Map provides three methods that return iterable: map.keys(), map.values() and map.entries().

Iteration over Maps is always in insertion order.

                    let map = new Map()
                    
                    map.set("one", "first element");
                    map.set("two", "second element");
                    map.set(3, "third element");    
                    
                         for (let [key, value] of map) {
                                console.log(key+" : "+value);
                                }
                
                map.keys() - Returns an iterable for keys
                map.values() - Returns an iterable for values
                map.entries() - Returns an iterable of key,value
            
            Example
            
            for (let key of map.keys()) {
                console.log(key);
            }
            
            // output
            // one
            // two
            // 3
            
            for (let value of map.values()){
                console.log(value);
            }
            
            // output
            // first element
            // second element
            // third element
            
            for (let [key, value] of  map.entries()) {
                console.log(key + " = " + value)
            }
            
            //output
            // one = first element
            // two = second element
            // 3 = third element
        
        map.forEach(function(value, key) {
            console.log(key + " = " + value);
        })
        
        //output
        // one = first element
        // two = second element
        // 3 = third element
Sandeep Jain
  • 1,019
  • 9
  • 13
  • Please don't use code formatting for the entire answer, you seem to have done that for all your recent answers. If you are struggling with the formatting tools, remember that indenting text is a shortcut to formatting it as code, and have a look at the "preview" section below the editor to make sure it's looking the way you intended. – DBS Jun 20 '23 at 09:32
  • Changes done as requested. – Sandeep Jain Jun 27 '23 at 11:49