Asked  7 Months ago    Answers:  5   Viewed   28 times

We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.

So for the scenarios like this:

<input onChange = { this._handleChange.bind(this) } ...../>

We can bind _handleChange method either in constructor:

this._handleChange = this._handleChange.bind(this);

Or we can use property initializer syntax:

_handleChange = () => {....}

Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:

todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)

For now just assume that todo names are unique.

As per DOC:

The problem with this syntax is that a different callback is created each time the component renders.

Question:

How to avoid this way of binding inside render method or what are the alternatives of this?

Kindly provide any reference or example, thanks.

 Answers

27

First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.

Parent

deleteTodo = (val) => {
    console.log(val)
}
todos.map(el => 
    <MyComponent val={el} onClick={this.deleteTodo}/> 

)

MyComponent

class MyComponent extends React.Component {
    deleteTodo = () => {
        this.props.onClick(this.props.val);
    }
    render() {
       return <div  onClick={this.deleteTodo}> {this.props.val} </div>
    }
}

Sample snippet

class Parent extends React.Component {
     _deleteTodo = (val) => {
        console.log(val)
    }
    render() {
        var todos = ['a', 'b', 'c'];
        return (
           <div>{todos.map(el => 
             <MyComponent key={el} val={el} onClick={this._deleteTodo}/> 
        
           )}</div>
        )
    }
    
   
}

class MyComponent extends React.Component {
        _deleteTodo = () => {
                     console.log('here');   this.props.onClick(this.props.val);
        }
        render() {
           return <div onClick={this._deleteTodo}> {this.props.val} </div>
        }
    }
    
ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

EDIT:

Second: The other approach to it would be to use memoize and return a function

constructor() {
    super();
    this._deleteTodoListener = _.memoize(
                   this._deleteTodo, (element) => {
                        return element.hashCode();
                    }
              )
}

_deleteTodo = (element) => {
   //delete handling here
}

and using it like

todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)

P.S. However this is not a best solution and will still result in multiple functions being created but is still an improvement over the initial case.

Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like

_deleteTodo = (e) => {
     console.log(e.currentTarget.getAttribute('data-value'));

 }

 todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)

However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"

Tuesday, June 1, 2021
 
PHLAK
answered 7 Months ago
85

The feature you are using is not part of ES6. It's the class fields proposal. It allows you to initialize instance properties without having to write a constructor. I.e. your code:

class MyClass {

    myMethod = () => {
        this.myVariable++;
    }

}

is exactly the same as

class MyClass {

    constructor() {
        this.myMethod = () => {
            this.myVariable++;
        };
    }

}

And this also shows you what the difference is between a normal class method an a method created via a class field:

  • A normal method is shared between all instances of the class (it is defined on the prototype)
  • A "class field method" is created per instance

So all the same as reasons as presented in Use of 'prototype' vs. 'this' in JavaScript? apply, but in short:

  • Use "class field methods" if you need a method per instance. Such is the case for event handlers that need to access the current instance. Access to this also only works if you are using an arrow function.
  • Use normal class methods in all other cases.
Wednesday, June 9, 2021
 
IvanH
answered 6 Months ago
99

First, you need to have a backing component which implements NamingContainer and returns "javax.faces.NamingContainer" as component family. This is required by composite components, you can't change that part. The UINamingContainer implementation already does that, so if you can just extend from it.

@FacesComponent("mySelectOneRadio")
public class MySelectOneRadio extends UINamingContainer {
    // ...
}

Or if you rather want to extend from UISelectOne, then you'd have to implement the NamingContainer interface and make sure that you return UINamingContainer.COMPONENT_FAMILY in the getFamily() override.

Then, you need to specify it in <cc:interface componentType>.

<cc:interface componentType="mySelectOneRadio">

Note that at this step you can already perform the rendering (encoding) through Java. Just override the encodeChildren() method.

@FacesComponent("mySelectOneRadio")
public class MySelectOneRadio extends UINamingContainer {

