bind(this) in ReactJS and onChange update to state - javascript

I have the following code:
index.js
class App extends React.Component {
constructor() {
super();
this.state = {
homeLink: "Home"
};
}
onGreet() {
alert("Hello!");
}
onChangeLinkName(newName) {
this.setState({
homeLink: newName
});
}
render() {
return (
<div className="container">
<div className="row">
<div className="col-xs-10 col-xs-offset-1">
<Header homeLink={this.state.homeLink}/>
</div>
</div>
<div className="row">
<div className="col-xs-10 col-xs-offset-1">
<Home
name={"Max"}
initialAge={27}
greet={this.onGreet}
changeLink={this.onChangeLinkName.bind(this)}
initialLinkName={this.state.homeLink}
/>
</div>
</div>
</div>
);
}
}
And
Home.js
export class Home extends React.Component {
constructor(props) {
super();
this.state = {
age: props.initialAge,
status: 0,
homeLink: props.initialLinkName
};
}
onMakeOlder() {
this.setState({
age: this.state.age + 3
});
}
onChangeLink() {
this.props.changeLink(this.state.homeLink);
}
onHandleChange(event) {
this.setState({
homeLink: event.target.value
});
}
render() {
return (
<div>
<p>In a new Component!</p>
<p>Your name is {this.props.name}, your age is {this.state.age}</p>
<p>Status: {this.state.status}</p>
<hr/>
<button onClick={() => this.onMakeOlder()} className="btn btn-primary">Make me older!</button>
<hr/>
<button onClick={this.props.greet} className="btn btn-primary">Greet</button>
<hr/>
<input type="text" value ={this.state.homeLink}
onChange={(event) => this.onHandleChange(event)}/>
<button onClick={this.onChangeLink.bind(this)} className="btn btn-primary">Change Header Link</button>
</div>
);
}
}
Would onChange in the input tag be triggered as soon as I write something in the input field and update to state? I can't see the state change in the React Developer Tool extension in Chrome when I write something in the input field.
When I click the button this.onChangeLink it triggers the onChangeLink function. The onChangeLink doesn't seem to take any arguments since the brakets are empty, still I'm able to pass this.state.homeLink to this.props.changeLink inside the onChangeLink function. this.props.changeLink which is also a function in index.js takes an argument newName. I guess this is where the bind(this) comes in. What does bind(this) do? Could I rewrite it with a fat arrow function like (event) => this.onChangeLink(event)?

bind is a function in Function.prototype which returns a function object which is bound to the current this.
onClick={this.onChangeLink.bind(this)}
Here onClick function will be passed as a handler with the current context.
An arrow function does not create a new this context at all. The this will refer to the context in which the function is defined.
An arrow function does not create its own this context, so this has its original meaning from the enclosing context.
onChange={(event) => this.onChangeLink(event)}
Here even though onChangeLink is not bound, it is called within an arrow function within the same this context.
So.. yes, you can replace it with a fat arrow notation to get the same effect. Although you have to rewrite the list of arguments in this case twice.

