Manipulating DOM in componentDidMount() without setTimeout - javascript

I want to manipulate the DOM in ReactJS in the componentDidMount() method. My problem is that at this time the DOM isn't fully rendered somehow and I need a setTimeout function, which I would rather omit.
When I console.log the scrollHeight of the rendered element in componentDidMount() it gives me a different number as when I wait for let's say 100 milliseconds.
What I want to achieve is to scroll down to the end of an element which is described here How to scroll to bottom in react?
The component is a modal-window which renders {this.props.children} of another component. The modal-window is rendered into the DOM with visibility: hidden and opacity: 0 and it has the height of the window, when it first appears on the page. By clicking on a button it shows up and still has the height of the window until I wait some milliseconds.
I guess, I do something wrong here when setTimeout is needed, but I didn't found out what.
I also tried to change the DOM in the componentDidUpdate() method with the same results.
I wrote this code in the modal-window component:
componentDidMount() {
console.log(document.querySelector('.myModal').scrollHeight);
setTimeout(function() {
console.log(document.querySelector('.myModal').scrollHeight);
}, 100);
}
First console.log gives me for example 497 and the second one something like 952.
Update
I have a modal-window component which renders a child like this for example for my inbox-thread:
<Modal>
<InboxThread />
</Modal>
The problem was, that I needed to wait until the modal-window component rendered its children like this in the Modal.js:
render() {
return (
<React.Fragment>
{this.props.children}
</React.Fragment>
);
}
So my solution in the end was to hand over a method in the props from the parent component where I call the modal to check if componentDidUpdate() in Modal.js.
My code looks now like this in the parent component:
...
export default class InboxThreadList extends React.Component {
constructor(props) {
super(props);
this.scrollToModalBottom = this.scrollToModalBottom.bind(this);
}
render() {
return (
<React.Fragment>
...
<Modal onRender={this.scrollToModalBottom}>
<InboxThread/>
</Modal>
</React.Fragment>
)
}
scrollToModalBottom() {
const myModalObject = document.querySelector('.myModal');
myModalObject.scrollTop = myModalObject.scrollHeight;
}
}
And in the Modal.js:
...
export default class Modal extends React.Component {
...
componentDidUpdate() {
if ('onRender' in this.props) {
this.props.onRender();
}
}
render() {
return (
<div className={'myModal'}>
{this.props.children}
</div>
);
}
I know! I still should work with refs instead of document.querySelector and I will do as described here React - Passing ref from dumb component(child) to smart component(parent).

If you use a ref - as long as the element is always rendered in render() - it is guaranteed to resolve before componentDidMount runs:
componentDidMount() {
// can use any refs here
}
componentDidUpdate() {
// can use any refs here
}
render() {
// as long as those refs were rendered!
return <div ref={/* ... */} />;
}
componentDidMount called BEFORE ref callback
So in your case, you might code it a bit like this:
componentDidMount() {
console.log(this.mymodal.scrollHeight)
}
render() {
return <div className="mymodal" ref={ref => this.mymodal = ref} />
}

Related

How can I trigger a method in a subcomponent when parent changes props?

I have a React web application that has three components, a parent and two child subcomponents. I've left out the JavaScript related to <HeaderComponent /> because it isn't relevant to this question.
class App extends Component {
token = null;
constructor(props) {
super(props);
this.state = {
AllInvoices: [],
CurrentInvoice: null
};
this.setCurrentInvoice = this.setCurrentInvoice.bind(this);
}
setCurrentInvoice = (sharedValue) => {
this.setState({
CurrentInvoice: sharedValue
});
}
componentDidMount()
{
fetch("http://api.app.local/api/getALLinvoices", {
"method": "GET"
})
.then(resp => {
this.setState({
AllInvoices: resp.Result
CurrentInvoice: resp.Result[0]
});
});
}
componentDidUpdate()
{
// this DOES trigger
console.log("App componentDidUpdate triggered");
}
render() {
return (
<>
<HeaderComponent AllInvoices={this.state.AllInvoices} setCurrentInvoice={this.setCurrentInvoice} />
<MainFormComponent CurrentInvoice={this.state.CurrentInvoice} />
</>
)
}
}
class MainFormComponent extends Component {
token = null;
constructor(props) {
super(props);
this.state = {
Field1Value: "",
Field2Value: "",
Field3Value: "",
// ...
Field12Value: ""
};
}
componentDidUpdate()
{
//> For some reason this does NOT trigger when CurrentInvoice is updated
console.log("MainFormComponent componentDidUpdate triggered");
}
getInvoiceDetailsAndUpdateForm = () =>
{
fetch("http://api.app.local/api/getinvoicedetails", {
"method": "POST",
"body": {
"invoice_id": this.props.CurrentInvoice.Id
}
})
.then(resp => {
/*
* Run various business logic and update 12+ form fields with AJAX response
*
*/
});
}
render() {
return (
<>
<TextField Value={this.state.Field1Value} />
<TextField Value={this.state.Field2Value} />
<TextField Value={this.state.Field2Value} />
{ /* ... */ }
<TextField Value={this.state.Field12Value} />
</>
)
}
}
class HeaderComponent extends Component {
token = null;
constructor(props) {
super(props);
this.state = {
MiscVariable: ""
};
}
setUpdateCurrentInvoice = (row) =>
{
this.props.setCurrentInvoice(row);
}
render() {
return (
<>
{this.props.AllInvoices.map((row, i) => (
<Button onClick={() => { this.setUpdateCurrentInvoice(row); }} />
))}
</>
)
}
}
Within the App component an AJAX call returns all invoices, and sets an initial value for this.state.CurrentInvoice. Afterwards, buttons in <HeaderComponent /> or <MainFormComponent /> can change CurrentInvoice.
When CurrentInvoice within App is changed, I want to trigger getInvoiceDetailsAndUpdateForm within the <MainFormComponent /> so that I can perform another AJAX call and run other business logic within that component.
What I'm finding is that within <MainFormComponent /> I can't seem to be able to "subscribe" to props.CurrentInvoice value changes. Within the render() method in that Component I see the change. But, that hasn't helped me because I want to trigger getInvoiceDetailsAndUpdateForm.
Does anyone have any ideas on how I can achieve the outcome I want?
Update:
The original code I published did not contain code related to the HeaderComponent. I've now included that to help draw the whole picture.
Basically what's happening is that the user clicks on a button in HeaderComponent which then calls setCurrentInvoice. When this happens componentDidUpdate is triggered, but only in the parent component. I am trying to figure out why componentDidUpdate within MainFormComponent is NOT also firing.
What you are looking for is componentDidUpdate. You can compare prevProps and currentProps and perform necessary actions
you would want to implement this lifecycle method in your MainFormComponent
Also, here's this example which is emulating the behavior you are having.
if you see App is the component storing the state and the handler,
and ChildComponent has the componentDidUpdate in it
now when you click either in App or AnotherChildComponent
it fires the state update, since ChildComponent has ComponentDidUpdate and it is receiving the counter prop you can clearly see it printing the previous and current values.
One thing to note - componentDidUpdate does not get immediately fired but when the state/prop changes so you may not see it fired on the first render but as soon as your api returns something and updates the state you will see it's getting fired for the 1st time in your MainForm component
Also, if you can post some proof of it not firing like screenshot of console.log would be helpful.
I don't see a point of componentDidUpdate not firing.

React render JSX from method

In order to keep the render method of my component shorter and also avoid creating additional components I was hoping to render the HTML from a class method depending on the state like so:
class ExampleComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
step: 'step1'
}
}
handleChangeStep(step) {
this.setState({ step: step })
}
step1 () {
return (
<>
<h2>Step 1</h2>
<div onClick={this.handleChangeStep('step2')}>Next Step</div>
</>
)
}
step2 () {
return (
<>
<h2>Step 2</h2>
<div onClick={this.handleChangeStep('step1')}>Previous Step</div>
</>
)
}
render () {
return this.state.step === 'step2' ? this.step2() : this.step1();
}
}
However this gives the error: Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state..
Seems you can't do this and instead need to do something like (from: https://reactjs.org/docs/conditional-rendering.html)
return this.state.step === 'step2' ? <Step1 /> : <Step2 />
However I don't understand why the former example isn't allowed as those two methods just return JSX so in theory should allow you to return one or the other depending on the state.
The problem has nothing to do with what you're describing. You're trying to update state during a render:
onClick={this.handleChangeStep('step2')}
When this.handleChangeStep('step2') is invoked, state is updated. Updating state triggers a re-render. Which in this case would then update state again, and again, indefinitely.
I suspect you meant to pass a function reference to onClick, not invoke a function:
onClick={() => this.handleChangeStep('step2')}

