React : Call a function from inside render function - javascript

I have referred to couple of similar questions, but I have a situation little different.
Call a React Function from inside Render
How to Call a Function inside a Render in React/Jsx
React: Cant call a function inside child component
export default class CodeEditor extends React.Component {
appendAssets(asset) {
console.log(asset)
this.refs.editor.editor.insert(`player.asset('${asset}')`)
this.refs.editor.editor.focus()
}
render() {
function sequenceHierarchy (data, outputArray) {
level++
data.forEach(function (asset){
outputArray.push(<li className={`level_${level}`}><button onClick={_ => this.appendAssets(asset.name)}>{asset.name}</button></li>)
if(asset.children.length) {
sequenceHierarchy(asset.children, outputArray)
}
})
level--
}
}
}
So the onClick of button inside sequenceHierarchy function has to call appendAssets. Of course since this wouldn't be able to call it as it is not the part of this component, I also tried with just appendAssets(asset.name), still it gives a error of Uncaught ReferenceError: appendAssets is not defined

I strongly advise you to read this answer for understanding this keyword and its behaviors inside function.
Quoting from the linked answer:
this (aka "the context") is a special keyword inside each function and its value only depends on how the function was called, not how/when/where it was defined. It is not affected by lexical scope, like other variables.
(Please read whole linked answer before proceeding further)
You can either set a reference to the variable which refers to correct this. See this JSFiddle
let that = this; // assign this reference to variable that.
function sequenceHierarchy (data, outputArray) {
data.forEach((item) => {
outputArray.push(
<li>
<button onClick={that.appendAssets.bind(this, item)}>
Append {item}
</button>
</li>
);
})
}
or
You can use bind method to set correct this, while invoking sequenceHierarchy function as follows. See this JSFiddle
{sequenceHierarchy.bind(this, (['1', '2', '3'], []))}
or
You can use ES6 arrow function to invoke sequenceHierarchy function like this.
{() => {sequenceHierarchy(['1', '2', '3'], [])}}
All above methods will work fine.
Hope this helps :)

Related

Accessing props in separate function in a functional component