You dont need to onChangeLink in Home component. You can directly pass the value to App component.
class Home extends React.Component {
constructor(props) {
super();
this.state = {
age: props.initialAge,
status: 0,
homeLink: props.initialLinkName
};
}
onMakeOlder() {
this.setState({
age: this.state.age + 3
});
}
onHandleChange(event) {
this.setState({
homeLink: event.target.value
});
}
render() {
return (
<div>
<input type="text" value ={this.state.homeLink}
onChange={(event) => this.onHandleChange(event)}/>
<button onClick={()=>this.props.changeLink(this.state.homeLink)} className="btn btn-primary">Change Header Link</button>
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
homeLink: "Home"
};
}
onGreet() {
alert("Hello!");
}
onChangeLinkName(newName) {
this.setState({
homeLink: newName
});
}
render() {
console.log(this.state)
return (
<div className="container">
<div className="row">
<div className="col-xs-10 col-xs-offset-1">
</div>
</div>
<div className="row">
<div className="col-xs-10 col-xs-offset-1">
<Home
name={"Max"}
initialAge={27}
greet={this.onGreet}
changeLink={this.onChangeLinkName.bind(this)}
initialLinkName={this.state.homeLink}
/>
</div>
</div>
</div>
);
}
}
ReactDOM.render(<App />, 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>

Related

why does one react project use .bind(this) method but another react project does not use it? [duplicate]

This question already has answers here:
Arrow vs classic method in ES6 class
(1 answer)
Why we don't need to bind the arrow function in React?
(2 answers)
Closed 2 years ago.
I did 2 tutorials on using REACT. "to-do-app" is a django/react project while "robofriends" is a purely react project. "to-do-app" uses .bind(this) for its functions while "robofriends" does not use this method. While i understand why the .bind method is needed, i do not understand why "robofriends" was able to work without binding its functions.
to-do app (apps.js)
import React from 'react';
import './App.css';
class App extends React.Component{
constructor(){
super();
this.state={
todoList:[],
activeItem:{
id:null,
title:"",
completed:false
},
editing:false,
}
this.fetchTasks=this.fetchTasks.bind(this)
this.handleChange=this.handleChange.bind(this)
this.handleSubmit=this.handleSubmit.bind(this)
}
componentWillMount(){
this.fetchTasks()
}
fetchTasks(){
console.log('fetching')
fetch('http://127.0.0.1:8000/api/task-list/')
.then(response=>response.json())
.then(data=>
this.setState({todoList:data})
)
}
handleChange(e){
const name=e.target.name;
const value=e.target.value;
console.log('Name', name)
console.log('Value', value)
this.setState({
activeItem:{
...this.state.activeItem,
title:value
}
})
}
handleSubmit(e){
e.preventDefault()
console.log('ITEM', this.state.activeItem)
var url='http://127.0.0.1:8000/api/task-create/'
fetch(url, {
method:'POST',
headers:{
'Content-type':'application/json',
},
body:JSON.stringify(this.state.activeItem)
}).then((response) =>{
this.fetchTasks()
this.setState({
activeItem:{
id:null,
title:"",
completed:false
}
})
}).catch(function(error){
console.log('ERROR', error)
})
}
render(){
const tasks=this.state.todoList
return(
<div className="container">
<div id="task-container">
<div id="form-wrapper">
<form onSubmit={this.handleSubmit} id="form">
<div className="flex-wrapper">
<div style={{flex:6}}>
<input onChange={this.handleChange} className="form-control" id="title" type="text" name="title" placeholder="Add task" />
</div>
<div style={{flex:1}}>
<input id="submit" className="btn btn-warning" type="submit" name="Add" />
</div>
</div>
</form>
</div>
<div id="list-wrapper">
{tasks.map((task, index)=>{
return(
<div key={index} className="task-wrapper flex-wrapper">
<div style={{flex:7}}>
<span>{task.title}</span>
</div>
<div style={{flex:1}}>
<button className="btn btn-sm btn-outline-info">Edit</button>
</div>
<div style={{flex:1}}>
<button className="btn btn-sm btn-outline-dark delete">-</button>
</div>
</div>
)
})}
</div>
</div>
</div>
)
}
}
export default App;
robofriends(apps.js)
import React, {Component} from 'react';
import CardList from '../components/Cardlist';
// import {robots} from './robots';
import SearchBox from '../components/SearchBox';
import './App.css';
import Scroll from '../components/Scroll';
import ErrorBoundary from '../components/ErrorBoundary';
class App extends Component {
constructor(){
super()
this.state={
robots:[],
searchfield:''
} //a state is what changes in an app
}
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/users')
.then(response=> response.json())
.then(users => this.setState({robots:users}))
}
// remember to use => for functions you create yourself.
onSearchChange=(event) => {
this.setState({searchfield:event.target.value})
}
render(){
const {robots, searchfield}=this.state;
const filteredRobots=robots.filter(robot=>{
return robot.name.toLowerCase().includes(searchfield.toLowerCase());
})
if (!robots.length){
return <h1>Loading</h1>
}else{
return(
<div className='tc'>
<h1 className='f1'>Robofriends</h1>
<SearchBox searchChange={this.onSearchChange}/>
<Scroll>
<ErrorBoundary>
<CardList robots={filteredRobots}/>
</ErrorBoundary>
</Scroll>
</div>
);
}
}
}
export default App;
robofriends 'searchbox.js'
import React from 'react';
const SearchBox=({searchChange})=>{
return (
<div className='pa2'>
<input
className='pa3 ba b--green bg-lightest-blue'
type='search'
placeholder='search robots'
onChange={searchChange}
/>
</div>
);
}
export default SearchBox;
fetchTasks(){
if you define a function like this, then you need to .bind(this)
as this will lose the context of this of class, to maintain it we need to use .bind(this)
But if you use below one then no need to bind, it maintains the context of this, so no need of using .bind(this)
Arrow functions do not bind their own this, instead, they inherit the
one from the parent scope, which is called "lexical scoping"
fetchTasks = () => {
For more detail : DO READ
Hope the below snippet will help you get understanding :
const { useState , useEffect } = React;
class App extends React.Component {
constructor() {
super();
this.state = {
name : "Vivek"
}
this.withBound = this.withBound.bind(this);
}
withoutBound() {
console.log(this.state.name)
}
withBound() {
console.log(this.state.name)
}
withFatArrow = () => {
console.log(this.state.name)
}
render() {
return (
<div>
Check the console on click:
<br/><br/>
<button onClick={this.withoutBound}>Without Bind</button>
<br/><br/>
<button onClick={this.withBound}>With Bind</button>
<br/><br/>
<button onClick={this.withFatArrow}>Fat Arrow</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('react-root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>

Passing ref up to parent in react

I have a little app that has an input and based on the search value, displays weather for a particular city. I'm stuck at a certain point though. The idea is that once you search a city, it hides the text input and search button and displays some weather info and another search button to search a new city. My issue is that I want to focus on the search box once I click to search again. I hope that makes sense. I read that the ideal way to do this is with refs. I wired it up like such:
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
With this I can pass that ref up to the parent to use in my search by using this.state.myRefs.current.value; It works great, but when I try to reference this.state.myRefs.current in a different function to use .focus(), it returns null.
resetSearch = () => {
console.log(this.state.myRefs.current); // <- returns null
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
Is this because I'm hiding and showing different components based on the search click? I've read numerous posts on SO, but I still can't crack this. Any help is appreciated. I'll include the full code below. To see it in full here is the git repo: https://github.com/DanDeller/tinyWeather/blob/master/src/components/WeatherMain.js
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {
recentCities: [],
details: [],
isOpen: true,
myRefs: '',
video: '',
city: ''
};
this.updateInputValue = this.updateInputValue.bind(this);
this.getRefsFromChild = this.getRefsFromChild.bind(this);
this.resetSearch = this.resetSearch.bind(this);
this.getWeather = this.getWeather.bind(this);
}
updateInputValue = (e) => {
...
}
resetSearch = () => {
console.log(this.state.myRefs.current);
this.setState({
isOpen: !this.state.isOpen,
details: [],
video: []
});
}
getWeather = (e) => {
...
}
getRefsFromChild = (childRefs) => {
...
}
render() {
return (
<section className={style.container}>
<div className={style.weatherMain + ' ' + style.bodyText}>
<video key={this.state.video} className={style.video} loop autoPlay muted>
<source src={this.state.video} type="video/mp4">
</source>
Your browser does not support the video tag.
</video>
<div className={style.hold}>
<div className={style.weatherLeft}>
<WeatherForm
updateInputValue={this.updateInputValue}
getWeather={this.getWeather}
passRefUpward={this.getRefsFromChild}
resetSearch={this.resetSearch}
isOpen={this.state.isOpen}
/>
<WeatherList
details={this.state.details}
city={this.state.city}
isOpen={this.state.isOpen}
/>
</div>
<div className={style.weatherRight}>
<Sidebar
recentCities={this.state.recentCities}
/>
</div>
<div className={style.clear}></div>
</div>
</div>
</section>
);
}
}
class WeatherForm extends React.Component {
constructor(props) {
super(props);
this.city = React.createRef();
}
componentDidMount() {
this.props.passRefUpward(this.city);
this.city.current.focus();
}
render() {
if (this.props.isOpen) {
return (
<div className={style.weatherForm}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
)
} else {
return (
<div className={style.resetButton}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
);
}
}
}
export default Weather;
You try to achieve unmounted component from DOM, because of this you can not catch the reference. If you put this code your instead of render function of WeatherForm component, you can catch the reference. Because i just hide it, not remove from DOM.
render() {
return (
<div>
<div className={style.weatherForm}
style={this.props.isOpen ? {visibility:"initial"} :{visibility:"hidden"}}>
<form action='/' method='GET'>
<input
ref={this.city}
onChange={this.props.updateInputValue}
type='text'
placeholder='Search city'
/>
<input
onClick={e => this.props.getWeather(e)}
type='submit'
value='Search'
/>
</form>
</div>
<div className={style.resetButton} style={this.props.isOpen ? {visibility:"hidden"} :{visibility:"initial"}}>
<p>Seach another city?</p>
<button
onClick={this.props.resetSearch}>Search
</button>
</div>
</div>
)
}
console.log(this.state.myRefs.current) returns null , because it's a reference to an input dom element which does not exists as currently Weather form is displaying Search another city along with a reset button.
In reset function state changes, which results in change of prop isOpen for WeatherForm component. Now, screen would be displaying the input field along with search button.
After component is updated ComponentDidUpdate lifecycle method is called.
Please add ComponentDidUpdate lifecycle method in WeatherForm and add ,
this.city.current.focus() in the body of method.
There is no need to pass reference of a dom element to the parent element as it is not consider as a good practise.
Edit 1 :-
Need to set input field in focus only if prop ( isOpen ) is true as we will get reference to the input field only if its mounted.
ComponentDidUpdate(){
if(this props.isOpen)
this.city.current.focus
}
Link to Lifecycle method :-
https://reactjs.org/docs/react-component.html#componentdidupdate
Hope this helps,
Cheers !!

Why TypeError for onclick function? - ReactJS

I am developing an app to fetch posts from a API and load posts.Everything is working fine except gotopost function.After rendering this error comes TypeError: Cannot read property 'gotopost' of undefined
But don't know why this is happening.Though i bind that function and declared that function above the render() function
below is the code
import React, { Component } from 'react';
import './App.css';
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
posts: [],
singleLoaded: false,
singlepost: []
}
this.fetchposts = this.fetchposts.bind(this);
}
fetchposts(){
fetch("http://smashingdevs.com/wp-json/wp/v2/posts/")
.then((response) => {
return response.json();
})
.then((posts) => {
this.setState({
isLoaded: true,
posts: posts
});
});
}
render() {
let container;
if(this.state.isLoaded){
container = <Allposts posts={this.state.posts}/>;
}else if(this.state.singleLoaded){
container = <Singleposts />;
}
return (
<div>
<h2>Hello there</h2>
<button onClick={this.fetchposts}>
fetch posts
</button>
{container}
</div>
);
}
}
class Allposts extends React.Component{
constructor(props){
super(props);
}
gotopost(){}
render(){
return (<div className="row mt-5">
{
this.props.posts.map(function(post){
return <div className="col-md-6 mb-2" key={post.id}>
<div className="card" >
// Problem is here on onClick
Go somewhere
</div>
</div>
})
}
</div>
)
}
}
class Singleposts extends React.Component{
}
export default Todos;
Use arrow function:
this.props.posts.map((post) => {
this will not be what you expect inside the function given to map. You can bind the function to this, or you can use an arrow function to use the surrounding lexical scope, and this will be what you want.
class Allposts extends React.Component {
gotopost() {}
render() {
return (
<div className="row mt-5">
{this.props.posts.map(post => {
return (
<div className="col-md-6 mb-2" key={post.id}>
<div className="card">
<a
href={post.link}
onClick={this.gotopost.bind(this)}
className="btn btn-primary"
>
Go somewhere
</a>
</div>
</div>
);
})}
</div>
);
}
}
This is common error of JS. When you go to internal function, var this in this scope is no longer outside class anymore. The workaround is assigning this to another variable at the top of method.
render(){
const self = this;
return (<div className="row mt-5">
{
this.props.posts.map(function(post){
return <div className="col-md-6 mb-2" key={post.id}>
<div className="card" >
// use self instead this
Go somewhere
</div>
</div>
})
}
</div>
)
}

this.someFunction is not a function

After having read about the bind requirement for methods to be bound to a React ES6 class, I am still having some difficulty with this example:
class ProductList extends React.Component {
constructor(props) {
super(props);
this.state = { products: [] };
this.updateState = this.updateState.bind(this);
}
componentDidMount() {
this.updateState();
}
handleProductUpvote(productId) {
Data.forEach((item) => {
if (item.id === productId) {
item.votes = item.votes + 1;
return;
}
});
this.updateState();
}
updateState() {
const products = Data.sort((a,b) => {
return b.votes - a.votes;
});
this.setState({ products });
}
render() {
const products = this.state.products.map((product) => {
return (
<Product
key={'product-' + product.id}
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitter_avatar_url={product.submitter_avatar_url}
product_image_url={product.product_image_url}
onVote={this.handleProductUpvote}
/>
);
});
return (
<div className='ui items'>
{products}
</div>
);
}
}
class Product extends React.Component {
constructor() {
super();
this.handleUpvote = this.handleUpvote.bind(this);
}
handleUpvote() {
this.props.onVote(this.props.id);
}
render() {
return (
<div className='item'>
<div className='image'>
<img src={this.props.product_image_url} />
</div>
<div className='middle aligned content'>
<div className='header'>
<a onClick={this.handleUpvote}>
<i className='large caret up icon'></i>
</a>
{this.props.votes}
</div>
<div className='description'>
<a href={this.props.url}>
{this.props.title}
</a>
</div>
<div className='extra'>
<span>Submitted by:</span>
<img
className='ui avatar image'
src={this.props.submitter_avatar_url}
/>
</div>
</div>
</div>
);
}
}
ReactDOM.render(
<ProductList />,
document.getElementById('content')
);
This returns
Uncaught TypeError: this.updateState is not a function(...) at handleProductUpvote
Is the initialized binding not sufficient in this case?
Whenever you see this issue, you don't want to be adding the bind to the method that it's trying to call right then, but the method that you are inside of when the "this.xxx not defined" issue occurs.
Currently, it's getting the function handleProductUpvote just fine - but it's calling it in the wrong object context. So you need to do the same thing as you did with updateState in the constructor, but with that function. Though I have limited react knowledge I believe it's common to do that for every function that's used as an event listener or callback.

How to call setState function onClick ES6 way

class BlogPost extends React.Component{
//getInitialState
constructor(){
super();
this.onLike = this.onLike.bind(this);
this.state = {
like :0
}
}
onLike(){
this.setState({
like: this.state.like++
});
}
render(){
var postListItem = this.props.postList.map(function(post){
return <li> {post} </li>
});
return (
<div className="blogPost">
<h2>Posts</h2>
<ul>{postListItem}</ul>
<button onClick={(e) => {this.onLike}}>{this.state.like}</button>
</div>
);
}
}
Nothing happens on clicking Button.
There is no function on button and index.js has added Empty Function to it. IDK why?please explain
In this case you need remove arrow function ((e) => { }), it is not necessary here, because you bind this in constructor., also inside onLike change this.state.like++ to this.state.like + 1 because you can't mutate state
<button onClick={ this.onLike }>{this.state.like}</button>
class BlogPost extends React.Component{
constructor(){
super();
this.onLike = this.onLike.bind(this);
this.state = {
like: 0
}
}
onLike(){
this.setState({
like: this.state.like + 1
});
}
render(){
var postListItem = this.props.postList.map(function(post){
return <li> {post} </li>
});
return (
<div className="blogPost">
<h2>Posts</h2>
<ul>{ postListItem }</ul>
<button onClick={ this.onLike }>{this.state.like}</button>
</div>
);
}
}
ReactDOM.render(<BlogPost postList={ [] } />, document.getElementById('container'));
<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="container"></div>
I think you're not invoking the onLike function in the annonymous function you're passing to onClick.
Try this:
<button onClick={(e) => {this.onLike()}}>{this.state.like}</button>
Note the () after onLike.

Categories

Resources