I am using a callback function selectionHandlerList() to set my state in Parent component (AudiobookList) from Child (AudiobookDetail). When I run the below code, I get this error message: "TypeError: this.setState is not a function".
When commenting out this.setState({ selectedAudiobook: someArg }) in my selectionHandlerList() function, I receive the expected value from the Child component (from console.log(someArg), so the callback must be working properly.
This is the code of my Parent component:
class AudiobookList extends Component {
constructor(props) {
super(props);
this.selectionHandlerList.bind(this);
}
state = {
audiobooks: [],
selectedAudiobook: null
}
componentWillMount() {
axios.get('http://www.demo.demo/api/get/all')
.then(response => this.setState({
audiobooks: response.data }))
.catch(e => console.log(e));
}
selectionHandlerList(someArg) {
this.setState({ selectedAudiobook: someArg });
console.log(someArg);
}
renderAudiobookChoice(audioBookChoice, selectionHandlerList) {
if (audioBookChoice === 'all') {
return (
this.state.audiobooks.map(audiobook =>
<AudiobookDetail
key={audiobook.id}
audiobook={audiobook}
selectionHandlerList={selectionHandlerList}
/>)
);
}
if (audioBookChoice === 'prose') {
return this.state.audiobooks.map(audiobook => {
return audiobook.text_type === 1 ?
<AudiobookDetail
key={audiobook.id}
audiobook={audiobook}
selectionHandlerList={selectionHandlerList}
/>
:
null;
});
}
if (audioBookChoice === 'poetry') {
return this.state.audiobooks.map(audiobook => {
return audiobook.text_type === 2 ?
<AudiobookDetail
key={audiobook.id}
audiobook={audiobook}
selectionHandlerList={selectionHandlerList}
/>
:
null;
});
}
}
render() {
console.log('ABL: ' + this.state.selectedAudiobook);
const selectionHandlerList = this.selectionHandlerList;
const { audioBookChoice } = this.props;
return (
<ScrollView>
{this.renderAudiobookChoice(
audioBookChoice,
selectionHandlerList
)}
</ScrollView>
);
}
}
export default AudiobookList;
I am under the impresseion, that the issue must be with binding this properly. But I am not able of figuring it out from related questions.
Any help is much appreciated.
Calling .bind on a function returns a new version of that function. You have called .bind, but you also have to reassign the function to the "bound" version of itself.
"TypeError: this.setState is not a function" is almost always caused by this simple error. Something like this should correct the issue:
constructor(props) {
super(props);
this.selectionHandlerList = this.selectionHandlerList.bind(this);
}
This part of the React docs explains more about why this is the case https://reactjs.org/docs/components-and-props.html#es6-classes
Another way of structuring your code which avoids this problem is to use an arrow function class property. For example:
selectionHandlerList = () => {
// this.[everything] works here, because `this`
//is preserved from outer scope within an arrow function
};
This feature still a proposal, but it does have babel support. You would need to enable transform-class-properties or enable stage-2 in Babel to use this syntax.
Related
Could you please have a look on the following code. I need to get some value from another class. This works asynchronous, so I provided a function handleGameDeserialization.
The function gets the right value (as I tested with the alert), however the setState function has no impact. Could that be a "this-context" issue?
export default class GameplayScreen extends React.Component {
constructor(props) {
super(props);
this.fbGame = new FBGame();
global.currentScreenIndex = 'Gameplay';
this.state = {
currentGame: 'N/A'
}
// this.handleGameDeserialization = this.handleGameDeserialization.bind(this);
if (this.props.route.params != null) {
this.gameKey = this.props.route.params.gameKey;
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
}
handleGameDeserialization = (game) => {
// alert('yeah'+game); // here comes the expected output
this.setState({
currentGame: game
});
}
render() {
return (
<View>
<Text>{this.state.currentGame}</Text>
</View>
/*<Board game={this.state.game}/>*/
)
}
}
I call that function when the component GameplayScreen is navigated to. As you can see above, there is a class FBGame, which does the deserialization (read the game from firebase database)
export default class FBGame {
...
deserializeGame(key, handleGameDeserialization) {
var gameRef = firebase.database().ref("games/"+key).child("serialized");
gameRef.on("value", snapshot => {
//console.log('deserialized: ' + );
handleGameDeserialization(snapshot.val().serialized);
});
}
...
}
edit:
When I use componentDidMount like below, it works fine. But this seems to be an anti-pattern. I still don't understand, why it doesn't work, when callded in the constructor and how I am supposed to solve this.
componentDidMount() {
this.game = this.fbGame.deserializeGame(this.gameKey, this.handleGameDeserialization);
}
For things like subscriptions that will update the state and other side-effects, you should put the logic out in componentDidMount() which will fire immediately after the component is mounted and won’t give you any trouble if you update the state inside of it.
You can't but things that call this.setState in the constructor.
I have followed React JS Crash Course - 2019 on youtube and I was not able to find the answer to the following question.
Why the method context in parent component is not overwritten by the child bind call.
The codebase can be found here https://github.com/bradtraversy/react_crash_todo
But to simplify the question here is a small code snippet.
Parent
class App extends Component {
state = {
todos: []
}
// Toggle Complete
markComplete = (id) => {
this.setState({ todos: this.state.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
}
return todo;
}) });
}
render() {
return (
<TodoItem markComplete={this.markComplete} />
);
}
}
Child
export class TodoItem extends Component {
render() {
const { id, title } = this.props.todo;
return (
<input type="checkbox" onChange={this.props.markComplete.bind(this, id)} /> {' '}
)
}
}
From my understanding of bind https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
I would expect that when the mark markComplete is called that the "this" would be the child context but it stays the parent.
markComplete = (id) => {
this.setState({ todos: this.state.todos.map(todo => {
^^^^ why it is not child
if(todo.id === id) {
todo.completed = !todo.completed
}
return todo;
}) });
}
I do understand the public class fields syntax.
Is react doing some magic? https://github.com/facebook/react/blob/64e1921aab4f7ad7e2f345f392033ffe724e52e9/packages/events/EventPluginHub.js#L148
From answer #Li357 https://stackoverflow.com/a/51759791/4322180
That's because you are using an arrow function. Arrow Functions use a "Lexical this" which is not the "caller". Try to write your method like this, then the bind should work :
markComplete(id) {
this.setState({ todos: this.state.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
}
return todo;
}) });
}
Lexical this : http://es6-features.org/#Lexicalthis
[Edit]
You could also write your function as a "classical" function with function : markComplete = function(id) { /*...*/ }
Since MDN does not mention this in its documentation here is the official link to the ECMASript which explains this case.
https://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.bind
Pay attention to -> NOTE 2
If Target is an arrow function or a bound function then the thisArg passed to this method will not be used by subsequent calls to F.
Arrow functions automatically bind to the current context, which would be the parent in your case.
Change it to a normal function, and bind manually in the parent constructor
You don't need to use bind into your child component.
Why it is not child?
Because when you use arrow function, this context refers to the class itself.
If you want to refer this as a child context when you bind your function into your child component, don't use arrow function.
markComplete (id) {
this.setState({ todos: this.state.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
}
return todo;
}) });
}
I have a scenario where I'm trying to update a React/Redux state from a function that's placed on the Window. The function on the window is unable to access the function that's in the React component. Any idea how to bind that function in this kind of setup? This snippet just has a console log where the Redux call would go.
class MyComponent extends Component {
updateRedux = a => {
console.log(a)
}
componentDidMount() {
window.windowFunction = function(a) {
this.updateRedux(a)
}
}
render() {
return (
<Stuff />
)
}
}
this is not accessible inside your function, you need to bind it.
Try with:
class MyComponent extends Component {
updateRedux = a => {
console.log(a)
}
componentDidMount() {
window.windowFunction = function(a) {
this.updateRedux(a)
}.bind(this)
}
render() {
return (
<Stuff />
)
}
}
if you meant that you want to update Redux state with some action (this is the only way to update Redux state by design), then you need to make this action and its functions available to your Component with connect(mapStateToProps, mapDispatchToProps)(Component)
One of the comments above about converting the windowFunction to an arrow function resolved the issue. Thanks!
class MyComponent extends Component {
updateRedux = a => {
console.log(a)
}
componentDidMount() {
window.windowFunction = a => {
this.updateRedux(a)
}.bind(this)
}
render() {
return (
<Stuff />
)
}
}
What you could do is separate the concerns using a presenter and a connected
component, using react-redux. I am assuming you know of this library, comment
if you need more details.
// Simple "presenter", the getComponentData is used to get the data for the
// redux store.
class MyComponentPresenter extends Component {
// returns data for redux
getComponentData () {}
componentDidMount() {
this.props.updateRedux(this); // update Redux
}
render() {
return (
<Stuff />
)
}
}
// This component has the exact same interface, but comes with a updateRedux
// props which automatically dispatches an action
export const MyComponent = connect(null, {
updateRedux(componentInstance) {
return {
type: "updateRedux"
};
}
});
// in the reducer
//
function reducer (state, action) {
switch (action.type) {
case "updateRedux":
return ...
}
}
No more need for globally available function (which in your example is redefined for each instance of MyComponents which is probably not what you want).
I am calling an asynchronous function, and based off that response, the function should be returning a different React Native Element.
I have written simpler code that creates the same error.
async function doNothing(){ };
class RouterComponent extends Component {
render() {
return(
doNothing().then(()=> {
return(
<View></View>
);
})
The error I am getting is
A Valid React element or (null) must be returned. You may have returned undefined, an array, or some other invalid object.
I am returning a valid React element within the .then(statement). How would I change it so it returns the React Element.
If you want to perform async operation in the same file, then move async call to some lifecycle method like ComponentDidMount. Define a state object in constructor,
constructor(props) {
super(props);
this.state = {ComponentToRender: ''}
}
Trigger network call from you componentDidMount method,
componentDidMount() {
doNothing().then(()=> {
this.setState({
ComponentToRender: <ComponentToRenderBasedOnResponse>
})
})
}
Your render method should use this.state.ComponentToRender method to decide, which component to render.
render() {
if(this.state.ComponentToRender === 'A') {
return this.renderComponentA();
}
else {
return null;
}
}
You can decide, how you want to organize your render method.
In this case render function is not a asncy function. so render function would not wait for your doNothing finishes. So it returns null. You can probably try something like this.
constructor(props) {
super(props);
this.state = { renderA: false, renderB: false }
}
componentDidMount() {
doNothing().then((res)=> {
if (res.Something){
this.setState({ renderA: true })
}
});
}
renderA() {
return (<View> view for A </View>);
}
renderB(){
return (<View> view for B </View>);
}
render() {
this.state.renderA ? this.renderA() : null;
this.state.renderB ? this.renderB() : null;
}
I am trying to use axios to get data from the api (https://reqres.in/) and display in my react app. Before this I fetched the data from the API using fetch method in javascript. Now I have tried coding this from various resources. How should I do it. Is it the correct method?
My app.js file-
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
class App extends Component {
constructor(props) {
super(props);
this.successShow = this.successShow.bind(this);
this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(function (response) {
this.successShow(response);
})
.catch(function (error) {
this.errorShow(error);
});
}
successShow(response) {
this.member = <pre>{JSON.stringify(response.data, null, '\t')}</pre> ;
}
errorShow(error) {
this.member = <pre>{JSON.stringify(error.response.data, null, '\t')}</pre>;
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<div>{this.member}</div>
</div>
);
}
}
export default App;
It also gives the error - Unhandled Rejection (TypeError): Cannot read property 'errorShow' of undefined.
Changes:
1. You need to bind this with then and catch callback methods, use arrow functions.
2. You didn't define the initial state and using this.state.person.data it will throw error.
3. Storing the UI in state or global variable is not a good idea, ui part should be inside render method only.
Write it like this:
class App extends Component {
constructor(props) {
super(props);
this.state = {
person: {}
}
//this.successShow = this.successShow.bind(this);
//this.errorShow = this.errorShow.bind(this);
}
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.successShow(error);
});
}
successShow(response) {
this.setState({
person: response.data
});
}
render() {
return (
<div>
<h2>Welcome to React</h2>
<h3>{JSON.stringify(this.state.person.data)}</h3>
<pre>{JSON.stringify(this.state.person.data)}</pre>
<div>{this.member}</div>
</div>
);
}
}
When you call this.errorShow() inside of the function, this is not your component object, but context of function. You should use arrow functions instead, arrow functions do not create its own this so you can access your component this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch(error) => {
this.errorShow(error);
});
}
More info about arrow functions
Try this:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then((response) => {
this.successShow(response);
})
.catch((error) => {
this.errorShow(error);
});
}
Use arrow functions to remain the right scope of this
The problem is that the this in your then and catch callbacks doesn't refer to your class, but to the default (global) scope. You need to bind the right this. You actually already have the appropriate functions set up with this binding, so you can just use them directly:
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(this.successShow)
.catch(this.errorShow);
}
In general, you can also use => function syntax, which inherits the 'this' from the scope the function is declared in, rather than using the global scope. E.g.
componentDidMount() {
axios.get('https://reqres.in/api/products/3')
.then(success => this.successShow(success))
.catch(error => this.errorShow(error));
}
(note the => functions are completely unnecessary here of course).
You have an additional problem, which is the you need to store member in component state (this.state.member), not just as a field, and use the setState function to update it. Otherwise your component won't re-render when you update member.