I have a simple Parent Functional Component (App.js) and a child Functional component (counters.jsx), I want to pass a signal/prop from child to parent. I want to send the props as an argument to another function in my child component. I can easily trigger parent function by onClick={props.parentCallback}. but if I send the same props as an argument to another function in child component, I get the error "Expected an assignment or function call and instead saw an expression no-unused-expressions"
//This is parent Component, App.js
function App() {
return (
<div className="App">
<Counters parentCallback = {handleCallback}/>
</div>
);
}
const handleCallback = () => {
console.log("CALLED FRM CHILD");
}
//This is Child Component- Counters.jsx
function Counters (props) {
return (
<div>
<button onClick={ ()=> parentHandler(props) }>CALL PARENT</button><hr/>
{//onClick={props.parentCallback} - This works well}
</div>
);
};
function parentHandler (props) {
props.parentCallback; // This line throws the aforementioned error
}
You are just referencing the function. Not calling it. You can modify parentHandler function as
function parentHandler (props) {
props.parentCallback()
}
no-unused-expressions usually means that you evaluated a value but never used. This rule aims to eliminate unused expressions.
For example if(true) 0
In your case you are calling props.parentCallback without parenthesis, leaving unused. Either you add parenthesis after your function call like this props.parentCallback() or return some value at the end. By that your eslint error will disappear.
Also onClick={props.parentCallback} - This works well because you are just passing a reference of your function to the parent Component. But here onClick={ ()=> parentHandler(props) } you are creating a new inline function on each render.
Thank u for ur answer guys, but none of the answer is exactly what I wanted. Finally, I got the answer myself as it is:
Do you need the function to run now?
Then add the () to execute it
Do you need to function to be referenced so it is called later?
Do not add the ().
More example:
onClick=parentCallback() means call "parentCallback() ASAP, and set its return value to onClick".
On the other hand, onClick=parentCallback means "onClick is an alias for parentCallback. If you call onClick(), you get the same results as calling parentCallback()"
I found it by googling "function call and function reference".

Why is 'this' itself undefined in function scope

I have the following code:
class App extends Component {
constructor(){
super()
//this.buttonOneClick = this.buttonOneClick.bind(this);
}
buttonOneClick() {
const { setTestData } = this.props;
setTestData("test");
}
render() {
const { testData } = this.props;
return (
<div className="App">
<h1>Test App</h1>
<div>{testData}</div>
<button onClick={this.buttonOneClick}>Test</button>
</div>
);
}
}
The component App is exported with react-redux's connect method to map the setTestData-function to the props (thats where setTestData is coming from).
When I set the buttonOneClick() handler without biding the component context to it (see the outcommented line in my code in constructor()) I get the following error:
Cannot read property 'props' of undefined
I understand that props wouldn't be defined since the handler is executed in the global context which doesn't know about props but how I understand the error it implies that this itself is not defined which doesn't make sense to me. Why is this the case? Shouldn't this always be defined in any execution context?
Thanks in advance
Shouldn't this always be defined in any execution context?
No.
There is no implicit this value in strict mode.
"use strict";
function example() {
alert(this);
}
example();
We’re getting an error because this is not defined when onClick calls our buttonOneClick() function.
Usually, you would fix this by binding this to the buttonOneClick function so it always stays the same. So we have to bind that in the constructor.
this.buttonOneClick = this.buttonOneClick.bind(this);
Possible work-around if you don't want to use the binding and all is to use the es2015 javascript arrow functions
buttonOneClick = () => console.log(this.props);
An arrow function does not have its own this so it will use this off its context, which is our Component. This is called Lexical Scoping

React Native - Passing Strings or Variables via onPress

This probably doesn't just apply to React Native, but I do want to understand what's happening here.
Take the following 5 lines of code. Line 3 will cause the app not to render in Expo.
<Button onPress={this.filterOfficeTypePitt} title="Pitt" />
<Button onPress={this.filterOfficeTypeAtl} title="Atl" />
<Button onPress={this.filterOfficeType("pitt")} title="DOES NOT WORK" />
<Button onPress={(e) => this.filterOfficeType("pitt")} title="Pitt" />
<Button onPress={(e) => this.filterOfficeType("atl")} title="Atl" />
I somewhat understand what is going on in each line.
Line 1 and 2: Straight forward call to a method within the component.
Line 3: Trying to do the same thing and pass a value. This causes the application to crash. Reason is maximum depth exceeded.
Line 4 and 5: I think this is a function passing an anon function, and suddenly allowing me to add in a string value.
Once I used 4 and 5, I was able to use a single method and have it filter the list. When I was using 1 and 2, I had to have a unique method for each.
I just want to understand what it going on better and why exactly #3 will not work. I'm sure I need at least a better understanding of arrow functions.
Including the code for the helper function. It basically grabs data from an index array and pushes it into a FlatList component.
filterOfficeType(officetype){
let newarray = [];
this.state.fulldataSource.forEach((element, index) => {
if(element.office === officetype) {
newarray.push(element);
}
});
this.setState({
dataSource:newarray,
office:officetype
});
}
filterOfficeTypePitt = () => {
this.filterOfficeType("pitt");
}
filterOfficeTypeAtl = () => {
this.filterOfficeType("atl");
}
Line 3 is executing the function and trying to assign the result of that to onPress prop. It never gets there(Why? : explained below)
const Button = {
onPress: this.filterOfficeType("pitt")
}
Note: function is being called at the creation of Button object.
whereas the other lines are assigning the function to the onPress prop
const Button = {
onPress: this. filterOfficeTypePitt
}
or
const Button = {
onPress: (e) => {
this.filterOfficeType("pitt")
}
}
Note: the function is not being called a the Button object creation rather when something presses that button
And Why Line 3 causes the application to crash is because, it triggers a state change by calling setState . When setState is called, react will call render() again. But this render() will execute the function again and this will call setState and react will call render() again and so the maximum depth exceeded and crash
The main difference between arrow functions and normal functions is the this scope. In arrow functions this refers to its parent object, where as in normal function this refers to itself. You can read more about arrow functions

React: Can't call prop function when it is inside of another function?

I am trying to move over the Auth0 login function as described in their tutorial. I am able to get it work if I use it like this:
<button className="btn" onClick={this.props.route.auth.login.bind(this)}>test</button>
but if I set up the button to call a function I define above the render function like this:
login() {
this.props.route.auth.login.bind(this);
}
And change the onclick to be like this:
onClick={this.login()}
or
onClick={() => this.login()}
Then the auth login modal never opens and i receive no error. Also i added a console.log to login() and I can see it in the console, but the actual login modal never opens? It works in the first example, but not in the others.
The reason I am attempting to move this into a function is because I would like to pass the login function down into a child component later, and I was unable to do so and I believe this to be the root issue thats preventing me.
bind does not call your function:
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called. docs
Also, you are setting the value of onClick prop to the return value of login. If you want to pass a reference to the function, you have to do it without the ().
Your code should look like this:
<button className="btn" onClick={() => this.login()}>test</button> <!-- You need to keep a reference to `this`, hence the binding -->
Then:
login() {
this.props.route.auth.login();
}
I edited the answer so that it uses an arrow function. However, I prefer not doing that, since it makes the code a bit cumbersome, and rather bind all the functions in the constructor, like #patrick-w-mcmahon did.
Let's say you have a container MyContainer and this container renders a view called MyView. This view has a button that calls a method. MyContainer is going to pass to the MyView the method it needs to use.
MyContainer:
class MyContainer extends React.Component {
constructor(props) {
super(props);
this.myFunc = this.myFunc.bind(this);
}
myFunc() {
console.log("hello world");
}
render() {
return <MyView myClick={this.myFunc}/>;
}
}
MyView:
const MyView = ({ myClick }) => {
return <button onClick={myClick} />;
};
MyView.propTypes = {
myClick: PropTypes.func
};
export default MyView;
You pass the needed function from the container to the view and the view calls its parents function from props. the use of bind() sets this scope to the current scope so that when you call this from a different scope it is going to be the scope of the bind. When you are in the render you run a different scope so you must bind your functions to the current class scope so that this.myReallyCoolFunction() is pointing to the correct scope (your class scope).
.bind() will only bind the object and arguments but won't call (run) the function.
TL;DR Just use .call() instead of .bind()
instead of .bind() you can use
.call(this, args) which is basicaly the same as bind only that call will call (run) the function.
you could also use .apply(), which is basicaly the same as .call() but takes an array with the arguments instead of object like .call()
this way you can avoid arrow functions in you jsx render()
and kind of keeping the line of thought with react.
something like ->
login() {
this.props.route.auth.login.call(this);
}
When you call props function through return(JSX) React takes care of calling it once propagation ends.

ES6 method is not defined in create-react-app

I'm having difficulty understanding why create-react-app is unable to compile, telling me that error 'updateWord' is not defined no-undef. I'm fairly new to React with ES6. Normally I would write a component like const App = React.createClass({ }); but I've decided to try out some syntactical sugar instead.
I have parent component App and a child component Input:
class App extends Component {
constructor(props) {
super(props);
// all other code omitted...
}
handleInput(input) {
console.log(`handle: ${input}`);
updateWord(input);
// this also causes an error
// this.updateWord(input);
}
updateWord(input) {
console.log(`update ${input}`);
// why isn't this defined?
}
render() {
return (
<div className="App">
<Input onInput={this.handleInput} />
</div>
);
}
}
class Input extends Component {
handleInput(e) {
let input = e.target.value;
this.props.onInput(input);
}
render() {
return (
<form>
<input onChange={this.handleInput.bind(this)} />
</form>
);
}
}
I've tried changing to this.updateWord(input); instead of updateWord(input) but to no avail. I get:
"App.js:55 Uncaught TypeError: this.updateWord is not a function"
Normally when I implement a similar pattern (with ES5) to what I'm doing now I have no difficulties. For example:
const App = React.createClass({
getInitialState: function() {
// all other code omitted...
},
handleInput: function(input) {
console.log(`handle: ${input}`);
this.updateWord(input);
},
updateWord: function(input) {
console.log(`update ${input}`);
// In theory, this implementation would not cause any errors?
},
render: function() {
return (
<div className="App">
<Input onInput={this.handleInput} />
</div>
);
}
}
The problem is that when you do this.updateWord(...) in this.handleInput, this refers to the Input component. Let me illustrate the problem:
When you set the onInput handler, like so:
onInput={this.handleInput}
Here, since your Input component is calling the function, this refers to the Input component. This is due to the line:
this.props.onInput(input);
The Input component is calling handleInput. That means, in your handleInput function, the this context is Input. Consider the line:
this.updateWord(input);
in the handleInput function. Here you call this.updateWord, but since this is Input, it tries to call updateWord from Input which does not exist, thus throwing the error.
The solution is to explicitly bind the this context as the class (App component) instead of the Input component, using either Function.prototype.bind or an arrow function. From the documentation:
The bind() method creates a new function that, when called, has its this keyword set to the provided value
You can apply it like so:
onInput={this.handleInput.bind(this)}
Or more preferably in the constructor:
this.handleInput = this.handleInput.bind(this);
With the second option you may then do:
onInput={this.handleInput}
(This is more preferable as binding in the render method will create a new function every time on render, which isn't preferred).
The this context in the line above is the class. Since you bind this, the class will be correctly used as this context in the function and executing this.updateWord will invoke the method in the class.
An even more preferable way is to use arrow functions instead of regular ES6 methods. From the documentation:
An arrow function expression has a shorter syntax compared to function expressions and does not bind its own this, arguments, super, or new.target.
We can apply this by assigning handleInput to an arrow function instead of a regular method:
handleInput = (input) => {
console.log(`handle: ${input}`);
this.updateWord(input);
}
This will eliminate the use of bind completely and instead, use arrow functions. Since arrow functions don't bind their own this, it means this refers to the enclosing context. In the example above, a method is not used thus this refers to the class (the enclosing context). That will correctly call the class method updateWord, and consequently, you need not change the onInput event handler if you go this route.

Categories