1

I have an array with strings and I want to return these items as a single string but with a space between them.

listProperties() {
    this.properties = "";
    this.state.withoutSuit.forEach(function (i, j) { 
      this.properties += i;
      this.properties += " ";
    });
    console.log(this.properties);
    return this.properties;
  }  

So in the constructor I have this.state.withoutSuit as my array, and this.properties as the place I'll store the spaced out string version of it.

In the function first I set this.properties to an empty string, and then I want to fill it up with the withoutSuit array items.

But when i go into the forEach loop, this.properties is undefined. I presume this is because the "this" is now referring not to the constructor but to this.state.withoutSuit :- is that right?

And if so, how do I reference the properties variable from within the loop?

Thank you!

Ludovic Feltz
  • 11,416
  • 4
  • 47
  • 63
user43107
  • 365
  • 3
  • 13
  • Why do you want to bind the result to `this`? You can just call that function e.g. from your render function. Also using [`Array.prototype.join()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join) is the cleanest way to join an array of strings. – trixn May 18 '18 at 13:00
  • Thanks -- yes trixn mentioned join below and that was very helpful. I was binding to this because I called that function from within an onClick handler, so I needed to store it somewhere I could access it from within the render function. I used a getter in the end. – user43107 May 18 '18 at 13:28
  • That sounds like a design-smell to me. If you need the result of that in your `render()` then you should most likely store it in component state by calling `setState()` (which will trigger a re-render with the new state) or not store it at all and calculate it on demand. I guess that you need to re-build that string, when `this.state.withoutSuit` changes? It would be easier to tell if you show more of your component code including the `render()` function. – trixn May 18 '18 at 13:34
  • @trixn yep -- that's the idea, it would be rebuilt when the user clicks a button, using the getter as you advised worked, but I see now that this should be stored in state. I am just getting to grips with react, danke for your help! – user43107 May 18 '18 at 13:40

7 Answers7

5

you can use arrow functions

listProperties() {
    this.properties = "";
    this.state.withoutSuit.forEach((i, j)=> { 
      this.properties += i;
      this.properties += " ";
    });
    console.log(this.properties);
    return this.properties;
} 
Igor Alemasow
  • 4,484
  • 2
  • 27
  • 24
1

Just change function(i, j) to (i, j) =>.

listProperties() {
    this.properties = "";
    this.state.withoutSuit.forEach((i, j) => { 
      this.properties += i;
      this.properties += " ";
    });
    console.log(this.properties);
    return this.properties;
  }

And make sure that at the constructor you're binding this method:

this.listProperties = this.listProperties.bind(this);
Gabriel Carneiro
  • 643
  • 5
  • 16
0

Use a local variable inside your fonction, for example me (It is more compatible with older browser version than Arrow functions):

listProperties() {
    var me = this; // local variable
    this.properties = "";
    this.state.withoutSuit.forEach(function (i, j) { 
      me.properties += i; // use 'me' in your loop instead of 'this'
      me.properties += " ";
    });
    console.log(this.properties);
    return this.properties;
}

Or as suggested by many here you can use arrow functions:

listProperties() {
    this.properties = "";
    this.state.withoutSuit.forEach((i, j)=> { 
      this.properties += i;
      this.properties += " ";
    });
    console.log(this.properties);
    return this.properties;
} 

but it is NOT compatible with older browser (IE11 do not support it). Check browser's versions compatibility.

Ludovic Feltz
  • 11,416
  • 4
  • 47
  • 63
  • 1
    This is such an old school way of doing it, why not use an arrow function which lexically scopes `this`? This shouldn't be the accepted answer. – Dan May 18 '18 at 14:57
  • @Dan Because it is compatible with older browser. Even IE11 do not support arrow functions. – Ludovic Feltz May 18 '18 at 17:26
0

Use "arrow function" instead of function. Its added in ES6

    listProperties() {
        this.properties = "";
        this.state.withoutSuit.forEach((i, j) => { 
        this.properties += i;
        this.properties += " ";
     });
     console.log(this.properties);
     return this.properties;
    }  
DannyGolhar
  • 196
  • 1
  • 10
0

One more, pass thisArg argument to forEach method, like so:

this.state.withoutSuit.forEach(function (i, j) { 
  this.properties += i;
  this.properties += " ";
}, this);

forEach at MDN.

Teemu
  • 22,918
  • 7
  • 53
  • 106
0

It looks to me as if you don't want the properties variable to be a member of this, instead you want it to be a temporary inside the listProperties() method.

listProperties() {
    let properties = "";
    this.state.withoutSuit.forEach(function (i, j) { 
        properties += i;
        properties += " ";
    });
    return properties;
}

Why?

  1. It's generally not recommended to have private properties on a React component, rather use state. An exception to this rule is currently refs, but this might change in the future aswell.
  2. In computer programming, you usually want your functions to be pure. A method is pure when it has no side effects, like setting a variable visible outside the function. A pure method is easier to reason about.

How to use from your render function.

class MyComponent 
  extends React.Component
{
    listProperties() {
        let properties = "";
        this.state.withoutSuit.forEach(function (i, j) { 
            properties += i;
            properties += " ";
        });
        return properties;
    }

    render() {
        const properties = this.listProperties();
        return <span>{properties}</span>
    }
}
Wazner
  • 2,962
  • 1
  • 18
  • 24
  • Thanks -- I see where you're coming from here. But the reason I put it in a variable outside the function is because I wanted to display the string in the render function, like "

    {listProperties}

    " but it didn't let me put a function there. So I thought I had better store it elsewhere.
    – user43107 May 18 '18 at 12:26
  • @user43107 Why not call `listProperties()` from your render function? If it's because it's too expensive performance-wise you should store it in state (using `setState`). – Wazner May 18 '18 at 12:52
  • Oh good point! Well, I didn't do that for the same reason Bruce Banner didn't reprogram the synapses to work collectively -- I didn't think of it! However, on further inspection, I am calling listProperties from inside a handleClick function, so I think it would be out of scope. edit: but you think it's better stores in state? So I'd have a state.withoutSuit and a state.withoutSuitString, for instance? – user43107 May 18 '18 at 13:11
0

You should use Array.prototype.join() to join strings in an array. It is supported in every major browser:

const strings = ['This', 'is', 'just', 'an', 'example'];

const joined = strings.join(' ');

console.log(joined); // > This is just an example

To use it in your component class you can just do:

Using babel and public class fields syntax:

listProperties = () => this.state.withoutSuit.join(' ');

Or using bind():

class App extends Component {
    constructor(props) {
        super(props);

        // other initialization

        this.listProperties = this.listProperties.bind(this);
    }

    listProperties() {
        return this.state.withoutSuit.join(' ');
    }

    // other functions
}

Or as a getter so you can access it like a property:

class App extends Component {
    state = {
        withoutSuit: ['This', 'is', 'just', 'an', 'example'],
    }    

    get listProperties() {
        return this.state.withoutSuit.join(' ');
    }

    render() {
        // will render: <p>This is just an example</p>

        return() (
            <p>{this.listProperties}</p>
        );
    }
}
trixn
  • 15,761
  • 2
  • 38
  • 55