ReactJS cannot seem to call function after page load - javascript

I need to be able update button class styles after page loads.
Tried doing it in Render() and i have seen people talk about setTimeout and setInterval, but this other way with event is working part of the time
ComponentDidMount makes a axios web api call grabs data and in a map all is in render. I need to check local storage and such, and want to update button color and text, so I tried many things, but then tried window.addeventlistener ... this.handleload
Seems to work only part of the time.... its like it is happening TOO FAST, I don't want to add "hack" like timers, but i'm stuck with no idea how to do this.
I tried calling functions in the render as well. Not sure why this is so hard to do.
handleLoad() {
alert('always runs from outside loop');
// loop ONLY runs after refreshing browser several times
for (var i = 0; i < this.state.data.length; i++) {
//rarely makes it in
alert('made it');
document.getElementById("4534552").classList.remove('btn-warning');
}
}
componentDidMount() {
webApi.get('sai/getofflinemembers?userId=N634806')
.then((event) => {
//........
}
// THIS is what i call
window.addEventListener('load', this.handleLoad);
}
render() {
const contents = this.state.data.map(item => (
<button id={item.Member_ID} type="button" onClick={(e) => this.downloadUser(item.Member_ID,e)}
className="btn btn-warning">Ready for Download</button>
)
}
I just need to call a function and loop over all the DOM and change it as needed. Now that has me thinking about react creating a virtual DOM to which i don't know.
Needing to check local storage if a member is already set, then set the class of a bootstrap button to a specific color and text as well.
Thoughts?

Just an advice: If you want to manipulate the DOM, it's better to use refs instead of using document.getElementById. https://reactjs.org/docs/glossary.html#refs
But your problem can be solved by using state to store the css class:
class SomeComponent extends React.Component {
state = {
buttonCSSClass: 'btn btn-warning',
}
componentDidMount() {
webApi.get('sai/getofflinemembers?userId=N634806')
.then((event) => {})
.then(() => this.setState({buttonCSSClass: 'btn'})) // it will update the css class
}
}
render() {
const { data, buttonCSSClass } = this.state
return data.map(item => (
<button
key={item.Member_ID}
id={item.Member_ID}
type="button"
onClick={e => this.downloadUser(item.Member_ID, e)}
className={buttonCSSClass}
>
Ready for Download
</button>
))
}
}

Related

Modal component with fade-in overlay and dialog disabled until animation ends

I'm trying to rewrite one of my JS plugins to react, as a way of learning.
I have a panel that when hidden/shown needs to be updated with several classnames as well as some that need to wait for a css animation to complete (why the timer).
How should I do this in a react way? Using querySelector to change classnames seem very wrong..?
Detailed explanation
When showPanel is triggered the following need to happen
the body/html element need updated css (hence me adding classes)
an existing overlay fades in (adding a class to that)
the modal div is displayed (adding a class for that)
the modal div is told to be active AFTER the animation has been run (hence the timer and class "am-animation-done")
What I preferably would like to have/learn is best practice to do this in reactjs. I'm thinking a toggle state that when triggered sets the state to visible/hidden and if set to "visible" the class changes below happens. My biggest issue is the timer thing.
showPanel = () => {
document.querySelector('body').classList.add('am-modal-locked');
document.querySelector('html').classList.add('am-modal-locked');
document.querySelector('.am-overlay').classList.add('fadein');
document.querySelector('.am-modal').classList.add('am-show');
const timer = setTimeout(() => {
document.querySelector('.am-modal').classList.add('am-animation-done');
}, 500);
return () => clearTimeout(timer);
};
hidePanel = () => {
document.querySelector('.am-modal').classList.remove('am-show');
document.querySelector('.am-modal').classList.remove('am-animation-done');
document.querySelector('.am-overlay').classList.add('fadeout');
const timer = setTimeout(() => {
document.querySelector('.am-overlay').classList.remove('fadein');
document.querySelector('.am-overlay').classList.remove('fadeout');
document.querySelector('body').classList.remove('am-modal-locked');
document.querySelector('html').classList.remove('am-modal-locked');
}, 500);
return () => clearTimeout(timer);
};
Source code updated for clarifaction
This is a lot simpler in React, here's an example with hooks
function Panel() {
const [hidden, setHidden] = useState(false);
const toggleCallback = useCallback(() => setHidden(hidden => !hidden), []);
const cls = hidden ? 'hide' : 'show';
return (
<div className={cls}>
<button onClick={toggleCallback}>Toggle</>
</div>
)
}
You can use the state to dinamically change classnames inside your component
className={this.state.isPanelVisible}
And maybe instead of setting it as boolean you can set your variable to the class you need at the moment.
React working with virtual DOM so you should play with state and change class of that particular element like below example:
constructor(props) {
super(props);
this.state = {'active': false, 'class': 'album'};
}
handleClick(id) {
if(this.state.active){
this.setState({'active': false,'class': 'album'})
}else{
this.setState({'active': true,'class': 'active'})
}
}
<div className={this.state.class} onClick={this.handleClick.bind(this.data.id}>
<p>Data</p>
</div>
In very basic use cases you can write the logic inside of the class itself.
<div className={active ? "active" : "disabled"} />
In more advanced cases I would suggest to use something like classnames package.
https://www.npmjs.com/package/classnames
<div className={classNames({ foo: true, bar: true, boo: false })} />
Which would result in div having class foo and bar
This is mainly regarding one component, but if you really have to affect class of something so far away as body would be, than you are most likely gonna need useQuerySelector or put the state somewhere high and then base the logic on it.
Yes that's not a very good way to do it. Instead you should use state variables to toggle your classes as well. There is no need to manually manipulate DOM. The you can set up your timeout inside the callback of your first setState to change state again.
Maybe something like this:
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
class1: 'on',
class2: 'off'
}
}
toggle = () => {
this.setState({class1: 'off'}, () => {
setTimeout(() => {
this.setState({class2: 'on'})
}, 2000)
})
}
render() {
const {class1, class2} = this.state;
return (
<div>
<h1 className={`${class1} ${class2}`} onClick={this.toggle}>Class toggle</h1>
</div>
)
}
}
use this approach for change styling on state change
<div className={`rest_of_classes ${isClassChange ? 'change_class_name': ''}`} />

