React - wait for div to load fully - reactjs

So Im fetching some messages from DB and then I'd like to show them in a chat div. Here's the code:
componentDidMount():
socketio.on("message", function(data) {
// socketio is initialized outside of class
// and the server emits "message" on client connection
that.setState({
// before this happens, there is no "messages" var in state
messages: data
});
});
render():
<div class="chatbox">
{messages &&
messages !== undefined &&
messages.length > 0 ? (
messages.map(function(item, i) {
if (item.from === uid) return <div class="message-incoming">{item.body}</div>;
else return <div class="message-outgoing">{item.body}</div>;
})) : (
<></>
)
}
</div>
The message get loaded into a div with class "chatbox". What I would like to do is to wait for these messages to get rendered and after that manually scroll the "chatbox" to bottom, so users can see latest messages. But so far I haven't figured out a way to make this work, because no matter what code I try, it gets triggered before map finishes and not after. Basically I would like to do:
document.getElementById("chatbox").scrollTop =
document.getElementById("chatbox").scrollHeight;
But no matter where i execute this code, scrollHeight is always the same as initial height of "chatbox", because the line gets triggered before all messages are rendered. I want to trigger it after the div is completely loaded.
Any ideas?

Just return null if messages is not yet populated:
render() {
if(!this.state.messages) return null;
<div class="chatbox">
...

I recommend you use the componentDidUpdate lifecycle method to scroll to the bottom.
componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render.
Use this as an opportunity to operate on the DOM when the component has been updated. [...]
class ChatComponent extends Component {
componentDidUpdate(prevProps, prevState) {
if (prevState.messages.length != this.state.messages.length) { // Or any other comparison
document.getElementById("chatbox").scrollTop = document.getElementById("chatbox").scrollHeight;
}
}
/* ... */
}

Related

How to keep value displayed after reloading of the page?

I have a React.js application and it connects and subscribes to an MQTT broker. Initially, it gets the value from the broker properly but if I reload the page, it first shows me 0 and then it has to wait for this value to be received again from the broker so it will not get its actual value until the subscribe is notified for a change.
What I want is that after reloading of the page, I can still see the value before that. How can I do that?
Thanks in advance.
Here is my code:
render() {
//access range from redux store via props
const {rangeEstimate} = this.props
//if range is not null - show it, otherwise - show no data
const rangeValue = rangeEstimate && rangeEstimate>=0 ?
(<div>
{rangeEstimate.toString()}
</div>)
: (<div>0</div>)
if(rangeEstimate>=0 && rangeEstimate) {
localStorage.setItem('rangeValue', rangeEstimate);
}
const {battery} = this.props
//if battery is not null - show it, otherwise - show no data
const batteryValue = battery && battery>=0 ?
(<div>
{battery +"%"}
</div>)
: (<div>0%</div>)
return (
<StyledRangeContainer className="RANGE">
<div>
<StyledRangeText>{rangeValue}</StyledRangeText>
</div>
<StyledBatteryCapacityValue>{batteryValue}</StyledBatteryCapacityValue>
</StyledRangeContainer>
);
}
}
//map this component's state to props from redux store
const mapStateToProps = (state) => {
return {
rangeEstimate:state.rangeEstimate,
battery:state.battery
}
}
//connect this component to Redux
export default connect(mapStateToProps,null) (RangeEstimate)
Assuming you have control over the MQTT publisher, publish the value with the retained bit set.
That means that when ever a client subscribes to the topic, the last published value will be delivered by the broker immediately. The next published message (with the retained bit) will then replace this stored value.
In most software engineering, we set initial or starting values in the constructor. So, let's add one and use it in your class. I'm assuming it's a ReactJS class, because you are using a render() method. Here's your constructor...
constructor(props) {
super(props);
this.state = {
'rangeValue':localeStorage.getItem('rangeValue'),
'batteryValue':localeStorage.getItem('batteryValue'),
};
}
Notice I am populating the state with the results of localeStorage.getItem().
Then you need to update your render() to be based on the state...
return (
<StyledRangeContainer className="RANGE">
<div>
<StyledRangeText>{this.props.rangeValue ? this.props.rangeValue : this.state.rangeValue}</StyledRangeText>
</div>
<StyledBatteryCapacityValue>{this.props.batteryValue ? this.props.batteryValue : this.state.batteryValue}</StyledBatteryCapacityValue>
</StyledRangeContainer>
);
Of course, you'll want to use the prop value if it's there, so this code reflects that, by checking the props. You may want this to check a variable, but that would work, too.

React: img onLoad and Chicken/Egg problem I cannot escape

