React conditional rendering: (a)wait for children to be mounted - javascript

I have a React class component that renders null by default, and some children after an activate() function is called. Roughly like this:
class MyComponent extends React.Component {
...
activate() {
this.setState({showComponent: true})
}
...
render() {
if (this.state.showComponent) {
return <Child />
} else {
return null
}
}
}
I have an external JavaScript script in which I interact with the MyComponent (call activate function).
To keep it short, my problem is that after calling activate() in this external JS script and trying to access HTMLElements in the <Child /> component right after (with document.getElementById) I get null pointers as the <Child /> component is rendered asynchronously a bit later.
Is there a way to adapt the activate() function in MyComponent to 'wait' for all children in <Child /> to be mounted?
I already tried to exploit the async nature of setState and tried async activate() with await this.setState({showComponent: true}) but this did not change the rendering order.
So in short, is there a way to wait for children to be rendered after you perform a setState update. I guess this is a bit of a special case as it involves conditional rendering and MyComponent initially renders nothing.
Happy about any ideas!:)

How about emitting a custom event on window object inside componentDidMount of Child component and listening for this event in the external javascript and accessing the HTML element in the event handler.

we can pass a function to child component which we can call in child's componentDidMount hooks , so when the child gets mounted , it will trigger that function ... but keep in mind don't update any state variable of parent component in that passed function otherwise it will stuck in a loop (coz as the state variable changes , it will re- render all the child component and eventually component did mount will also be called ...)
class MyComponent extends React.Component {
...
activate() {
this.setState({showComponent: true})
}
callback = ()=>{
// function to be passed in child component
// don't update any state variable here ..
}
...
render() {
if (this.state.showComponent) {
return <Child callback={this.callback} />
} else {
return null
}
}
}
class child extends React.Component {
constructor (props){
super(props)
}
componentDidMount(){
this.props.callback() // this will be called when this child will be
mounted
}
}

Related

How to check if react component detached

have a question, how to check if react component detached from its parent components?
Let’s say I have a react component, that is subscribed to any async events: WebSocket, timer.
I just want to not waste computer resources on listener to this event, when react component no more in use, and deallocate react component as well.
Any though?
React provides various methods to keep track of a component's lifecycle. And in your case you need to track if a component has unmounted. So, there are 2 approach for this based on the type of component you are using:
Class Component
Use componentUnmount lifecycle method.
class YourComponent extends Component {
constructor(props) {
super(props);
}
componentWillUnmount() {
// this method is invoked immediately before a component
// is unmounted and destroyed. you can perform any necessary
// cleanup in this method, such as invalidating
// timers, canceling network requests,
// or cleaning up subscriptions
}
render() {
return (
<div>
{/* ...contents... */}
</div>
);
}
}
Functional Component
Leverage useEffect hook with cleanup.
const YourComponent = () => {
useEffect(() => {
// rest of code
return () => {
// similar to componentWillUnmount() method, this function
// would invoke at the time of component's unmount.
};
},[]);
return (
<div>
{/* ...contents... */}
</div>
);
}

Call React Component Method From Parent

Im new to react and i was wondering how i can call a child components method from the parent?
for example
var ChildClass = class Child {
howDoICallThis () {
console.log('Called!')
}
render () {
return (<Text> Child Class </Text>)
}
}
var ParentClass = class Parent {
// how can i call child.howDoICallThis() ?
render () {
return (<ChildClass> </ChildClass>)
}
}
Can someone explain clearly how i can do this?
This isn't really how things should be done in React. One very common way of approaching these situations is to define methods within a container component that handles business logic, then pass those methods down to presentational components as props.
class Child extends Component {
render () {
return <Text onClick={this.props.handleClick}>Child Class</Text>
}
}
class Parent extends Component {
handleClick = () => {
console.log('Called!')
}
render () {
return <Child handleClick={this.handleClick} />
}
}
Basically, you're wanting to pass things up, where React is designed to pass things down. You can still pass things up using refs and 'lifting state up' (see https://facebook.github.io/react/docs/lifting-state-up.html), but as far as methods go, you should really only ever pass them down. If you structure your components correctly, you shouldn't ever have to call a child's method from the parent.
(btw... make sure you are extending Component. you missed this in you sample code)