How to return an HTML <div> tag from a javascript function in React?

I am working on a React application where I am trying to render text on the screen when a button is clicked. I have defined a function onButtonClick which gets triggered whenever the button is clicked. However, the HTML that I am returning from the function is not rendered on the screen. I am in the learning stages of React so please excuse me if the question seems silly.
class App extends Component {
constructor() {
super();
this.state = {
blockno:0
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1})
return(
<div>
<h3>Some text</h3>
</div>
);
}
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
</div>
);
}
}
The value is being returned, but the framework/browser/etc. has no reason to do anything with that value.
Try thinking about this a different way, a "more React way". You don't want to return the value to be rendered, you want to update state. Something like this:
constructor() {
super();
this.state = {
blockno:0,
showDiv: false // <-- note the new property in state
}
}
OnButtonClick = () => {
this.setState({blockno: this.state.blockno + 1, showDiv: true})
}
Now you're not returning anything, but rather updating the state of the component. Then in your render method you conditionally render the UI based on the current state:
render() {
return(
<div>
<Button onButtonClick={this.OnButtonClick}/>
{
this.state.showDiv
?
<div>
<h3>Some text</h3>
</div>
: ''
}
</div>
);
}
The click handler doesn't modify the page, it just modifies the state of the component you're writing. The render method is responsible for rendering the UI based on that state. Any time state changes, render will be called again to re-render the output.
(Note: It's not 100% clear if this is exactly the functionality you're looking for in the UI, since it's not really clear what you're trying to build. But the point here is to illustrate how to update state and render output in React. Your logic can be tweaked as needed from there.)
You have to make a render based on your state. Please check the tutorial at the react docs to learn more about how React works. It's really good
Here is a version of your code that works. Hope it helps
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
};
}
OnButtonClick = () => {
//updates the states
this.setState({ blockno: this.state.blockno + 1 });
};
//remember: every time there is an update to the state the render functions re-runs
render() {
//variable holding the blocks in an array
let blocks = []
//if blockno is greater than 0, it checks everytime that there is a state change
if (this.state.blockno > 0) {
//for every block added
for (let index = 0; index < this.state.blockno; index++) {
//We`re going to add to the array of blocks a new div with the block number
blocks.push(
<div>
<h3>My block number is {index}</h3>
</div>
);
}
}
return (
<div>
<div>
{/**button that updates the state on every click */}
<button onClick={this.OnButtonClick}>
Click me to add a new div!
</button>
</div>
{/**This render the blocks variable that holds the divs */}
{blocks}
</div>
);
}
}
What I see is that you are trying to build a counter. The value that you're returning from the click handler function can't be rendered, instead you need to manage it in the render function as follow:
class App extends Component {
constructor() {
super();
this.state = {
blockno: 0
}
}
OnButtonClick = () => {
this.setState(prevState => ({ blockno: prevState.blockno + 1 }));
}
render() {
return(
<div>
{this.state.blockno > 0 && <div>some text {this.state.blockno}</div>}
<Button onButtonClick={this.OnButtonClick} />
</div>
);
}
}
Also note that the setState method is asynchronous, please read the documentation https://reactjs.org/docs/react-component.html#setstate