I have a React control that renders a bunch of images. My goal is to avoid the flickering that is caused by an unknown time it takes React to load the images (yes, I know about inline image loading, let's pretend it doesn't exist for a moment)
I have an initialized array in my class:
this.loadedImages = [];
For this purpose I use onLoad in this manner:
render () {
let items = this.props.images.map((value, index) => {
let style = {};
if (this.isImageLoaded(index))
style = value.style;
else
style = {visibility: 'hidden'};
return <img
key={ index }
onClick={ this.onClick }
onLoad={ this.onLoad(index) }
style={ style }
src={ value.image }
alt={ value.alt}/>
});
return (
<div>
{items}
</div>
);
}
}
my onLoad and isImageLoaded look like this:
onLoad = (index) => {
if (!this.isImageLoaded(index)) {
this.loadedImages.push(index);
}
};
isImageLoaded = (index) => {
let isloaded = this.loadedImages.includes(index);
if (isloaded)
console.log(index + " is loaded!");
else
console.log(index + " is NOT loaded ");
return isloaded;
};
The issue is that once my page loads, the images switch from a "not loaded" into a "loaded" mode -- BUT there is only ONE RENDER that occurs before the images are loaded, thus the {visibility: 'hidden'} style remains permanent.
So my page loads without images. Now, if I click my component even once, the images will appear correctly because the component is forced to re-render (since now the images are loaded). BUT there is no option for me to force such a re-draw programmatically from the onLoad function as I'm getting a warning I should not be doing that from render...
My question is: how can I break the chicken/egg problems here and re-render my component once any image completes its loading.
I suggest combining your loadedImages data with the your other image state (as a boolean flag on each) and updating it using setState every time one loads (your headaches are due to this separation and the fact that you are having to manually keep them synchronised).
Then map over the single array of images (including loading state), using something like the src for the key.

Cannot read property 'destination' of undefined when react state is an array

when my react state is an object, I can get its property in render method,
but when I set the state to array and use state[0].property in render method, it give me the undefined error, cannot figure out, any help??? Thanks!!!
class App extends Component {
state={
dataArray:[]
}
componentDidMount(){
this.getTrainInfo();
}
getTrainInfo=()=>{
fetch('https://api-v3.mbta.com/routes')
.then(response=>response.json())
.then(res=>{
//res will return an array of objects
let arr=[];
let data={};
data.destination=res.data[0].attributes.direction_destinations[0];
data.lineID=res.data[0].relationships.line.data.id
arr.push(data);
this.setState({dataArray:arr});
//I put a console.log(dataArray) here, I can get
// [{destination:xxx, lineID:xxx }] back.
})
}
render() {
//in here, I also can get the array: [{destination: xxx, lineID: xxx}]
let data=this.state.dataArray;
//but in here, I get error saying property destination is undefined,
//why?
let destination=data[0].destination;
//console.log(destination);
return (
<div className="App">
<h1>Train info</h1>
</div>
);
}
}
This is normal behavior. React will render you component a single time before ever executing the logic in componentDidMount() Which is why you're getting the undefined error because your inital state starts out as an empty array.
To resolve this, it is common practice to have a "loading state" while your component updates with the new data. So when the state is finally updated, your component will re-render and display your desired content.
In your render, try doing something like:
render() {
let data=this.state.dataArray;
if(this.state.dataArray.length == 0){
return Loading...
} else {
return (
<div className="App">
// whatever logic you want to display
<h1>Train info</h1>
</div>
)
}
You need to add condition because on initial render the dataArray is empty array and doesn’t have any objects in it.
From second render on wards you have data in dataArray so add below condition
if(this.state.dataArray.length){ //this condition will be true when dataArray length is greater than zero
let destination=data[0].destination;
console.log(destination); //this will print the value
}

Stop React from rendering code that opens websockets

I have this code in render() that opens a websocket and connects to a Rest service.
return (
<div className="App">
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={(msg) => this.update()}
debug= {true}
/>
Everything works fine - the only issue with this approach is, that when the UI is rerendered, the application closes the websocket and reopens it again. Nothing breaks, the app just resumes and nothing happens.
Is this operation very resource consuming? Is there any way to tell react not to re-render that portion of code? Should I be worrying at all?
this.update() re-sets an array in my state.
I know shouldComponentUpdate() is what I need, but then I would have to create a new component that does the rerendering, right? I'm confused.
Thanks!
EDIT: This also works (#zavjs's solution is much more cleaner)
componentDidMount(){
var self = this; // use self to reference this
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame,) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification) {
self.update(); // here
})
}, function(err) {
console.log('err', err);
});
Also here's more about this!
Yes, I'd create an intermediary component, to wrap around your SocketJSClient, and condition shouldComponentUpdate to whenever you purposefully want to make that re-render happen (perhaps when a Socket config like url has changed and you need to pass that down, for instance).
class PreventUpdate extends React.Component {
shouldComponentUpdate = (nextProps) => {
//set true if a Socket connection config has changed
//or something that needs to trigger a re-render in the
//child component
if(this.nextProps.shouldPreventUpdate) {
return false;
}
};
render() {
this.props.children;
}
}
And now you can do:
<PreventUpdate
shouldPreventUpdate={someDynamicCondition}>
<SocketJSClient />
<PreventUpdate/>
With this you can pass in an arbitrary flag to prevent update of a component and all its children. I hope it fits you well

