No need to bind functions in React Component Classes anymore? - javascript

I just noticed, that if I define normal class component functions, I do not need to bind these anymore inside the class constructor... (so even if I dont use ES6 public class field syntax), I can just normally pass those functions to my event handlers via onClick={this.someFunction} without the need to bind them to my class beforehand and it does not throw me an error when the native DOM event (or synthetic event in React's case) is executed. And it also does not matter if I use an arrow function as my event handler or just pass the function reference...
is this a new feature to React? I think some months ago, this feature was not there yet..
EDIT: here is some code example, its a simple newsfeed api app, where index has a ListItem subcomponent thats passed the click handler...
import {React, Component, fetch, API_KEY, BASE_URL} from '../config/config';
import ListComponent from '../components/ListComponent';
import PropTypes from 'prop-types';
import { makeStyles } from '#material-ui/core/styles';
import ListItem from '#material-ui/core/ListItem';
import ListItemText from '#material-ui/core/ListItemText';
import { List } from '#material-ui/core';
const useStyles = makeStyles(theme => ({
root: {
width: '100%',
height: 400,
maxWidth: 360,
backgroundColor: theme.palette.background.paper,
},
}));
export default class index extends Component {
constructor(props) {
super(props);
this.state = {
news: this.props.news
}
}
static async getInitialProps() {
let querystring = `${BASE_URL}top-headlines?q=sex&fsortBy=popularity&apiKey=${API_KEY}`;
console.log(querystring);
let news = await fetch(querystring);
news = await news.json();
//console.log("NEWS:",news.articles);
return {
news: news.articles
}
}
getOutput (e) {
console.log("Item ",e," was clicked!");
}
render() {
return (
<div>
{
this.state.news.map((news,index) => (
// <ListItem button
// key={index}
// onClick={e => this.getOutput(e)}>
// <ListItemText primary={`${news.title}`} />
// </ListItem>
<ListComponent
index = {index}
news = {news}
clicked = {this.getOutput}
/>
)
)
}
</div>
)
}
}
Here is the List subcomponent:
import React from 'react'
export default function ListComponent({index,clicked,news}) {
return (
<li key={index} onClick ={clicked}>
{
news.title
}
</li>
)
}
I just tested it, and it worked! Note: This is a Next.js example, but I have tested it in a normal React-app (created with create-react-app), too and it worked with same kind of example...
when I click a list item I get console output:
Item Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: li, …} was clicked!

This is not a new feature of React. You can access any function or property from within class without binding. The reason why you do binding( or declare arrow function) is to connect local this to global context so it can refer to the class(parent function). Try using e.g this.props inside getOutput function and you will get an error.

This is not related to react but how JavaScript's class and this works.
In your example you are not getting errors because you are not doing anything wrong. Although when you want to call this.setState or reference anything via this you may get an error or unexpected results because this will not reference what you think it will, it will reference the element that triggered the event.
Why class field with arrow functions "solve" the problem without hard binding the this? because they way they "handle" the this context, which they actually don't do anything. meaning, what ever the this reference in the wrapping execution context, that's the reference you will get inside an arrow function.
By the way, there is a difference between a class field functions and class methods is that class methods are created on the prototype and fields are created on the instance.
I've made a simple flow chart that may help understand what is this referencing to for a given context (always top to bottom, order matters)

Related

how to throw a react-alert inside of a function in a class based component