React state is out of sync between render method and what is actually displayed on page

I need to access DOM elements outside of my React app, which may load slower than my app. Then I need to update my state to render a few different things. To do that I am polling for the DOM elements with a recursive function that gets kicked off from componentDidMount(). I'm seeing a weird issue where once the element is found and I've updated the state, things get out of sync. In the render function, my console.log() shows the updated state value, in React Developer Tools I see the updated state value, but on the actual rendered page I see still see the old state value.
Code:
// initially doesn't exist. Added to the DOM after 3 seconds
let slowElement = document.querySelector('.external-dom-element')
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
showFoundSlowElementMessage: false,
slowElementCheckMaxAttempts: 5,
slowElementCheckCount: 0,
}
this.checkForSlowElement = this.checkForSlowElement.bind(this)
}
componentDidMount () {
this.checkForSlowElement()
}
checkForSlowElement () {
slowElement = document.querySelector('.external-dom-element')
if (slowElement !== null) {
console.log('found') // element found, show message
this.setState({
showFoundSlowElementMessage: true
})
} else {
console.log('not found') // element not found, increment count and check again after delay
this.setState({
slowElementCheckCount: this.state.slowElementCheckCount + 1
}, () => {
if (this.state.slowElementCheckCount < this.state.slowElementCheckMaxAttempts) {
window.setTimeout(this.checkForSlowElement, 1000)
}
})
}
}
render() {
const foundSlowElement = this.state.showFoundSlowElementMessage
? <p>Found slow element</p>
: <p>No sign of slow element, checked {this.state.slowElementCheckCount} times</p>
// null until it is added to the page
console.log(foundSlowElement)
return (
<div>
<h1>Hello</h1>
{foundSlowElement}
</div>
);
}
}
}
ReactDOM.render(<App />, document.getElementById('react-target'));
// Simulate slow element by adding it to the DOM after 3 seconds
window.setTimeout(() => {
const root = document.getElementById('root');
const newElement = '<div class="external-dom-element">slow element</div>';
root.innerHTML += newElement;
}, 3000)
Working example on codepen
I figured this out myself. It has nothing to do with my component, it's the demo itself that is breaking it. When I simulate the slow element by appending the root element's inner html:
root.innerHTML += newElement;
It re-parses the entire element and React loses all of the event handlers, etc. that it had previously set up.
This thread helped me out

React, Using Refs to scrollIntoView() doesn't work in componentDidUpdate()