    @Override
    public void encodeChildren(FacesContext context) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("div", this);
        writer.writeText("hello world", null);
        writer.endElement("div");
    }

}

Coming back to your concrete question, you'd thus like to have a standalone Renderer class for this. That's fine. For that you need to extend Renderer:

@FacesRenderer(componentFamily=UINamingContainer.COMPONENT_FAMILY, rendererType=MySelectOneRadioRenderer.RENDERER_TYPE)
public class MySelectOneRadioRenderer extends Renderer {

    public static final String RENDERER_TYPE = "com.example.MySelectOneRadio";

    @Override
    public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
        ResponseWriter writer = context.getResponseWriter();
        writer.startElement("div", component);
        writer.writeText("hello world", null);
        writer.endElement("div");
    }

}

The backing component should be changed as follows in order to properly register this renderer as the default renderer (don't override getRendererType() method! otherwise you or anyone else would be unable to change this by <renderer> in faces-config.xml):

@FacesComponent("myComposite")
public class MyComposite extends UINamingContainer {

    public MyComposite() {
        // Set default renderer.
        setRendererType(MySelectOneRadioRenderer.RENDERER_TYPE);
    }

}

Note that thanks to the @FacesRenderer, you don't need to hassle with faces-config.xml.


Whatever way you choose to encode the children, you can get component's children just by UIComponent#getChildren(). When you're inside MySelectOneRadio component:

if (getChildCount() > 0) {
    for (UICompnent child : getChildren()) {
        // ...
    }
}

Or when you're inside MySelectOneRadioRenderer renderer:

if (component.getChildCount() > 0) {
    for (UICompnent child : component.getChildren()) {
        // ...
    }
}

To delegate to the component's own default rendering, invoke super.encodeChildren() or component.encodeChildren(). To delegate to child's own default rendering, invoke child.encodeAll().

See also:

  • What is the relationship between component family, component type and renderer type?
Tuesday, August 10, 2021
 
xrock
answered 4 Months ago
94

Why can't I use arrow functions along with an assignment expression in ES2015 classes?

Because that's just not how the ES2015 class syntax is designed — for now, see under the line below.

Is there a concise way to achieve my goal anyway?

It's not clear to me that you want classes at all, just an object:

const List = {
  map: f => xs => xs.map(x => f(x)),
  of:  x => [x]
};

(You've said that extending is important to what you're doing.)

But if you want List to extend Array (e.g., you will have instances) but then add these statics to it, you'll need a two-step:

let List = Object.assign(
  class List extends Array { },
  {
    map: f => xs => xs.map(x => f(x)),
    of:  x => [x]
  }
);

console.log(List.of(42)); // [42]

If you want them non-enumerable or non-configurable, etc., you'll want Object.defineProperties rather than Object.assign; I'll leave that as an exercise for the reader...


There's a Stage 3 proposal for class "fields," including static fields, which is being actively implemented by JavaScript engine builders. (And you can use it now via tools like Babel.) It provides static field declaration syntax within the class, almost exactly the way you showed them:

// Not in the language yet, but at Stage 3 and shipping without
// any flags in V8 (for instance, in Chrome)
class List extends Array {
  static map = f => xs => xs.map(x => f(x));
  static of = x => [x];
}

console.log(List.of(42)); // [42]

Note: There's a standard Array.of method, so I wouldn't add an incompatible of to that List.

Finally, I should note that unless there's some reason they have to be arrow functions, ES2015's class syntax supports static methods:

// ES2015+
class List extends Array {
  static map(f) {
    return xs => xs.map(x => f(x));
  }
  static of(x) {
    return [x];
  }
}

console.log(List.of(42)); // [42]
Friday, August 13, 2021
 
iammichael
answered 4 Months ago
87

Based on the MDN link, (near the bottom), Safari does not yet support this feature.

Tuesday, October 12, 2021
 
user308827
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share