React: Can only update a mounted or mounting component - javascript

This is my component:
var booksRef = new Firebase("https://bookshelf.firebaseio.com/books");
class BookShelf extends React.Component {
constructor(props){
super(props);
this.state = {books: [] };
var self = this;
booksRef.on("value", function(snapshot){
const newbooks = [];
var firebaseBooks = snapshot.val();
for(var bookId in firebaseBooks){
newbooks.push({key: bookId, book: firebaseBooks[bookId]});
}
var newState = self.state;
newState.books = newbooks;
self.setState(newState);
});
}
...
When I navigate to this component for the first time, there is no problem. But when I navigate to another component and then back again to this component, I get the following warning in the console:
Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the component.
I guess I need to do something before I dispose the component, but I'm not sure why.

To your question, all the code you have in the class constructor above is done before mounting its essentially: componentWillMount, so that logic is ONLY being run at that point.
Now thats fine and all, but the issue is complicated buy the asynchronous request you have with firebase. There used to be a method called isMounted that you could just run a check on but now that is deprecated, the best practices for your scenario are outlined here: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
Check out this blog post about another (but hackey) ways to solve the issue: http://jaketrent.com/post/set-state-in-callbacks-in-react/
scroll to the es6 part, the first bit isn't directly relevant.
Additional:
Check out this babel blog post: Specifically the section on Classes:
Not sure if you need it, but its good
https://babeljs.io/blog/2015/06/07/react-on-es6-plus

Related

React setState: Callback to function of child-component

how do I call a function of a child component in the parent component when using setState with a callback function?
Background information
Three components of a simple quiz app:
App.jsx
-- Questions.jsx
-- Results.jsx
To access the answers in the results component AND the questions component, the answers must be saved in the state of the app component.
To modify the state of the app component, each time the user answers a question, I need to call a function that is passed down from the app component to the questions component. After setting the State of the app component, the next question should be displayed. Hence, the function nextQuestion() in the questions component should be called. Normally, I would just write setState({results: results}, nextQuestion()) but since nextQuestion() is a function of the questions component and not of the app component, this does not work.
How can I solve this problem?
Thank you very much!
In the questions component:
checkAnswer = (answer) => {
if (answer !== this.state.solution) {
let s = this.state;
this.props.saveResult(s.question, s.solution[0], answer);
//[...]
}
};
newQuestion = () => {
//[...]
let question = [...this.state.question];
let solution = [...this.state.solution];
const task = taskGenerator.taskDirectory[taskId](); //generate new question + solution
question = task.question;
solution = task.solution;
this.setState({ question, solution });
};
In the app component:
saveResult = (question, solution, answer) => {
let results = cloneDeep(this.state.results)
results.question = question
results.solution = solution
results.answer = answer
this.setState({ results: results }, newQuestion()); //here is the problem; how do I call the newQuestion function of the child component?
};
how do I call a function of a child component in the parent component when using setState with a callback function?
In short, you don't :) Instead, you pass the state from the parent to the child. That's actually the root of your problem with:
but since nextQuestion() is a function of the questions component and not of the app component, this does not work.
In short, there are two "rules" to follow as a React dev for where your state needs to go in your app. To understand them, you have to think of your app as a "tree", with App at the top and all children components, grandchildren components, etc. below ...
The state must be at or above every component that needs to use the state
The state should be as low as possible in your app tree (while still following #1)
It seems you are trying to follow rule #2 and keep your state low (good) ... but your app is breaking that first rule: you have state (nextQuestion relies on this.state.question) which your App depends on. That state is "lower" (in your app tree) than it needs to be, and since it's not at the App-level, App can't use it.
What this means is that you need to move nextQuestion (and the state(s) powering it) into App, so that your code in App can access nextQuestion. Then, you can pass any data that Question needs as props, from App.
With this structure your state (and associated functions like nextQuestion) will live as high as it needs to live (ie. at the App-level), so that all logic that relies on it has access, and any "lower" components (like Question) will simply have that state passed "down the tree" via props.
class App extends Component {
...
setAnswers(answers, callback){
this.setState({results:answers},callback)
}
render(){
return <Questions setAnswers={this.setAnswers}/>
}
}
class Questions extends Component {
onSubmit(answers){
this.props.setAnswers(answers,this.nextQuestion)
}
}
another way would be to react to state change in Questions child component, probably the better way.
If I understand your problem correctly, you should be able to pass the function as props to the child component from the parent. I believe this post answers your question as well.
Call child method from parent
Cheers!

How do I update state when a window variable changes in react

I have a fairly simple class component that needs access to some data from a service written in vanilla JS. It's simply an interface for the Web MIDI API, that must get access to the MIDI ports, then triggers a callback. I'm importing a function setMidiPorts to the MIDI service then calling it and sending the list of ports on MIDI success. I then need to render those ports in a drop down, but can't seem to get them updated in the component. I've tried passing them down as props from the parent, I've tried importing them directly. Nothing seems to work. I'm pretty new to react so I'm probably doing something pretty wrong, can anyone help me by pointing out where I'm going wrong.
window.inputs = [];
export const setMidiPorts = (inputs) => {
window.inputs = inputs;
console.log(inputs);
};
export default class Preferences extends Component {
constructor(props) {
super(props);
this.state = {
midiInputs: window.inputs,
midiOutputs: [],
};
}
......
EDIT -
I'm looking at this question to see if I can update state from outside, but don't understand how it works properly.
Update component state from outside React (on server response)
Thanks for everyones advice, I managed to solve it by following what it says here.
https://brettdewoody.com/accessing-component-methods-and-state-from-outside-react/
Adding this inside the component render
ref={(Preferences) => {window.Preferences = Preferences;}}
Then I was able to define the setMidiPorts function inside the component and call it from anywhere with window.Preferences.setMidiPorts

React Best Practices: Having properties other than `state` in a component

I'm reviewing a React Component and it contains a state property as well as an allData property.
To follow best practice, shouldn't allData be a part of state?
constructor(props) {
super(props);
this.allData = [];
this.state = {
allDisplayedData: [],
allRowsCount: -1,
favData: [],
favRowsCount: -1,
};
this.searchAll = this.searchAll.bind(this);
this.handleCellClick = this.handleCellClick.bind(this);
}
If you want the component to re-render on changing it, then it needs to be in state, otherwise put it wherever. Any instance variables other than this.state aren't part of React's control, so they don't have the same ability to set using setState. This means that they don't re-render the component like the state does.
Essentially, it depends on what you do with it and how you want to work with it.
I tend to use this pattern for things like cancelTokens and intervalIds and other data I might need later, but don't need as part of the state because it's only needed in unmount or update but not in the render itself.
If it's needed in the render, you should have it in state or be prepared to deal with the component not rendering when it's updated.
If you want to make the array part of state, then yes, if no, then no.
Other than the two previous detailed answers, I found the following statement at https://reactjs.org/docs/state-and-lifecycle.html
While this.props is set up by React itself and this.state has a
special meaning, you are free to add additional fields to the class
manually if you need to store something that doesn’t participate in
the data flow (like a timer ID).

MapStateToProps to component state in an sync context

I know we can easily send the content of mapStateToProps in the component's state by doing so :
constructor(props){
super(props);
this.state = {
filteredApps: this.props.apps
}
}
In this usecase, this.state.filteredApps gets filled with what was mapped to props from Redux.
But what if this.props.apps is only filled properly after an async call? In an async context, this.props.apps will probably be an empty array for when it is initialized until the real data is fetched. Take this as an example :
class AppFilterer extends React.Component {
constructor(props) {
super(props);
this.state = {
filteredApps : this.props.apps
}
}
componentWillMount() {
this.props.getApps();
}
render(){ return <div> </div> }
}
const mapStateToProps = state => {
let { apps } = state.Admin;
return { apps };
};
export default connect(mapStateToProps, { getApps })(AppFilterer);
In this case, my Redux action (which is caught by an Saga) this.props.getApps(); is the call that fills my props full of apps and is called from the componentWillMount function. It is initialized as an empty array and then gets filled with apps once the call is complete.
I wish to filter these apps once they are fetched from the API so want to put them inside my component's state so that I don't mess with the Redux state. What is the best practice for updating the component's state in this case? In other words, is there any way to take the result of a saga that has been mapped to props and set it into the component's state or am I looking for a weird pattern and should filter it some other way?
First of all API calls go in componentDidMount not in componentWillMount which is also now deprecated. Please refer this guide:
https://reactjs.org/docs/react-component.html
Secondly, when you are using redux state and mapping it to props, you should not set that in your component local state, that’s not a good practice. You’ll receive updated props when your promise will return and you can always rely on props in that scenario.
But if you still want to do that you can override componentDidUpdate(prevProps) which will be called when your props or state is updated. Here is where you can set your state if you still want to do that.
Note for your filter thing
You can do filtering in componentDidUpdate method like:
this.setState({filteredApps. this.props.apps.filter(<your filter logic>)})

React: Can I pass component default state values via props?

For eample:
<Counter start="10">
...
export default class Counter extends React.Component {
constructor(props) {
super();
this.state = {
start: props.start
};
}
}
I googled this question and I got an idea that the answers I found are outdated
The questions on StackOVerflow
ReactJS: Why is passing the component initial state a prop an anti-pattern?
ReactJs: How to pass the initial state while rendering a component?
But I found this post in React blog: React v0.13.0 Beta 1
And in that post author does exactly what I want, as I understand getDefaultProps is deprecated now.
So the question is: Is passing state through props still an anti-pattern?
IMHO 'yes' because you give the impression that changing the prop value would change the behaviour of the component which won't happen. Your component would behave exactly the same when I change the start param.
Impressions are cool but needs are real. At times when I need this type of behaviour I simply name my prop accordingly like initialFoo or defaultBar.

Categories