I'm using Redux in my app, inside a Component I want to scroll to an specific div tag when a change in the store happens.
I have the Redux part working so it triggers the componentDidUpdate() method (I routed to this compoennt view already).
The problem as far as I can tell, is that the method scrollIntoView() doesn't work properly cos componentDidUpdate() has a default behavior that scrolls to the top overwriting the scrollIntoView().
To work-around it I wrapped the function calling scrollIntoView() in a setTimeout to ensure that happens afeterwards.
What I would like to do is to call a preventDefault() or any other more elegant solution but I can't find where to get the event triggering the 'scrollTop'
I looked through the Doc here: https://facebook.github.io/react/docs/react-component.html#componentdidupdate
and the params passed in this function are componentDidUpdate(prevProps, prevState) ,since there is no event I don't know how to call preventDefault()
I've followd this Docs: https://facebook.github.io/react/docs/refs-and-the-dom.html
And tried different approaches people suggested here: How can I scroll a div to be visible in ReactJS?
Nothing worked though
Here is my code if anyone has any tip for me, thanks
class PhotoContainer extends React.Component {
componentDidUpdate(){
setTimeout(() => {
this.focusDiv();
}, 500);
}
focusDiv(){
var scrolling = this.theDiv;
scrolling.scrollIntoView();
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>this is the div I'm trying to scroll to</div>
</div>
)
};
}
Ok it's been a while but I got it working in another project without the setTimeOut function so I wanted to answer this question.
Since Redux pass the new updates through props, I used the componentWillRecieveProps() method instead of componentDidUpdate() , this allowes you a better control over the updated properties and works as expected with the scrollIntoView() function.
class PhotoContainer extends React.Component {
componentWillReceiveProps(newProps) {
if (
this.props.navigation.sectionSelected !==
newProps.navigation.sectionSelected &&
newProps.navigation.sectionSelected !== ""
) {
this.focusDiv(newProps.navigation.sectionSelected);
}
}
focusDiv(section){
var scrolling = this[section]; //section would be 'theDiv' in this example
scrolling.scrollIntoView({ block: "start", behavior: "smooth" });//corrected typo
}
render() {
const totalList = [];
for(let i = 0; i < 300; i += 1) {
totalList.push(
<div key={i}>{`hello ${i}`}</div>
);
}
return (
<div >
{totalList}
<div ref={(el) => this.theDiv = el}>
this is the div I am trying to scroll to
</div>
</div>
)
};
}
I also struggled with scrolling to the bottom of a list in react that's responding to a change in a redux store and I happened upon this and a few other stackoverflow articles related to scrolling. In case you also land on this question as well there are a few ways this could be a problem. My scenario was that I wanted a 'loading' spinner screen while the list was rendering. Here are a few wrong ways to do this:
When loading = true, render spinner, otherwise render list.
{loading ?
<Spinner />
:
<List />
}
as stated above this doesn't work because the list you might want to scroll to the bottom of isn't rendered yet.
When loading set the display to block for the spinner and none for the list. When done loading, reverse the display.
<div style={{display: loading ? 'block' : 'none'>
<Spinner />
</div>
<div style={{display: loading ? 'none' : 'block'>
<List />
</div>
This doesn't work either since the list you want to scroll to the bottom of isn't actually being displayed likely when you call the scroll.
The better approach for the above scenario is to use a loading that acts as an overlay to the component. This way both the spinner and list are rendered and displayed, the scroll happens, and when the loading is complete, the spinner can be de-rendered or set to be invisible.

React updating DOM in an unpredictable manner with setState and class names

I'm attempting to do an animation with React and CSS classes. I have created a live demo, if you visit it and click the Start button you will see the text fade in and up one by one. This is the desired animation that I am after.
However, there seems to be issues of consistency when you hit Start multiple times and I cannot pinpoint why.
The Issue: Below is a recording of the issue, you can see the number 1 is not behaving as expected.
live demo
The process: Clicking Start will cancel any previous requestAnimationFrame' and will reset the state to it's initial form. It then calls the showSegments() function with a clean state that has no classNames attached to it.
This function then maps through the state adding a isActive to each segment in the state. We then render out the dom with a map and apply the new state.
This should create a smooth segmented animation as each class gets dropped one by one. However when i test this in Chrome (Version 56.0.2924.87 (64-bit)) and also on iOS, it is very inconsistent, sometimes it works perfectly, other times the first DOM element won't animate, it will just stay in up and visible it's completed transitioned state with "isActive".
I tried to replicate this issue in safari but it worked perfectly fine, I'm quite new to react so i am not sure if this is the best way to go about things, hopefully someone can offer some insight as to why this is behaving quite erratic!
/* MotionText.js */
import React, { Component } from 'react';
import shortid from 'shortid';
class MotionText extends Component {
constructor(props) {
super(props);
this.showSegments = this.showSegments.bind(this);
this.handleClickStart = this.handleClickStart.bind(this);
this.handleClickStop = this.handleClickStop.bind(this);
this.initialState = () => { return {
curIndex: 0,
textSegments: [
...'123456789123456789123456789123456789'
].map(segment => ({
segment,
id: shortid.generate(),
className: null
}))
}};
this.state = this.initialState();
}
handleClickStop() {
cancelAnimationFrame(this.rafId);
}
handleClickStart(){
cancelAnimationFrame(this.rafId);
this.setState(this.initialState(), () => {
this.rafId = requestAnimationFrame(this.showSegments);
});
}
showSegments() {
this.rafId = requestAnimationFrame(this.showSegments);
const newState = Object.assign({}, this.state);
newState.textSegments[this.state.curIndex].className = 'isActive';
this.setState(
{
...newState,
curIndex: this.state.curIndex + 1
},
() => {
if (this.state.curIndex >= this.state.textSegments.length) {
cancelAnimationFrame(this.rafId);
}
}
);
}
render(){
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
}
export default MotionText;
Thank you for your time, If there any questions please ask
WebpackBin Demo
Changing the method to something like this works
render(){
let d = new Date();
const innerTree = this.state.textSegments.map((obj, key) => (
<span key={d.getMilliseconds() + obj.id} className={obj.className}>{obj.segment}</span>
));
return (
<div>
<button onClick={this.handleClickStart}>Start</button>
<button onClick={this.handleClickStop}>Stop</button>
<hr />
<div className="MotionText">{innerTree}..</div>
</div>
)
}
How this helps is that, the key becomes different than previously assigned key to first span being rendered. Any way by which you can make the key different than previous will help you have this animation. Otherwise React will not render it again and hence you will never see this in animation.

Categories