React Server Side Rendering - addEventListener - reactjs

I have a server side rendering react app (because I need Facebook Seo as well).
A part of my app requires to get window.innerWidth.
I have been searching for a long time, most of them says you cannot find window on server side, so you need to do rendering on client side as well.
I'm not sure how things work, I have componentdidmount but my windowWidth is forever 0.
After server rendering, our bundle.js will kick in and window on client side will work right? How come it's still 0?
state = {
windowWidth: 0
}
handleResize(){
this.setState({windowWidth: window.innerWidth});
}
componentDidMount () {
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}
render() {
return (<div>{this.state.windowWidth}</div>)
}

The problem is that you attaching a function to set the new width to a "resize" listener which means only when you resize the screen the new width will added to state. You need to set the width inside componentDidMount and then you will have it width right on mount.
sandbox
CODE:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
windowWidth: 0
};
}
handleResize = () => {
this.setState({ windowWidth: window.innerWidth });
};
componentDidMount() {
this.setState({ windowWidth: window.innerWidth });
window.addEventListener("resize", this.handleResize);
}
componentWillUnmount() {
window.removeEventListener("resize", this.handleResize);
}
render() {
return (
<div>{this.state.windowWidth && <p>{this.state.windowWidth}</p>}</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Related

How to update state just after rendering

I have the following component:
import React from 'react';
import './styles.css';
import ToolTip from '../../Common/components/ToolTip/ToolTip';
export default class RouteTitleTooltipComponent extends React.Component {
constructor(props) {
super(props);
this.titleParagraphRef = React.createRef();
this._tooltipTimer = null;
this.state = { shouldPopupBeEnabled: false, isTooltipShown: false };
this._showTooltip = this._showTooltip.bind(this);
this._hideTooltip = this._hideTooltip.bind(this);
}
componentDidMount() {
const { scrollWidth, clientWidth } = this.titleParagraphRef.current;
const shouldPopupBeEnabled = scrollWidth > clientWidth;
this.setState({ shouldPopupBeEnabled });
}
_showTooltip() {
this._tooltipTimer = setTimeout(
() => {
this.setState({ isTooltipShown: true });
}, 1000,
);
}
_hideTooltip() {
clearTimeout(this._tooltipTimer);
this.setState({ isTooltipShown: false });
}
render() {
const { shouldPopupBeEnabled, isTooltipShown } = this.state;
const { message } = this.props;
return (
<ToolTip
message="Tooltip!!"
popoverOpen={shouldPopupBeEnabled && isTooltipShown}
>
<div
ref={this.titleParagraphRef}
onMouseOver={this._showTooltip}
>
{message}
</div>
</ToolTip>
);
}
}
This basically renders a floating tooltip over a div element if the message inside of it is bigger than the container. To do that, I use scrollWidth and clientWidth of the div element using a React reference. To detect those values I use componentDidMount, but this only works in full renders of the component. That is, if I have the component visible and reload the page, both values are equal to 0 and it does not work.
In addition, if I change the message, it does not work either because the component is already mounted.
So what I want is to change the state right after the component is mounted or updated so that the react reference is rendered and clientWidth and scrollWidth are not 0.
I have tried replace componentDidUpdate instead of componentDidMount but it's not a good practica to use setState inside componentDidUpdate.
Any solution?
First you should know that componentDidMount will execute only once. Therefor you can go for componentDidUpdate but don't forget to put a condition as it will render in a loop.
componentDidUpdate(prevProps,prevState) {
const shouldPopupBeEnabled = scrollWidth > clientWidth;
if (shouldPopupBeEnabled !== this.state.shouldPopupBeEnabled ) {
this.setState({shouldPopupBeEnabled });
}
}
Or you can go for functional components and use useEffect which will only render again if state changes.
useEffect(() => {
console.log('mounted');
}, [shouldPopupBeEnabled]) // It will re render id `shouldPopupBeEnabled` changes

How do you use the Swiper callback methods in React?

I have a component class and need to be able to use .slideToLoop() and .update(), and haven't been able to figure out how to use those methods with the Swiper React library.
I need to do this when something else is clicked on, so that the Swiper can update (as it's hidden initially), and then slideTo the relevant slide.
At the moment, I have the click trigger in jQuery in componentDidMount() as I'm porting things over into React. But happy to change this as well if it's better to. The click happens on a grandchild component.
And I have the swiper instance being set into the state, but that happens after componentDidMount, so I can't access it from there.
Code:
constructor(props) {
super(props);
this.state = {
swiperIns: ''
}
}
setSwiper = (swiper) => {
this.setState({swiperIns: swiper}, () => {
console.log(this.state.swiperIns);
});
}
componentDidMount() {
const { appStore } = this.props;
if (!appStore.hasLoadedHomePage)
appStore.loadHomePage().catch((ex) => null);
const mySwiper = this.state.swiperIns;
console.log(mySwiper); // returns ''
$('.modal-trigger').on('click', function(e) {
e.preventDefault();
var modalToOpen = $(this).attr('data-modal-id');
console.log(modalToOpen);
if ($(this)[0].hasAttribute('data-slideindex')) {
const slideTo = parseInt($(this).attr('data-slideindex'));
// this.state.slideToLoop(slideTo);
}
$('#' + modalToOpen).show();
$('body').addClass('modal-open');
if ($('#' + modalToOpen).hasClass('modal--swiper')) {
// this.state.swiperIns.update();
}
});
}
and the return part of the Swiper:
<Swiper onSwiper={ this.setSwiper } spaceBetween={0} slidesPerView={1} loop>
...
</Swiper>
Any help or suggestions are appreciated.
Okay, so I figured it out.
First, you add a ref to the constructor:
constructor(props) {
super(props);
this.swiperRef = React.createRef();
}
And in componentDidMount, like so:
const mySwiper = this.swiperRef;
And then on the Swiper element, you set the ref to the Swiper instance like so:
<Swiper ref={ this.swiperRef }...</Swiper>
And then in button clicks/functions, you can use this.swiperRef.current?.swiper.slideNext(); or any other Swiper callback methods to update/slideTo/etc.

React - get element.getBoundingClientRect() after window resize

I have a class that needs to get the size of a DOM element. It works well, but when I resize the window it doesn't update until I change the state in my app, forcing a rerender. I've tried adding this.forceUpdate to a 'resize' event listener in componentDidMount(), but it didn't work. Perhaps I did something wrong? Ideally I'd like to avoid using this.forceUpdate for perfomance implications anyway. Any work arounds for this? Thanks in advance!
My code:
class MyComponent extends React.Component {
state = { x: 0, y: 0 }
refCallback = (element) => {
if (!element) {
return
}
const { x, y } = element.getBoundingClientRect()
this.setState({ x, y })
}
render() {
console.log('STATE:', this.state) // Outputs the correct x and y values.
return (
<div ref={this.refCallback}>
<button>Hello world</button>
</div>
)
}
}
If you want to measure some element in your component whenever the window resizes, it's going to look something like this:
class MyComponent extends React.Component {
state = {
x: 0,
y: 0,
};
element = React.createRef();
onWindowResize = () => {
if (this.element.current) {
const {x, y} = this.element.current.getBoundingClientRect();
this.setState({x, y}, () => {
console.log(this.state);
});
}
};
componentDidMount() {
window.addEventListener('resize', this.onWindowResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onWindowResize);
}
render() {
return (
<div ref={this.element}>
<button>Hello, World</button>
</div>
);
}
}
The trick here is that your ref callback is only called once, when the element is initially added to the DOM. If you want to update state whenever you resize the window, you're going to need a 'resize' event handler.
That happening because:
From the React documentation:
Adding a Ref to a DOM Element
React supports a special attribute that you can attach to any component. The ref attribute takes a callback function, and the callback will be executed immediately after the component is mounted or unmounted.
React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.
So, that's why when you refresh you get the value. To overcome the problem you can do something like this:
import React from "react";
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
this.state = {
x: 0,
y: 0
};
}
updateDimensions = () => {
if (this.myRef.current) {
const {x, y} = this.myRef.current.getBoundingClientRect();
this.setState({ x, y });
}
};
componentDidMount() {
window.addEventListener("resize", this.updateDimensions);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions);
}
render() {
console.log("STATE:", this.state); // Outputs the correct x and y values.
return (
<div ref={this.myRef}>
<button>Hello world</button>
</div>
);
}
}
export default MyComponent;
Hope this works for you.