React: Do children always rerender when the parent component rerenders?

It is to my knowledge that if a parent component rerenders, then all its children will rerender UNLESS they implement shouldComponentUpdate(). I made an example where this doesn't seem to be the true.
I have 3 components: <DynamicParent/>, <StaticParent/> and <Child/>. The <Parent/> components are responsible for rendering the <Child/> but do so in different ways.
<StaticParent/>'s render function statically declares the <Child/> before runtime, like so:
<StaticParent>
<Child />
</StaticParent>
While the <DynamicParent/> handles receiving and rendering the <Child/> dynamically at runtime, like so:
<DynamicParent>
{ this.props.children }
</DynamicParent>
Both <DynamicParent/> and <StaticParent/> have onClick listeners to change their state and rerender when clicked. I noticed that when clicking <StaticParent/> both it and the <Child/> are rerendered. But when I click <DynamicParent/>, then only the parent and NOT <Child/> are rerendered.
<Child/> is a functional component without shouldComponentUpdate() so I don't understand why it doesn't rerender. Can someone explain why this is to be the case? I can't find anything in the docs related to this use case.
I'll post your actual code for context:
class Application extends React.Component {
render() {
return (
<div>
{/*
Clicking this component only logs
the parents render function
*/}
<DynamicParent>
<Child />
</DynamicParent>
{/*
Clicking this component logs both the
parents and child render functions
*/}
<StaticParent />
</div>
);
}
}
class DynamicParent extends React.Component {
state = { x: false };
render() {
console.log("DynamicParent");
return (
<div onClick={() => this.setState({ x: !this.state.x })}>
{this.props.children}
</div>
);
}
}
class StaticParent extends React.Component {
state = { x: false };
render() {
console.log("StaticParent");
return (
<div onClick={() => this.setState({ x: !this.state.x })}>
<Child />
</div>
);
}
}
function Child(props) {
console.log("child");
return <div>Child Text</div>;
}
When you write this code in your Application render:
<StaticParent />
What's rendered is this:
<div onClick={() => this.setState({ x: !this.state.x })}>
<Child />
</div>
And in reality, what happens (roughly) is this:
function StaticParent(props) {
return React.createElement(
"div",
{ onClick: () => this.setState({ x: !this.state.x }) },
React.createElement(Child, null)
);
}
React.createElement(StaticParent, null);
When you render your DynamicParent like this:
<DynamicParent>
<Child />
</DynamicParent>
This is what actually happens (again, roughly speaking)
function DynamicParent(props) {
return React.createElement(
"div",
{
onClick: () => this.setState({ x: !this.state.x }),
children: props.children
}
);
}
React.createElement(
DynamicParent,
{ children: React.createElement(Child, null) },
);
And this is the Child in both cases:
function Child(props) {
return React.createElement("div", props, "Child Text");
}
What does this mean? Well, in your StaticParent component you're calling React.createElement(Child, null) every time the render method of StaticParent is called. In the DynamicParent case, the Child gets created once and passed as a prop. And since React.createElement is a pure function, then it's probably memoized somewhere for performance.
What would make Child's render run again in the DynamicParent case is a change in Child's props. If the parent's state was used as a prop to the Child, for example, that would trigger a re-render in both cases.
I really hope Dan Abramov doesn't show up on the comments to trash this answer, it was a pain to write (but entertaining)
It's mainly cause of you have 2 different "children".
this.props.children
<Child/>
They're not the same thing, first one is a prop passed down from Application -> DynamicParent, while the second one is a Component rendered in StaticParent, they have separate rendering/life cycles.
Your included example
class Application extends React.Component {
render() {
return (
<div>
{/*
Clicking this component only logs
the parents render function
*/}
<DynamicParent>
<Child />
</DynamicParent>
{/*
Clicking this component logs both the
parents and child render functions
*/}
<StaticParent />
</div>
);
}
}
Is literally the same as:
class Application extends React.Component {
render() {
// If you want <Child/> to re-render here
// you need to `setState` for this Application component.
const childEl = <Child />;
return (
<div>
{/*
Clicking this component only logs
the parents render function
*/}
<DynamicParent>
{childEl}
</DynamicParent>
{/*
Clicking this component logs both the
parents and child render functions
*/}
<StaticParent />
</div>
);
}
}
As a comment to SrThompsons answer: "What would make Child's render run again in the DynamicParent case is a change in Child's props. If the parent's state was used as a prop to the Child, for example, that would trigger a re-render in both cases."
So props or not props passed to child component however it may look will cause a rerender if parent rerenders (so use React.memo for a child without props :) )
"Whether you’re implementing your component as a class component that extends React.Component, or as a functional component, the render function is called again whenever the parent container renders again." Please read here for more info, because great article. https://medium.com/free-code-camp/yeah-hooks-are-good-but-have-you-tried-faster-react-components-e698a8db468c"
It will only re-render components that have had a change. If nothing on the child component has changed, it will not be re-rendered.

