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!
Related
I have the following component where within the useEffect, I am calling some data reading related
functions meant to happen once on load.
The problem is, some of the prop data are not available at this stage (still undefined) like the prodData and index.
They are only available when I get into the Nested components like <NestedComponent1 />.
I wish to move this logic into the nested components which will resolve this issue.
But I do not want to repeat these code inside the useEffect for each component. Instead looking to write these 7 lines once maybe in a function
and just call it with the 3 NestedComponents.
Issue is that there is a higher order function wrapping here plus all the values like prodData and index is coming from Redux store.
I can't just move all these logic inside useEffect into a normal JS function and instead need a functional component for this.
And if I make a functional component to perform these operations, I can't call it in the useEffect for each of the NestedComponents.
Cos this is not valid syntax.
React.useEffect(() => {
<NewlyCreatedComponentWithReadingFunctionality />
}, []);
Thus my query is, is there a way I could write a functional component which has the data reading logic inside its useEffect.
And then extend this functional component for each of the functional components so that the useEffect would just fire
when each of these NestedComponents are called?
Doesn't seem to be possible to do this thus looking for alternatives.
This is the existing component where some of these prop values are undefined at this stage.
const MyComponent = ({
prodData,
index,
country,
highOrder: {
AHigherOrderComponent,
},
}) => {
// this is the logic which I am looking to write once and be
// repeatable for all the NestedComponent{1,2,3}s below.
React.useEffect(() => {
const [, code] = country.split('-');
const sampleData = prodData[index].sampleData = sampleData;
const period = prodData[index].period = period;
const indication = prodData[index].indication = indication;
AHigherOrderComponent(someReadDataFunction(code, sampleData));
AHigherOrderComponent(someReadDataFunction(code, period);
AHigherOrderComponent(someReadDataFunction(code, indication);
}, []);
return (
{/* other logics not relevant */}
<div>
<div>
<NestedComponent1 />
<NestedComponent2 />
<NestedComponent3 />
</div>
</div>
);
};
export default connect( // redux connect
({
country,
prodData,
index,
}) => ({
country,
prodData,
index,
})
)(withHighOrder(MyComponent));
React components implement a pattern called composition. There are a few ways to share state between parts of your React application but whenever you have to remember some global state and offer some shared functionality, I would try and manage that logic inside a context provider.
I would try the following:
Wrap all your mentioned components inside a context provider component
Offer the someReadDataFunction as a callback function as part of the context
Within your provider, manage react state, e.g. functionHasBeenCalled that remembers if someReadDataFunction has been called already
Set functionHasBeenCalled to true inside someReadDataFunction
Call someReadDataFunction inside your components within a useEffect based on the props data
This way, your application globally remembers if the function has been executed already but you can still use the latest data within your useEffect within your components to call someReadDataFunction.
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
We have a crazy DOM hierarchy, and we've been passing JSX in props rather than embedding children. We want the base class to manage which documents of children are shown, and which children are docked or affixed to the top of their associated document's window.
List (crazy physics writes inline styles to base class wrappers)
Custom Form (passes rows of JSX to Base class)
Base Class (connects to list)
Custom Form (passes rows of JSX to base class)
Base class (connects to list)
The problem is that we're passing deeply nested JSX, and state management / accessing refs in the form is a nightmare.
I don't want to re-declare every row each time, because those rows have additional state attached to them in the Base Class, and the Base Class needs to know which rows actually changed. This is pretty easy if I don't redeclare the rows.
I don't know how to actually deal with rows of JSX in Custom Form.
Refs can only be appended in a subroutine of render(). What if CustomForm wants to measure a JSX element or write inline CSS? How could that JSX element exist in CustomForm.state, but also have a ref? I could cloneElement and keep a virtual DOM (with refs) inside of CustomForm, or depend on the base class to feed the deeply-nested, mounted ref back.
I believe it's bad practice to write component state from existing state. If CustomForm state changes, and I want to change which rows are passed to BaseClass, I have to throttle with shouldComponentUpdate, re-declare that stage document (maintaining row object references), then call setState on the overarching collection. this.state.stages.content[3].jsx is the only thing that changed, but I have to iterate through every row in every stage document in BaseClass when it sees that props.stages changed.
Is there some trick to dealing with collections of JSX? Am I doing something wrong? This all seems overly-complicated, and I would rather not worsen the problem by following some anti-pattern.
Custom Form:
render () {
return <BaseClass stages={this.stages()}/>
}
stages () {
if (!this._stages) this._stages = { title: this.title(), content: this.content() };
return this._stages;
}
title () {
return [{
canBeDocked: false,
jsx: (
<div>A title document row</div>
)
}
}
content () {
return [{
canBeDocked: false,
jsx: (
<div>Hello World</div>
)
}, {
canBeDocked: true,
jsx: (
<div>Yay</div>
)
}
}
What I usually do is just connect the lower level components via Redux. This helps with not passing the state in huge chunks from the top-most component.
A great video course by one of the React creators, Dan Abramov: Getting started with Redux
Absolutely agree with #t1gor. The answer for us was to use REDUX. It changed the entire game for us. Suddenly a button that is nested 10 levels deep (that is, inside a main view, header, header-container, left side grid, etc, etc, deeper and deeper) into purely custom components, has a chance to grab state whenever it needs.
Instead of...
Parent (pass down state) - owns state vars
Child (will pass down again) - parent has state vars
Grandchild (will pass down a third time) - grandparent has state vars
Great Grandchild (needs that state var) - great grandparent has state vars
You can do...
Parent (no passing) - reads global state vars
Child
Grandchild
Great Grandchild - also reads same global level state vars without being passed...
Usually the code looks something like this...
'use strict'
//Importation of Connection Tools & View
import { connect } from 'react-redux';
import AppView from './AppView';
//Mapping -----------------------------------
const mapStateToProps = state => {
return {
someStateVar: state.something.capturedInState,
};
}
const mapDispatchToProps = dispatch => {
return {
customFunctionsYouCreate: () => {
//do something!
//In your view component, access this by calling this.props.customFunctionsYouCreate
},
};
}
//Send Mappings to View...
export default connect(mapStateToProps, mapDispatchToProps)(AppView);
Long story short, you can keep all global app state level items in something called a store and whenever even the tiniest component needs something from app state, it can get it as the view is being built instead of passing.
The issue is having content as follows, and for some reason not being able to effectively persist the child instances that haven't changed (without re-writing the entire templateForChild).
constructor (props) {
super(props);
// --- can't include refs --->
// --- not subroutine of render --->
this.state = {
templateForChild: [
<SomeComponentInstance className='hello' />,
<AnotherComponentInstance className='world' />,
],
};
}
componentDidMount () {
this.setState({
templateForChild: [ <div className='sometimes' /> ],
}); // no refs for additional managing in this class
}
render () {
return ( <OtherManagerComponent content={this.state.templateForChild} /> );
}
I believe the answer could be to include a ref callback function, rather than a string, as mentioned by Dan Abramov, though I'm not yet sure if React does still throw a warning. This would ensure that both CustomForm and BaseClass are assigned the same ref instance (when props.ref callback is executed)
The answer is to probably use a key or createFragment. An unrelated article that addresses a re-mounting problem. Not sure if the fragment still includes the same instances, but the article does read that way. This is likely a purpose of key, as opposed to ref, which is for finding a DOM node (albeit findDOMNode(ref) if !(ref instanceof HTMLElement).
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
I am studying the principles of react.
According to some reviews, some people says is better to keep your component stateless, what does it mean?
But other people says, that if you need to update your component, then you should learn how to set your state to the proper state.
I saw this.props / this.setProps and this.state / this.setState and I am confuse with that.
Something I am trying to figure is, how can I update a component by itself and not from a parent component? should I use props or state in this case?
I already read some docs about props and state, what I don't have clear, is: when to use one or another ?
Props vs. state comes down to "who owns this data?"
If data is managed by one component, but another component needs access to that data, you'd pass the data from the one component to the other component via props.
If a component manages the data itself, it should use state and setState to manage it.
So the answer to
how can I update a component by itself and not from a parent component? should I use props or state in this case?
is to use state.
Props should be considered immutable and should never be changed via mutation. setProps is only useful on a top-level component and generally should not be used at all. If a component passes another component a property, and the first component wants the second to be able to change it, it should also pass it a function property that the second component can call to ask the first component to update its state. For example:
var ComponentA = React.createClass({
getInitialState: function() {
return { count: 0 };
},
render: function() {
return <Clicker count={this.state.count} incrementCount={this.increment} />;
},
increment: function() {
this.setState({count: this.state.count + 1});
}
});
// Notice that Clicker is stateless! It's only job is to
// (1) render its `count` prop, and (2) call its
// `incrementCount` prop when the button is clicked.
var Clicker = React.createClass({
render: function() {
// clicker knows nothing about *how* to update the count
// only that it got passed a function that will do it for it
return (
<div>
Count: {this.props.count}
<button onClick={this.props.incrementCount}>+1</button>
</div>
);
}
});
(Working example: https://jsbin.com/rakate/edit?html,js,output)
For and object-oriented programming analogy, think of a class/object: state would be the properties you put on the class; the class is free to update those as it sees fit. Props would be like arguments to methods; you should never mutate arguments passed to you.
Keeping a component "stateless" means that it doesn't have any state, and all its rendering is based on its props. Of course, there has to be state somewhere or else your app won't do anything! So this guideline is basically saying to keep as many components as possible stateless, and only manage the state in as few top-level components as possible.
Keeping components stateless makes them easier to understand, reuse, and test.
See A brief interlude: props vs state in the React docs for more information.
Use state when you know the variable value is going to affect the view. This is particularly critical in react, because whenever the state variable changes there is a rerender(though this is optimized with the virtual DOM, you should minimize it if you can), but not when a prop is changed (You can force this, but not really needed).
You can use props for holding all other variables, which you think can be passed into the component during the component creation.
If you have want to make a multi-select dropdown called MyDropdown for example
state = {
show: true,
selected:[],
suggestions:this.props.suggestionArr.filter((i)=>{
return this.state.suggestions.indexOf(i)<0;
})
}
props={
eventNamespace:'mydropdown',
prefix : 'm_',
suggestionArr:[],
onItemSelect:aCallbackFn
}
As you can see, the objects in the state variable are going to affect the view some way or the other.
The objects in the props are mostly objects which should remain the same throughout the component life cycle. So these objects can be callback functions, strings used to namespace events or other holders.
So if you do want to update the component by itself, you need to have to look into how componentWillRecieveProps ,componentWillUpdate, componentDidUpdate and componentShouldUpdate works. More or less, this depends on the requirement and you can use these lifecycle methods to ensure that the rendering is within the component and not in the parent.