How to update Highchart from inside react a component?

I am working with react 16.3 where componentWillUpdate is deprecated (strict mode). We have a react wrapper around Highcharts and used to update the highchart in componentWillUpdate that runs just before render.
But now in react 16.3 when the input highchartoptions prop updates, there seems to be no way to call Highchart.update before render() is called. Its suggested to use componentDidUpdate but its called only after render() and it doesn't seem to work at all.Any suggestions will help.
Code snippet here:
export class HighchartReactWrapper extends React.Component {
constructor(props) {
super(props);
// We maintain the user provided options being used by highchart as state
// inorder to check if chart update is needed.
this.state = { highChartOptions: this.props.options };
this.onChartRendered = this.onChartRendered.bind(this);
}
componentDidMount() {
// Create chart
this.chart = new Highcharts.Chart(this.container, this.state.highChartOptions, this.onChartRendered);
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.options !== prevState.options) {
return { highChartOptions: nextProps.options };
}
}
componentDidUpdate() {
this.chart.update(this.state.highChartOptions, false, true); <---- Doesn't work
}
onChartRendered() {
// Callbacks..
if (this.props.onChartRenderedCallback !== undefined) {
this.props.onChartRenderedCallback();
}
}
componentWillUnmount() {
// Destroy chart
this.chart.destroy()
}
render() {
return (
<div className="react-highchart-wrapper">
<div id={container => this.container = container} />
</div>
);
}
}
HighchartReactWrapper.propTypes = {
/**
* Chart options to be used in Highcharts library.
*/
options: PropTypes.object.isRequired,
onChartRenderedCallback: PropTypes.func
};
HighchartReactWrapper.defaultProps = {
options: undefined,
onChartRenderedCallback: undefined
};
You may use shouldComponentUpdate(nextProps, nextState) which is called before the component rerender.

