The standard way for a React component to include child components is to create them in the render method and set to the children property. In my use case, children may be created before the parent is rendered and passed in through the parent's properties.
Events in the child are bubbled up to the parent as expected, but changes to the parent container do not re-render children created this way. The docs indicate that there is a difference between parent and owner relationships, the latter being established only for components created in render, so my guess is this relationship is missing and important for cascading re-renders.
Here is a simple example (fiddle)
/** #jsx React.DOM */
globalState = 'initial state';
var Child = React.createClass({
render: function() {
return React.DOM.input({
value:globalState
});
}
});
var Parent = React.createClass({
handleChange: function(e) {
globalState = e.target.value;
this.forceUpdate();
},
render: function() {
return React.DOM.div({
children: [
Child(),
React.DOM.br(),
this.props.passedChild
],
onChange: this.handleChange
});
}
});
c = Child();
p = Parent({passedChild:c});
React.renderComponent(p, document.body);
In this example, both child inputs can be edited, the onChange event is caught by the parent and a forceUpdate() is called. This does cascade down to the first child which is created in the render method, but not to the second child which is created elsewhere and passed in.
How can I update the owner of a child component so it will update as desired?
My backup plan is to wire up an event listener on child components. In my application, there is quite a bit of logic around when components are created that would make it impractical to do everything in render().
This is slightly complex to explain here, but your code will now work on the master build of React (post 0.10.0).
I'm not too sure what you're trying to accomplish here, but if you change your this.props.passedChild to this.props.passedChild() and c = Child(); to c = Child;, it'll work. Call it this.props.passedChildClass or something. You can also try this.props.passedChildFn with c = function() { return Child(); }. Whatever suits your need.
Don't create an instance of a component and pass it around (it won't be a big problem anymore soon; the return value of Child() won't be an instance anymore). In general it's bad practice because this encourages mutation. Create your children on the fly as you need them and let React handle the rest.
In my application, there is quite a bit of logic around when components are created that would make it impractical to do everything in render().
Break them into helper functions! You really don't have to stuff everything into a single render.
Also, global state is a bad idea. The fact that your child updates correctly by reading from globalState is very fragile. You must have gotten a warning in your console (providing you're using the dev build) to add an onChange handler. Go read it =).
Related
I am working on form layout for multiple children. Each child will have its own data about their name, surname,phone and email.
My data structure for each child looks like this.
[id]:{
name:"",
surname:"",
phone:""
email:""
}
I am changing between child using unregister with keepValue:true. Where selectedChildId is the active child with dark gray bg.
useEffect(() => {
unregister(selectedChildId, { keepValue: true });
}, [selectedChildId]);
This way I can jump between children without needing to save their state anywhere else.
Everything works fine, but if I delete a child, the react hook form still keeps the child object with empty string values and I dont want that. Is there any way of removing a specific value (object) from react hook form state?
Currently I have a two component set up, where the parent renders some data and handles retrieval and the child is a filter. This filter allows the user to filter by status or keyword. Nothing fancy.
Now this is a paginated system. After the parent makes an initial request for data, they're given the next page ID to request if they want more. But if the filter is updated, this next page ID needs to be wiped out, as it's no good.
So what I do is pass a function from the parent to the child called updateFilter(). If the filter component has an update in state, it calls up to the parent and runs updateFilter(). One of the values updated is included in a useEffect() dependency array, so the parent then requests the new data with the new filters. Easy.
The problem is in setting up the child's useEffect(). Eslint tells me I need to add props.updateFilter to the dependency array, and while I can just ignore this, I feel that it would be wrong to do so. But the parent has a fair bit of state that will update, and when it does, it passes a new copy of updateFilter() down into the child which causes it to incorrectly trigger.
How do I go about fixing this? Can I tell the child to only use a static version of this function somehow? Or do I just exclude props.updateFilter from the dependency array? Below is a rough psuedo code of my components.
Parent {
const [stateVal, setStateVal] = useState(...);
function updateFilter(filterStatus) {
...
setStateVal(filterStatus);
}
useEffect(() => ..., [stateVal]);
return <Child updateFilter={updateFilter} />
}
Child {
const [filterStatus, setStatus] = useState(...);
useEffect(() => {
props.updateFilter(filterStatus);
}, [filterStatus] // Adding `props` here is what I think I should do, but that causes the issue. Apparently the `props` val changes every time Parent's state changes
return ( ... );
}
How do I go about fixing this? Can I tell the child to only use a
static version of this function somehow?
Yep!
So, if props.updateFilter is included in the dependency array, you have issues. You call that function and it causes your parent component to re-render. And guess what? The parent creates a new updateFilter function (it does the same thing, but it makes a new one, the reference to the function is a new value which is all React checks). This causes the child to re-render, which causes your useEffect to run because its dependency is a new function. That's bad!
So... add useCallback
function updateFilter = useCallback((filterStatus) => {
...
setStateVal(filterStatus);
}, []);
useCallback creates the function one time, and only makes a new reference if its dependencies change (as it should). It has a little bit more overhead, but if I'm ever unsure I use it.
Also, bonus, after dealing with these issues, I use the setState(previousValue => previousValue + 1) form much more than setState(previousValue + 1) as it has many benefits. previousValue doesn't have to be in the dependency array and multiple setStates can be stacked in one render cycle (instead of using the initial value).
I render a React component SettingsTab within a wrapper called TeamView. Its API looks something like
class TeamView {
constructor() {
this.el = document.createElement('div');
}
render() {
ReactDOM.render(<SettingsTab/>, this.el);
return this;
}
remove() {
this.el.remove();
}
}
used something like
// to present the team view
const teamView = new TeamView();
document.body.appendChild(teamView.render().el);
// to remove the team view
teamView.remove();
And what I'm wondering is, should TeamView#remove call ReactDOM. unmountComponentAtNode(this.el) before calling this.el.remove()?
The examples I can find around the web make it seem like unmountComponentAtNode only needs to be called if the container is going to remain in the DOM; and the new portals example just removes the container, without calling unmountComponentAtNode.
But, I'm not sure if that's special because it's using a portal, and this post makes it kind of seem like it's always good practice to call unmountComponentAtNode.
Yes, it is important to call unmountComponentAtNode() because if you don't do this, none of the components below in the tree will know they have been unmounted.
User-defined components often do something in componentDidMount that creates a reference to the tree from the global environment. For example, you may add a window event handler (which isn't managed by React), a Redux store subscription, a setInterval call, etc. All of this is fine and normal as long as these bindings are removed in componentWillUnmount.
However, if you just remove the root from the DOM but never call unmountComponentAtNode, React will have no idea the components in that tree need to be unmounted. Since their componentWillUnmount never fires, those subscriptions stay, and prevent the whole tree from getting garbage collected.
So for all practical purposes you should always unmount the root if you're going to remove that container node. Otherwise you'll most likely get a memory leakāif not now, then later when some of your components (potentially deep in the tree, maybe even from third-party libraries) add subscriptions in their componentDidMount.
Even though you called this.el.remove(), you should still call the unmountComponentAtNode(this.el) because unmountComponentAtNode will clean up its event handlers and state, but the remove method will not.
For example, Eventhough you have clicked to remove the div, you can still call it's click event handlers:
var tap = document.querySelector('.tap');
var other = document.querySelector('.other');
tap.addEventListener('click', function(e) {
console.log(tap.getAttribute('data-name') + ' has been clicked');
tap.remove();
});
other.addEventListener('click', function(e) {
tap.click();
});
<div class="tap" data-name="tap">First Click me to remove me</div>
<div class="other">Then Click me </div>
I asked this question in the #react-internals Discord channel and received the following response:
So, this tallies with what #jiangangxiong says above: as long as we
don't keep our own references to component DOM elements
nor attach event handlers outside of React
and only need to support modern browsers
we should only need to remove the container to have the component's event handlers and state garbage collected, no need to call unmountComponentAtNode.
I have been struggling with this issue for quite some time and have failed to find any answers.
I use react-meteor-data to manage my data with react in my meteor application. It is working fine when dealing with data for mongo but I can't make it reactive with props.
Here in App.js, I call my container which I want to be reactive and rerender when the state of App change.
<MyContainer someState={this.state.MyState} />
In MyContainer.js I have a createContainer from react-meteor-data
export default createContainer(params => {
Meteor.subscribe('someCollection');
return {
someCollection: SomeCollection.find({}).fetch(),
stateFromParent: params.someState
};
}, MyContainer);
This worked fine when rendering the component for the first time, MyContainer correctly get MyState.
The thing is, when the MyState from App change, I can see in Chrome Dev React tool that it is indeed updated for the createContainer( ReactMeteorDataComponent has a prop with the right updated state) but the createContainer function is not run, thus the props do not update for MyContainer.
So the props are updated from ReactMeteorDataComponent but not for MyContainer who keeps indefinitely the data. It's like createContainer doesn't consider the update of its prop has a change and thus doesn't run its function.
I really think I'm missing something since that seems pretty basic stuff, thank you for your help.
The OP did not mention how the state was changed, so the original example is incomplete. Therefore, I will try to explain the gist of how the container creation works, in hope that understanding it will be useful.
How does it work?
It uses meteor's Tracker to auto-update the wrapped component when its computation is invalidated (i.e, when one of the reactive data sources, such as reactive variables, subscription handles or fetched MiniMongo cursors, has a new value). To learn more about Tracker, consult the Tracker manual. This is an in-depth resource, and is not necessary to understand how the basics work.
It does so in a way that is different from the way you normally approach reactivity tracking in Meteor, since it also needs to re-run the computation whenever the container's props are changed.
The source code is not very long or complex and can be found on GitHub (currently here).
Tracker.autorun((c) => {
if (c.firstRun) {
//...
data = component.getMeteorData();
} else {
// Stop this computation instead of using the re-run.
// We use a brand-new autorun for each call to getMeteorData
// to capture dependencies on any reactive data sources that
// are accessed. The reason we can't use a single autorun
// for the lifetime of the component is that Tracker only
// re-runs autoruns at flush time, while we need to be able to
// re-call getMeteorData synchronously whenever we want, e.g.
// from componentWillUpdate.
c.stop();
// Calling forceUpdate() triggers componentWillUpdate which
// recalculates getMeteorData() and re-renders the component.
component.forceUpdate();
}
})
Whenever the computation is invalidated (and therefore rerun), it stops the computation and forces a re-render of the container, which will re-create a new computation and have the updated data.
The high-level container functionality is here (some parts were removed for brevity):
export const ReactMeteorData = {
componentWillMount() {
this.data = {};
this._meteorDataManager = new MeteorDataManager(this); // (1)
const newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
},
componentWillUpdate(nextProps, nextState) {
// backup current state and props, assign next ones to components
let newData = this._meteorDataManager.calculateData(); // (2)
this._meteorDataManager.updateData(newData); // (3)
// restore backed up data
},
componentWillUnmount() {
this._meteorDataManager.dispose(); // (4)
},
};
The main points are:
- Before being mounted, a new data manager is created (1). It is in charge of running the computation and populating this.data according to data changes.
- At first and whenever the component should update, the computation is run (2) and the data is updated (3). The update happens whenever the component receives new state or props (in this type of container, it should only be props), and, as we saw earlier, also when the Tracker computation is invalidated, due to the call to component.forceUpdate().
The wrapped component receives the parent's props, as well as the Tracker computation's data as props:
return <WrappedComponent {...this.props} {...this.data} />;
Any more points as to how it should be used?
The react-meteor-data has a short section in the meteor guide.
Generally, the simple example in the guide (as well as the OP's example) should work just fine, as long as the state is set appropriately, using setState() (see the "how does it work?" section above).
Also, there is no need to re-map the container state to props sent to the child, as they are passed along (unless there is a very good reason for doing so).
Do consider the point in the preventing re-renders section if you encounter any performance issues.
From the guide:
export default ListPageContainer = withTracker(({ id }) => {
const handle = Meteor.subscribe('todos.inList', id);
const loading = !handle.ready();
const list = Lists.findOne(id);
const listExists = !loading && !!list;
return {
loading,
list,
listExists,
todos: listExists ? list.todos().fetch() : [],
};
})(ListPage);
in this example, note that the container expects an id prop, and it will also be made available to the wrapped component, as well as loading, list, etc (which come from the container's computation in the example).
I want to create a component with only one event and place it inside an ancestor of any given level (child or grandchild or deeper), and when the event triggers the parent of this ancestor will act (eg. alert("I am "+parent.name+" and one of my ansestors did something")).
Imagin of an old peoples home whos residents refuse to die and have a million ansestors, and the residents inform eachother everytime one of their ansestors has a birthday.
What would be the most elgant way to do this since i understand that the observer pattern is fround upon in react? and could it be done without passing the parent object manually when creating a child?
One way I know how to do this is to pass a function down the "family tree" that does something in the parent
class Parent extends React.Component {
constructor(props) {
super(props)
this.doSomethingInParent = this.doSomethingInParent.bind(this);
}
doSomethingInParent() {
console.log('running in parent');
}
render() {
return <Child parentsFunction={this.doSomethingInParent} />
}
}
Now if you ran parentsFunction within the child component you would see the console.log inside doSomethingInParent. If you wanted to go deeper you would have to continually pass that function down through props which can get tedious in more complex applications. Which is why redux/flux is popular because it lets you manage state/dispatch actions and avoid long chains of callback functions.
Solution was found using getChildContext , childContextTypes after discussion in :
check if a component is a child or ancestor of another component ReactJS
see example (for 2 solutions) : https://jsfiddle.net/shaibam/d0q5mwc4/33/.
contextTypes: {
notifyChange: React.PropTypes.func
}.....