[6:34 PM] marekweb: I started using ES6 classes for my React components because ES6 is the new hotness and all that, but I'm realizing that I can't actually name any advantages, and I have to bind methods manually. So why use classes over React.createClass?
[6:34 PM] acemarke: a few reasons
[6:35 PM] acemarke: first, classes are an official part of the language
[6:35 PM] acemarke: second, that means that tooling understands the syntax
[6:35 PM] acemarke: third, createClass is eventually going to be deprecated
[6:36 PM] acemarke: there's various solutions to the method binding issue
[6:36 PM] acemarke: the suggested one is to use the "class properties" syntax, which isn't yet final, but I think may have recently hit stage 3
[6:37 PM] acemarke: ah... no, looks like it's still stage 2. Object spread hit stage 3, that's what I was thinking of
[6:37 PM] acemarke: that said, class properties are well supported in Babel, and used in tools like create-react-app
[6:37 PM] acemarke: there's also various "autobind" functions and decorators out there
[6:39 PM] acemarke: I have pointers to several articles on this topic as part of my React links list, at https://github.com/markerikson/react-redux-links/blob/master/using-react-with-es6.md
[6:41 PM] LucasKA: Ohh, speaking of binding methods on ES6 classes, that means it binds to the instance of that class, right? Because not doing the binding, the methods still work on my components right now.
[6:41 PM] acemarke: "binding" means that when the function is called, this should refer to the class instance
[6:41 PM] acemarke: as opposed to, say, window
[6:42 PM] acemarke: this is important when you pass around methods as callbacks
[6:42 PM] rag: And there are (important) people who'd like to kill 'class' with fire:
[6:42 PM] rag: https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3
[6:42 PM] acemarke: yes, yes, definitely a number of people who hate the keyword
[6:42 PM] acemarke: but it's there, and it's being used.
[6:43 PM] acemarke: and more to the point, it's used by React
[6:43 PM] acemarke: there's certainly more FP-style utilities out there to let you start with a functional component and compose lifecycle behavior into it
[6:43 PM] acemarke: but those all really wind up delegating to something like createClass
under the hood anyway
[6:44 PM] acemarke: also note that the React team encourages you to not deeply extend components
[6:44 PM] acemarke: ie, no SubClass extends AnotherComponent extends YetAnotherComponent extends React.Component
[6:44 PM] LucasKA: oh god
[6:45 PM] acemarke: basically, the direction is to use one level of inheritance to make use of React.Component, but that's it
[6:45 PM] LucasKA: Yeah, no. Extend the component, and compose those is the pattern in react and it works well for me so far.
[6:45 PM] acemarke: yup
[6:45 PM] LucasKA: I'm way too dumb for multiple inheritance.
[6:46 PM] acemarke: I did some screwy things with Java generics in a GWT app I wrote several years back
[6:46 PM] acemarke: never again :)
[6:47 PM] rag: I just use stateless functional components by default and connect to Redux state when needed (and weep quietly for a moment when I need to go stateful to process the forms)
[6:47 PM] acemarke: I generally go classes first, so that I can define callbacks as class methods
[6:48 PM] acemarke: usually involving grabbing a couple fields out of props, and passing them to a callback prop
[6:50 PM] LucasKA: Lifecycle methods autobind to the instance?
[6:50 PM] acemarke: sort of. React calls them using the instance as this
[6:51 PM] acemarke: in general, there's several ways to indicate what this
should be for a method
[6:51 PM] acemarke: if it's on an object, calling someObject.someMethod()
will set this
to be the object on the left of the dot
[6:51 PM] acemarke: whereas going const {someMethod} = someObject; someMethod()
will not set this
[6:52 PM] acemarke: after that, there's Function.call
and Function.apply
[6:52 PM] acemarke: the articles in the link I pasted earlier go into more detail
[6:53 PM] acemarke: but yeah, while I don't know the exact syntax React uses internally, it's the moral equivalent of yourComponentInstance.shouldComponentUpdate(nextProps, nextState)
, etc
[6:54 PM] acemarke: realistically speaking, you only need to bind methods that 1) are being passed around for use elsewhere, and 2) actually reference this
inside
[7:02 PM] LucasKA: @acemarke So if I set state internally on a component with a method, I don't really need to bind it in the constructor? But If I pass down a method to another component, to change that state I should bind it in the constructor?
[7:03 PM] LucasKA: A good example would be, incrementing state in the container with a timer, vs having a stateless button component that recieves the increment function as a prop.
[7:04 PM] acemarke: both of those examples would probably need to be bound
[7:04 PM] acemarke: if you have a class that looks like this:
[7:05 PM] acemarke:
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {counter : 0};
}
incrementCounter() {
this.setState({counter : this.state.counter + 1});
}
// render stuff here
}
[7:06 PM] acemarke: and you try to do setTimeout(this.incrementCounter, 1000)
, then the method would need to be bound in order for this.setState to work
[7:06 PM] acemarke: ditto for rendering <SomeButton onClick={this.incrementCounter} />
[7:07 PM] acemarke: incrementCounter
totally fits the criteria for needing to bind: we're passing around a reference to the method, and it uses this
inside
[7:08 PM] LucasKA: So when would you not need to bind it in the constructor
[7:09 PM] acemarke: if either of those criteria did not match, particularly if it doesn't use this inside
[7:13 PM] LucasKA: So almost always?
[7:13 PM] acemarke: well, probably most of the time.
[7:13 PM] acemarke: but if, say, I had a helper function that I was calling from a lifecycle method
[7:14 PM] acemarke:
componentWillReceiveProps(nextProps) {
if(nextProps.someField !== this.props.someField) {
this.updateSomeValueInState();
}
}
[7:14 PM] acemarke: I wouldn't need to bind updateSomeValueInState
, because 1) I'm calling it with this
on the left of the dot, and 2) this
in the lifecycle method will always be the component instance
[7:14 PM] acemarke: similarly, if I had:
[7:15 PM] acemarke:
onButtonClick() {
console.log("Button was clicked!");
}
[7:15 PM] acemarke: that wouldn't need to be bound, because it doesn't use this inside
[7:16 PM] acemarke: but, given that a large percentage of class methods being used as callbacks are probably going to reference this.props
or this.state
, then yeah, a large percentage of methods need to be bound
[7:18 PM] LucasKA: So what happens if you didn't bind and passed a method as a prop? Cause I have not been binding them, and it has been working as I expected 0.0
[7:19 PM] acemarke: are you using createClass
, or ES6 classes?
[7:19 PM] acemarke: and what are the methods doing?
[7:19 PM] LucasKA: ES6
[7:20 PM] LucasKA: I'll make a gist
[7:21 PM] LucasKA: https://gist.github.com/LucasKA/4c150cb807ab7856ebffb86a04fb45a3
[7:21 PM] acemarke: sure, you're using arrow functions inside of render
[7:22 PM] acemarke: which effectively use the this
that existed when they were declared
[7:22 PM] acemarke: if, for example, you rendered closeLightbox={this.closeLightbox}
instead, then closeLightbox
would need to be bound
[7:23 PM] acemarke: using arrow functions inside of render works, but it's generally discouraged because of performance reasons
[7:23 PM] acemarke: it's technically creating brand new methods every time render is called
[7:23 PM] acemarke: this may or may not be a real performance concern, depending on your app
[7:24 PM] LucasKA: @acemarke I see, I would rather use the more performant pattern, and deal with verbosity in the constructor
[7:24 PM] acemarke: or enable the Babel class-properties plugin, and do closeLightbox = () => { code here }
[7:25 PM] acemarke: the class properties spec says that methods defined as arrow functions are pre-bound
[7:25 PM] LucasKA: I think it's enabled, because if you look at
shouldInjectAd = (itemCount, frequency) => (itemCount % frequency === 0)
[7:25 PM] LucasKA: Line 22
[7:25 PM] acemarke: yep, sure looks like it
[7:25 PM] acemarke: you using create-react-app as your baseline, or something else?
[7:26 PM] LucasKA: No, I wrote up some webpack as I learned it.
[7:26 PM] acemarke: (also, technically that shouldInjectAdd
method could be a standalone function, not actually attached to the class)
[7:26 PM] LucasKA: Create react app doesn't lint the way I want
[7:26 PM] acemarke: if you're using babel-preset-stage-2 in your setup, then it's got class properties included
[7:27 PM] LucasKA: I do
[7:28 PM] LucasKA:
{
"presets": ["es2015", "stage-1", "stage-2", "react"]
}
[7:28 PM] acemarke: yup, that'll do
[7:28 PM] acemarke: ARROW FUNCTION ALL THE THINGS!!!
[7:28 PM] LucasKA: Does that mean I can refactor the ones that should be bound into class properties?
[7:28 PM] LucasKA: SRS
[7:28 PM] acemarke: might as well, yeah
[7:28 PM] LucasKA: sweet
[7:28 PM] acemarke: I had used an autobind utility function myself originally, then switched over to class properties
[7:29 PM] acemarke: that also allows you to declare initial state on the class as well
[7:29 PM] acemarke: don't need to declare constructors very often
[7:29 PM] LucasKA: class properties allow you to do that, or the autobind did?
[7:29 PM] acemarke: class properties
[7:30 PM] acemarke:
class MyComponent extends Component {
state = {counter : 0}
}
[7:30 PM] LucasKA: Wait, I can just do that?
[7:30 PM] acemarke: yep
[7:31 PM] LucasKA: holy awesome
[7:31 PM] acemarke: :)
[7:31 PM] LucasKA: Shit just keeps getting more simple
[7:31 PM] LucasKA: Thanks for the conversation, I learned a lot. I also understand more than I think
[7:31 PM] acemarke: sure
[7:31 PM] acemarke: and if you do want more details on some of that stuff, the link I pasted at the start of the discussion has a bunch of article links
New Messages
[7:32 PM] LucasKA: I'll be reading up on that, good stuff.