In React, when will the componentDidMount fire for a component that does not directly render markup?

Say for example I have the following three components:
ComponentA: which renders actual markup
ComponentB: which passes some props to ComponentA and renders it
ComponentC: which passes some props to ComponentB and renders it
I understand when the componentDidMount will fire for components that directly output markup (it will fire when the generated markup is appended to the DOM), but what about components that render non-markup components? When does their componentDidMount fire?
In the example above what would be the fire order of the componentDidMount of the 3 components?
The components will still call componentDidMount even if they don't explicitly render markup. In your example,
From the react docs:
componentWillMount() is invoked immediately before mounting occurs. It is called before render()...
Even if the component is going to render null, it still has to figure that out after going through the normal lifecycle and it is still 'mounted'. They are still components and behave in the same way. I like to think of every component rendering markup because it has a child that does (or it returns null which does not affect the lifecycle).
The ordering of componentDidMount will be A, B, C with the following:
ex: JSBIN
class A extends React.Component {
componentDidMount() {
console.log('A');
}
render() {
return <div>AHHH IT IS A</div>
}
}
class B extends React.Component {
componentDidMount() {
console.log('B');
}
render() {
return <A someProps={'SomeProps'}/>
}
}
class C extends React.Component {
componentDidMount() {
console.log('C');
}
render() {
return <B someProps={'SomeProps'}/>
}
}
ReactDOM.render(
<C />,
document.getElementById('root')
);

How to access child component functions via refs