Using react-alert module for alerts
My code looks like this -
index.js:
const options = {
// you can also just use 'bottom center'
position: positions.TOP_CENTER,
timeout: 5000,
offset: '30px',
type: types.ERROR,
// you can also just use 'scale'
transition: transitions.FADE
}
ReactDOM.render(<AlertProvider template={AlertTemplate} {...options}>
<App /></AlertProvider>, document.getElementById('root'));
App.js
class App extends React.Component { //then my state, functions, constructors,
//here is the problem
nextClicked = (e) => {
if (//something) {
if (//something) {
const alert = useAlert();
alert.show("ERROR MESSAGE!!!");
}
} // etc
export default withAlert()(App)
Basically, I am getting the error
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/warnings/invalid-hook-call-warning.html for tips about how to debug and fix this problem.
From their docs, it says you can use it with a higher order-component. So, if you
import the withAlert module from react-alert, you can wrap your component when you export it. Again, check the docs on github, this is covered.
Converting the example from the docs to a class component, you get:
import React from 'react'
import { withAlert } from 'react-alert'
class App extends React.component {
constructor(props) {
super(props);
}
render {
return (
<button
onClick={() => {
this.props.alert.show('Oh look, an alert!')
}}
>
Show Alert
</button>
)
}
}
export default withAlert()(App)
Because you wrap the component in the HOC, you get access to the alert prop.

How to unit test React functions passed in as prop to child component with Enzyme shallow wrapper

I am very new to front-end dev & I am having some trouble getting my Enzyme unit tests using Shallow. Basically, I have something like
class MyComponent extends React.Component {
constructor(props, context) {
super(props, context);
}
render() {
const {
handleClick,
...other
} = this.props;
return (
<div className="someClass">
// a bunch of stuff
<div className="buttonArea">
<MyComponentChild onClick={handleClick} />
</div>
</div>
);
}
MyComponent.propTypes = {
handleClick: PropTypes.func.isRequired,
...other
};
export default MyComponent;
}
handleClick is a callback function defined in the container (i.e ComponentContainer) that MyComponent belongs to. I am passing it as a prop into MyComponent, and subsequently MyComponentChild (which is a button component). I want to test whether handleClick fires when MyComponentChild is clicked.
My current Enzyme test
it('handleClick should fire when clicked', () => {
const mockCallbackFn = jest.fn();
const wrapper = shallow(<MyComponent {handleClick = { mockCallbackFn }} />);
wrapper.find('MyComponentChild').simulate('click');
expect(mockCallbackFn).toHaveBeenCalled();
});
However, this test is currently failing, as mockCallbackFn is apparently never called. But, this is also passing
expect(wrapper.find('MyComponentChild').prop('handleClick')).toEqual(mockCallbackFn);
What am I doing wrong? Any help much appreciated!
simulate(someEventName) does really simple thing: it calls prop with name of "on" + someEventName. So simulate('click') runs .props().onClick().
But your component uses handleClick prop, that's why it does not called by simulate()
wrapper.find('MyComponentChild').props().handleClick();
Name simulate is so confusing that team is going to remove it out(https://github.com/airbnb/enzyme/issues/2173).
Side note: you don't need extra braces when declaring props. I mean {handleClick = { mockCallbackFn }} better be handleClick={mockCallbackFn} since it's typical for React code and looks less confusing.
You need to use mount instead of shallow. shallow only mounts the first level of components, so your ComponentChild is not being mounted and your click handler isn't being passed in.
You can see this yourself by calling debug() on your wrapper and console.log-ing it.

Grandparent component doesn't pass argument to function

I have some doubts trying to understand how react works with functions as props, passing them around to child components. I already saw some tutorials but didn't grasp my issue at the moment.
Basically I have a simple component that passes a list down, and other component that handles the list with Array.map to render another component.
Basically I have the following:
App.js -> Quotes -> Quote.
And I want to handle the click on the Quote component. So everytime the user clicks Quote I want to handle it on the APP.js.
I already tried to pass the reference as a prop down and in the app.js quotes component to call the function, but it didn't work.
This is what I tried till now:
App.js
import React, { Component } from 'react';
import classes from './App.module.css';
import Quotes from '../components/quotes/quotes'
class App extends Component {
state = {
quotes: [
{ id: 1, text: "Hello There 1" },
{ id: 2, text: "Hello There 2" },
{ id: 3, text: "Hello There 3" },
{ id: 4, text: "Hello There 4" }
],
clickedQuote: "none"
}
handleClickedQuote (id) {
console.log(id)
const quoteIndex = this.state.quotes.findIndex(q => q.id === id)
this.setState({
clickedQuote: this.state.quotes[quoteIndex].text
})
}
render() {
return (
<div className="App">
<div className={classes['quotes-wrapper']}>
<Quotes clicked={this.handleClickedQuote} quotes={this.state.quotes}/>
<p>clicked quote {this.state.clickedQuote}</p>
</div>
</div>
);
}
}
export default App;
Quotes.js
import React from 'react';
import Quote from './quote/quote'
const quotes = (props) => props.quotes.map((quote) => {
return (
<Quote clicked={props.clicked} text={quote.text}/>
)
})
export default quotes
Quote.js
import React from 'react';
import classes from './quote.module.css'
const quote = (props) => {
return (
<div onClick={() => props.clicked(props.id)} className={classes.quote}>
<p className={classes['quote-text']}>{props.text}</p>
</div>
)
}
export default quote
I need to get the id on the hanleClickedQuote in the App.js function. What am I doing wrong?
You need to explicitly pass the id as a prop. So, in Quotes.js in your map(),
something like:
<Quote id={quote.id} clicked={props.clicked} text={quote.text}/>
Update: as #Ashkan said in their answer, you also need to properly bind your handler.
Well there are two things going very wrong in your code. First one is a common problem in JS community. I suggest you read deeper into the usage of the 'this' keyword. in App.js you are defining your method as a function declaration.
handleClickedQuote(id) {
console.log(id)
const quoteIndex = this.state.quotes.findIndex(q => q.id === id)
this.setState({
clickedQuote: this.state.quotes[quoteIndex].text
})
}
Now the 'this' keyword in function declarations are dynamically set, meaning 'this' here actually gets set when the function gets called and since it's an event handler, the value of 'this' will actually be your event! You can test it out. But we want 'this' to refer to our class so we can access the state.
There are two ways to fix this, first:
You can bind the correct value for 'this' in the constructor of your App.js class like this (the old way):
constructor(props) {
super(props);
this.handleClickedQuote = this.handleClickedQuote.bind(this);
}
This replaces the method in your class with a version that uses the correct 'this' value at the construction step of your object.
Or simpler yet, you can use an arrow function since the 'this' keyword in an arrow function is set lexically:
handleClickedQuote = id => {
console.log(id);
const quoteIndex = this.state.quotes.findIndex(q => q.id === id);
this.setState({
clickedQuote: this.state.quotes[quoteIndex].text
});
}
NOTE: In an arrow function the value of 'this' basically refers to whatever is outside of that code block which in this case is your entire object.
Also you have a minor mistake in your code which someone mentioned. You actually forgot to pass the id of the quote as a prop to the Quote component. But that's just a minor oversight.
It's important to know this problem has less to do with React than JS itself. My advice is getting a little deeper into JS and learning all the quirks and technicalities of the language. It will save you a lot of hassle in the future. Also work on your debugging skills so mistakes like the missing prop don't fall through the cracks so easily.
Best of luck

Why I'm able to use this.state without having to bind or use arrow function React

I know that arrow functions inherit the context of the parent, that's why they're so useful in React. However, I have this React component:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class AlbumList extends Component {
constructor(props) {
super(props);
this.state = {
albums: [],
};
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => {
this.setState({ albums: response.data });
});
}
renderAlbums() {
const { albums } = this.state;
const array = albums.map(album => (
<Text>{album.title}</Text>
));
return array;
}
render() {
return (
<View>
{ this.renderAlbums() }
</View>
);
}
}
export default AlbumList;
And { this.renderAlbums() } is working complete fine without me having to transform renderAlbums() into an arrow function. I've been reading other answers on stackoverflow, but they all mention that you NEED arrow function or bind in order for this to work properly. But in my case it works fine as it is, so why use arrow function inside es6 class?
If you're using arrow functions then what "this" is is defined by the block that the function is defined in. If you're using "normal" functions then "this" is defined by the place the function gets called from. In this case you're calling it from within the render method so "this" is still an instance of the component. If you tried calling a function like that from something like a buttons onClick then it would fail to find "setState" as "this" would basically be defined by the actual rendered button and not the react class.
Simply the arrow functions inherit this from their parent's scope, but the regular functions inherit this from where the function gets called

Accessing this.props from React Component functions

I'm new to React.js and is confused by how the following 2 functions displayGender and toggleGender access this.props differently.
import React, { Component } from 'react';
export default class User extends Component {
displayGender() {
console.log('displayGender: ', this.props)
}
toggleGender() {
console.log('toggleGender: ', this.props)
}
render() {
return (
<Button onClick={ this.toggleGender.bind(this) } >
{ this.displayGender.bind(this) }
</Button>
);
}
}
Why is this.toggleGender.bind(this) able to access this.props that was passed to this React Component User, but this.displayGender.bind(this) sees this.props as undefined?
So far I am only able to access this.props from within this.displayGender if I pass it to the function. Is this the usual practice?
export default class User extends Component {
displayGender(props) {
console.log(props.user)
}
render() {
return (
<Button onClick={ this.toggleGender.bind(this) } >
{ this.displayGender(this.props) }
</Button>
);
}
}
Why is this.toggleGender.bind(this) able to access this.props that was
passed to this React Component User, but this.displayGender.bind(this)
sees this.props as undefined?
I'm not certain it's necessary to bind "this" to displayGender because unlike toggleGender it should already be bound to the correct object by default. I believe you should be able to access this.props from displayGender just by invoking the method.
The Handling Events section in the React docs might be helpful.
That's because your this.toggleGender.bind(this) function is added as a click event in your component and this.displayGender.bind(this) is added as a child node inside the <button>
What bind usually refers is put the given function inside some other anonymous enclosed function (and preserve the context ie, this if supplied)
So: toggleGender.bind(this) usually refers to function(){toggleGender()}
When the above is placed in click function it makes sense as we're attaching a function to click event. But it wouldn't make sense to add it as a child node

Categories