How to wait and fade an element out?

I have an alert box to confirm that the user has successfully subscribed:
<div className="alert alert-success">
<strong>Success!</strong> Thank you for subscribing!
</div>
When a user sends an email, I'm changing the "subscribed" state to true.
What I want is to:
Show the alert box when the subscribed state is true
Wait for 2 seconds
Make it fade out
How can I do this?
May 2021 update: as tolga and Alexey Nikonov correctly noted in their answers, it’s possible to give away control over how long the alert is being shown (in the original question, 2 seconds) to the transition-delay property and a smart component state management based on the transitionend DOM event. Also, hooks are these days recommended to handle component’s internal state, not setState. So I updated my answer a bit:
function App(props) {
const [isShowingAlert, setShowingAlert] = React.useState(false);
return (
<div>
<div
className={`alert alert-success ${isShowingAlert ? 'alert-shown' : 'alert-hidden'}`}
onTransitionEnd={() => setShowingAlert(false)}
>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={() => setShowingAlert(true)}>
Show alert
</button>
(and other children)
</div>
);
}
The delay is then specified in the alert-hidden class in CSS:
.alert-hidden {
opacity: 0;
transition: all 250ms linear 2s; // <- the last value defines transition-delay
}
The actual change of isShowingAlert is, in fact, near-instant: from false to true, then immediately from true to false. But because the transition to opacity: 0 is delayed by 2 seconds, the user sees the message for this duration.
Feel free to play around with Codepen with this example.
Since React renders data into DOM, you need to keep a variable that first has one value, and then another, so that the message is first shown and then hidden. You could remove the DOM element directly with jQuery's fadeOut, but manipulating DOM can cause problems.
So, the idea is, you have a certain property that can have one of two values. The closest implementation is a boolean. Since a message box is always in DOM, it's a child of some element. In React, an element is result of rendering a component, and so when you render a component, it can have as many children as you want. So you could add a message box to it.
Next, this component has to have a certain property that you can easily change and be completely sure that, as soon as you change it, the component gets re-rendered with new data. It's component state!
class App extends React.Component {
constructor() {
super();
this.state = {
showingAlert: false
};
}
handleClickShowAlert() {
this.setState({
showingAlert: true
});
setTimeout(() => {
this.setState({
showingAlert: false
});
}, 2000);
}
render() {
return (
<div>
<div className={`alert alert-success ${this.state.showingAlert ? 'alert-shown' : 'alert-hidden'}`}>
<strong>Success!</strong> Thank you for subscribing!
</div>
<button onClick={this.handleClickShowAlert.bind(this)}>
Show alert
</button>
(and other children)
</div>
);
}
}
Here, you can see that, for message box, either alert-shown or alert-hidden classname is set, depending on the value (truthiness) of showingAlert property of component state. You can then use transition CSS property to make hiding/showing appearance smooth.
So, instead of waiting for the user to click button to show the message box, you need to update component state on a certain event, obviously.
That should be good to start with. Next, try to play around with CSS transitions, display and height CSS properties of the message box, to see how it behaves and if the smooth transition happening in these cases.
Good luck!
PS. See a Codepen for that.
The correct way is to use Transition handler for Fade-in/out
In ReactJS there is synthetic event to wait till fade-out is finished: onTransitionEnd.
NOTE there are different css effects associated with different handlers. Fade is a Transition not an Animation effect.
Here is my example:
const Backdrop = () => {
const {isDropped, hideIt} = useContext(BackdropContext);
const [isShown, setState] = useState(true);
const removeItFromDOM = () => {
debugger
setState(false)
};
return isShown
? <div className={`modal-backdrop ${isDropped ? 'show' : ''} fade` } onClick={hideIt} onTransitionEnd={removeItFromDOM}/>
: null
}
An other way is to solve this with a CSS3 transition.
https://www.tutorialspoint.com/css/css_animation_fade_out.htm
You can add a new class to the alert (like .hidden) and then you can relate .hidden with the class you defined for the alert.
alert.hidden{
// Here you can define a css transition
}
In this solution you don't have to add a setInterval or anything, since css3 transitions already process it on browser render.

Resources