The docs for React state that component functions can be accessed by a parent component via refs. See: https://facebook.github.io/react/tips/expose-component-functions.html
I am attempting to use this in my application but run into an "undefined is not a function" error when the child function is called. I'm wondering if this has anything to do with using the ES6 format for React classes because I don't see any other differences between my code and the docs.
I have a Dialog component that looks like the following pseudocode. The Dialog has a "Save" button that calls save(), which needs to call the save() function in the child Content component. The Content component collects information from child form fields and performs the save.
class MyDialog extends React.Component {
save() {
this.refs.content.save(); <-- save() is undefined
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
}
}
I could instead pass a prop (saveOnNextUpdate) down to Content and then execute save whenever it is true, but I would rather figure out how to get the method detailed in the React doc above to work.
Any ideas on how to get the doc approach to work or access the child component function in a different way?
Redux connect accepts an option parametre as the forth parameter. In this option parameter you can set the flag withRef to true. Then you can access functions to refs by using getWrappedInstance(). Like this:
class MyDialog extends React.Component {
save() {
this.refs.content.getWrappedInstance().save();
}
render() {
return (
<Dialog action={this.save.bind(this)}>
<Content ref="content"/>
</Dialog>);
}
}
class Content extends React.Component {
save() { ... }
}
function mapStateToProps(state) { ... }
module.exports = connect(mapStateToProps, null, null, { withRef: true })(Content);
Read more about it here: https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options
Worth reading this article about use of refs and consider if there's better approaches: https://facebook.github.io/react/docs/refs-and-the-dom.html#dont-overuse-refs
An alternative way to do this would be to use some other prop name (other than ref). I've found that this also works well if you're using a library like styled-components or emotion For example in a connected MyComponent:
<MyComponent
...
innerRef={(node) => { this.myRef = node; }}
/>
As it turns out, m90 was right -- this was a different issue entirely. I'm posting the solution in case someone runs into the same problem in the future.
My application is built with Redux, and the problem stems from using the react-redux connect function to connect a component to the store/global state. For some reason, exporting a component and connecting it to the store makes it impossible to access the functions inside of it. In order to get around this, I had to remove all use of global state from Content so that I could export it as a "dumb" component.
To be more clear, Content.js looked like this:
var connect = require('react-redux').connect;
class Content extends React.Component {
save() {
// Get values from child fields
// and save the content
// Use of this.props.stateObject
}
}
function mapStateToProps(state) {
const {
stateObject
} = state;
return {
stateObject
};
}
module.exports = connect(mapStateToProps)(Content);
Removing the use of global state (and therefore the use of connect and mapStateToProps allowed me to export the component using:
module.exports = Content;
Accessing this.refs.content.save() magically worked after doing this.

React + Alt: Lifecycle Gap

Edit: Check out the git repository for a minmal example: https://github.com/maximilianschmitt/blind-lifecycle
I have a component RequireUser that tries to ensure that the user is logged in and will otherwise not render its children. Its parent component, App, should know if a user is required and render a login form if needed.
The problem is, that the App component mounts AFTER the RequireUser component in a tree like this:
App
RequireUser
SomeOtherComponent
In RequireUser's componentDidMount I am triggering an action requireLogin that sets the UserStore's loginRequired variable to true.
This does not update the parent component (App) because it has not yet been mounted and can therefor not register changes to the store.
class RequireUser extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
if (!this.state.requireUser) {
UserActions.requireUser();
// using setTimeout will work:
// setTimeout(() => UserActions.requireUser());
}
}
componentWillUnmount() {
this.unlisten();
}
render() {
if (this.state.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
class App extends React.Component {
constructor() {
super();
this.state = alt.stores.UserStore.getState();
}
componentDidMount() {
this.unlisten = alt.stores.UserStore.listen(this.setState.bind(this));
}
componentWillUnmount() {
this.unlisten();
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser />
</div>
);
}
}
Output:
User required? false
I have required your user
If I use setTimeout in RequireUser, App receives the state changes and renders, but only after a flicker:
User required? true
I have required your user
I have the feeling what I am doing is an anti-pattern and I would be grateful for suggestions of a more elegant solution than flickering with setTimeout. Thanks!
My suggested answer is to add this to the App component:
componentDidMount() {
// setup listener for subsequent changes
alt.stores.UserStore.listen(this.onChange);
// grab the current state now that we're mounted
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
There is no way to avoid the double render. Your RequireUser component already performs two renders.
Initial render of RequireUser
componentDidMount() callback
an action is dispatched
UserStore receives the dispatched action and updates its state
change notification is emitted
RequireUser sets state based on the state change
Second render of RequireUser
But your codebase is still considered Flux, and indeed follows the pattern intended for React apps. Essentially, you have a loading state... a state where we don't actually know if we need to require a user or not. Depending on what UserActions.requireUser() does, this may or may not be desired.
You might consider a refactor
You can fix the double-render if you rewrite RequireUser as a view-only component. This means no listeners nor setting state internally. This component simply renders elements based on the props passed in. This is literally all your RequireUser component would be:
class RequireUser extends React.Component {
render() {
if (this.props.requireUser) {
return <div>I have required your user</div>;
}
return <div>I will require your user</div>;
}
}
You will then make your App component a controller-view. The listener is added here, and any changes to state are propagated downward by props. Now we can setup in the componentWillMount callback. This gives us the single render behavior.
class App extends React.Component {
(other lifecycle methods)
componentWillMount() {
if (!this.state.requireUser) {
UserActions.requireUser();
}
var userStoreState = alt.stores.UserStore.getState();
this.setState(userStoreState);
}
componentDidMount() {
(same as above)
}
render() {
return (
<div>
<div>User required? {this.state.requireUser + ''}</div>
<RequireUser requireUser={this.state.requireUser} />
</div>
);
}
}
Flux architecture and controller-views/views: https://facebook.github.io/flux/docs/overview.html#views-and-controller-views
Your components each only gets the states from your Store once - only during the construction of each components. This means that the states in your components will NOT be in sync with the states in the store
You need to set up a store listeners on your components upon mounting in order to retrieve a trigger from the store and the most up-to-date states. Use setState() to update the states inside the component so render() will be called again to render the up-to-date states
What about putting the store listener in the constructor? That worked for me.

Categories