Race condition with load event in Javascript

So this is more or less the code
Sorry for the syntax, I typed it from my phone
export default class Main extends React.Component {
componentDidMount() {
axios.get('/user?ID=12345')
.then(function (response) {
if (response){
document.addEventListener('load', () => {/* remove spinner using jquery */});
} else { /* redirect to somewhere else */}
})
}
render() {
return (
<SomeComponent />
);
}
}
I used addEventListener with React because I couldn't find any other way to bind the removal of the loading spinner to the load event.
The issue is that there is a race here, for slow network stations or fast CPUs ones, the load event may be launched long before the request is resolved, which results in the loading spinner to remain.
Is there maybe a way to check if the load event was already lanched?
If I can do that, I'll be able to check it after adding the event listener and in case it was already launched, I'll remove the spinner manually.
I would't use jquery for this task (or at all in react) as you can do it in a more "reactish" way.
You can store the data in your state and conditionally render the component in your render method when the state has changed.
You can't avoid the first render by the way.
Small example:
const Loader = () => <div>Loading...</div>
const MyComponent = ({message}) => <div>{message}</div>
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ''
};
}
componentDidMount(){
// mimic async operation
setTimeout(()=>{
this.setState({message: 'Hi there!'})
}, 1500);
}
render() {
const {message} = this.state;
return (
<div>
{message ? <MyComponent message={message} /> : <Loader />}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Edit
As a followup to your comment:
But then you re-render the entire component just to change the display
style of the preloader element, right?
Not entirely true i think you should read more about Reconciliation and The Diffing Algorithm and look at this example:
React DOM compares the element and its children to the previous one,
and only applies the DOM updates necessary to bring the DOM to the
desired state.

Delay in Rendering a Component?

I'm attempting to make my own personal website, and trying to use React to do so. In the process, I intend to make each section a different React Component. My plan is to have the navbar at the top be able to select which component is currently "active", and actually gets rendered and shown. In addition, when switching to a new section, I would like the old component to have a "leaving" animation, and the new component to have an "entering" animation (these are done with react-motion). However, currently both the entering and leaving are done at the same time, because I'm changing the active state for both components at the same time. Is there any way to delay one component becomes active after another one becoming inactive?
The parent component that houses each section looks like so:
class Website extends React.Component {
constructor(props){
super(props)
this.state = {
homeActive: true,
aboutActive: false
}
homeActivator(){
this.setState({
homeActive: true,
aboutActive: false
})
}
aboutActivator(){
this.setState({
homeActive: false,
aboutActive: true
})
}
render(){
return (
<div>
<NavBar handleHome={this.homeActivator.bind(this)} handleAbout=
{this.aboutActivator.bind(this)}/>
<Home active={this.state.homeActive} />
<About active={this.state.aboutActive} />
</div>
}
And then one of the "sections" would look like so:
class Home extends React.Component{
render() {
let content = (
<div>
Home
</div>
)
if (!this.props.active){
return (
//Some jsx that results in the content leaving the page
)
}
return(
//Some jsx that results in the content entering the page
)
}
}
I did not have a ton of time to answer this, but came up with the best example I could. It's not an exact replica of what you are looking to do, but is very similar, so if you understand it, you will be able to figure out your problem quite easily.
To make things a little easier to understand, I am mimicking components with methods placed inside the React Class. Obviously in the real world, you would be importing your components from other files. I'm sure you'll understand what's going on.
export default class Example extends Component {
constructor(props) {
super(props)
this.state = {
c1: true,
c2: false
}
}
// Component One
renderc1() {
return (
<div>
I am component one
</div>
)
}
// Component Two
renderc2() {
return (
<div>
I am component two
</div>
)
}
changeComponents = () => {
this.setState({ c1: false })
setTimeout(() => {
this.setState({ c2: true })
}, 1500)
}
render() {
return (
<div className="example">
{this.state.c1 ? this.renderc1() : null}
{this.state.c2 ? this.renderc2() : null}
<button onClick={this.changeComponents}>Click me</button>
</div>
)
}
}
Clicking the button will fire off the changeComponents function, which will then immediately set the state of "c1" to false. A setTimeout after that ensures that component 2 will be delayed rendering to the screen.
Notice the arrow syntax, I used, which binds the this keyword to the class, so you don't have to worry about writing bind this everywhere.

Categories

Resources