React. Debouncing function that implements setState method

i am developing a simple hoc component that passes viewport dimensions to its children. On window resize, I initiate handleResize method to pass new window dimensions into child component. I want to use debounce func from lodash to minimize number of times that handleResize method is called(ref).
import React from 'react'
import debounce from 'lodash/debounce'
const getDimensions = (Component) => {
return class GetDimensions extends React.Component {
constructor () {
super()
this.state = {
viewport: {
x: window.innerWidth,
y: window.innerHeight
}
}
}
handleResize = () => {
this.setState(() => ({viewport: {x: window.innerWidth, y: window.innerHeight}}))
}
componentDidMount = () => {
if (window) window.addEventListener('resize', debounce(this.handleResize, 400))
}
componentWillUnmount = () => {
if (window) window.removeEventListener('resize', this.handleResize)
}
render () {
return (
<Component
{...this.props}
viewport={this.state.viewport}
/>
)
}
}
}
export default getDimensions
It works as expected but i keep getting the warning that:
does anyone knows what is going on?
please let me know
keep in mind you are not removing the event. if (window) window.addEventListener('resize', debounce(this.handleResize, 400)) will mutate the function and return a wrapped function, the removal of the event just passes the original this.handleResize, which won't be found.
you need to this.handleResize = debounce(this.handleResize, 400) in the constructor.
tl;dr: component will unmount but event will continue firing.

Resources