React track height of div in state? - reactjs

I found the code snippet from this answer for tracking page size to be useful. I want to switch window.innerHeight with $("#list_container").height:
constructor(props) {
super(props);
this.state = { width: 0, height: 0 };
this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
}
componentDidMount() {
this.updateWindowDimensions();
window.addEventListener('resize', this.updateWindowDimensions);
}
componentWillUnmount() {
window.removeEventListener('resize', this.updateWindowDimensions);
}
updateWindowDimensions() {
// !!! This works:
this.setState({ width: window.innerWidth, height: window.innerHeight });
// !!! This doesn't work:
this.setState({ width: $("#list_container").width(), height: $("#list_container").height() });
}
Edit: Updated to .width() and .height(), had tried both but neither is working for me.
List container is defined in the same module as the outer div:
render() {
return (
<div id="list_container">
<ListGroup>
<VirtualList
width='100%'
height={this.state.height}
itemCount={this.state.networks.length}
itemSize={50}
renderItem={({index, style}) =>
<ListGroupItem
onClick={() => this.updateNetwork(this.state.networks[index].ssid, this.state.networks[index].security)}
action>
{this.state.networks[index].ssid}
</ListGroupItem>
}
/>
</ListGroup>
<Modal/>
// ...
Note:
If I do:
<p>{this.state.height}</p>
It isn't 0 just empty, with the example that doesn't work.

If it's jQuery you're using, width() and height() are functions, not properties. Try:
this.setState({ width: $("#list_container").width(), height: $("#list_container").height() });

you need to use refs since the js might be running before the component renders to the dom.
React documentation on refs
render() {
return (
<div id="list_container" ref={ el => this.componentName = el }>
// ...
to reference this dom node in this component you just have to call this.componentName
updateWindowDimensions = () => {
$(this.componentName).height() // returns height
}

To expand on Erics answer using refs, to pull it off without jQuery, you can use getBoundingClientRect:
const listRect = document.getElementById('list_container').getBoundingClientRect();
or if using the ref from Erics answer:
const listRect = this.componentName.getBoundingClientRect();
followed by
this.setState({
width: listRect.width,
height: listRect.height
});
I would suggest this page for some good examples of life without jQuery.
Also, I would strongly suggest debouncing the resize handler.

Related

Images Rerendering inside Styled Component when Chrome Dev Tools is open

This is a bit of a strange one and not sure why it's happening exactly.
When the component mounts, I call a function that in my application makes an HTTP request to get an array of Objects. Then I update 3 states within a map method.
enquiries - Which is just the response from the HTTP request
activeProperty - Which defines which object id is current active
channelDetails - parses some of the response data to be used as a prop to pass down to a child component.
const [enquiries, setEnquiries] = useState({ loading: true });
const [activeProperty, setActiveProperty] = useState();
const [channelDetails, setChannelDetails] = useState([]);
const getChannels = async () => {
// In my actual project,this is an http request and I filter responses
const response = await Enquiries;
const channelDetailsCopy = [...channelDetails];
setEnquiries(
response.map((e, i) => {
const { property } = e;
if (property) {
const { id } = property;
let tempActiveProperty;
if (i === 0 && !activeProperty) {
tempActiveProperty = id;
setActiveProperty(tempActiveProperty);
}
}
channelDetailsCopy.push(getChannelDetails(e));
return e;
})
);
setChannelDetails(channelDetailsCopy);
};
useEffect(() => {
getChannels();
}, []);
Then I return a child component ChannelList that uses styled components to add styles to the element and renders child elements.
const ChannelList = ({ children, listHeight }) => {
const ChannelListDiv = styled.div`
height: ${listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
return <ChannelListDiv className={"ChannelList"}>{children}</ChannelListDiv>;
};
Inside ChannelList component I map over the enquiries state and render the ChannelListItem component which has an assigned key on the index of the object within the array, and accepts the channelDetails state and an onClick handler.
return (
<>
{enquiries &&
enquiries.length > 0 &&
!enquiries.loading &&
channelDetails.length > 0 ? (
<ChannelList listHeight={"380px"}>
{enquiries.map((enquiry, i) => {
return (
<ChannelListItem
key={i}
details={channelDetails[i]}
activeProperty={activeProperty}
setActiveProperty={id => setActiveProperty(id)}
/>
);
})}
</ChannelList>
) : (
"loading..."
)}
</>
);
In the ChannelListItem component I render two images from the details prop based on the channelDetails state
const ChannelListItem = ({ details, setActiveProperty, activeProperty }) => {
const handleClick = () => {
setActiveProperty(details.propId);
};
return (
<div onClick={() => handleClick()} className={`ChannelListItem`}>
<div className={"ChannelListItemAvatarHeads"}>
<div
className={
"ChannelListItemAvatarHeads-prop ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.propertyImage})`
}}
/>
<div
className={
"ChannelListItemAvatarHeads-agent ChannelListItemAvatarHead"
}
style={{
backgroundSize: "cover",
backgroundImage: `url(${details.receiverLogo})`
}}
/>
</div>
{activeProperty === details.propId ? <div>active</div> : null}
</div>
);
};
Now, the issue comes whenever the chrome dev tools window is open and you click on the different ChannelListItems the images blink/rerender. I had thought that the diff algorithm would have kicked in here and not rerendered the images as they are the same images?
But it seems that styled-components adds a new class every time you click on a ChannelListItem, so it rerenders the image. But ONLY when the develop tools window is open?
Why is this? Is there a way around this?
I can use inline styles instead of styled-components and it works as expected, though I wanted to see if there was a way around this without removing styled-components
I have a CODESANDBOX to check for yourselves
If you re-activate cache in devtool on network tab the issue disappear.
So the question becomes why the browser refetch the image when cache is disabled ;)
It is simply because the dom change so browser re-render it as you mentioned it the class change.
So the class change because the componetn change.
You create a new component at every render.
A simple fix:
import React from "react";
import styled from "styled-components";
const ChannelListDiv = styled.div`
height: ${props => props.listHeight};
overflow-y: scroll;
overflow-x: hidden;
`;
const ChannelList = ({ children, listHeight }) => {
return <ChannelListDiv listHeight={listHeight} className={"ChannelList"}>{children}</ChannelListDiv>;
};
export default ChannelList;
I think it has to do with this setting to disable cache (see red marking in image)
Hope this helps.

React TypeScript document.getElementById always selects the first instance

I've got 2 of the same components being rendered
<div><Modal title='Join'/></div>
<div><Modal title='Login'/></div>
the modal components is like this
import React, {useState} from 'react';
import {Join} from './join';
import {Login} from './login';
interface propsInterface {
title: string;
}
const Modal: React.FC<propsInterface> = (props) => {
const [state, setState] = useState({showLogin: props.title === "login" ? false : true});
let modalState = false;
function toggleLogin(event: any) {
setState({...state, showLogin: !state.showLogin});
}
function toggleModal(event: any) {
if (event.target.id !== 'modal') return;
modalState = !modalState;
const modal = document.getElementById('modal'); <<==this always selects the first one
const card = document.getElementById('card'); <<==this always selects the first one
if (modal && card && modalState === true) {
modal.style.display = "flex";
modal.animate([{backgroundColor: 'rgba(0, 0, 0, 0)'}, {backgroundColor: 'rgba(0, 0, 0, 0.6)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{opacity: 0}, {opacity: 1}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{transform: 'translateY(-200px)'}, {transform: 'translateY(0)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
}
if (modal && card && modalState === false) {
modal.animate([{backgroundColor: 'rgba(0, 0, 0, 0.6)'}, {backgroundColor: 'rgba(0, 0, 0, 0)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{opacity: 1}, {opacity: 0}],{duration: 200, easing: 'ease-in-out', fill: 'forwards'});
card.animate([{transform: 'translateY(0)'}, {transform: 'translateY(-200px)'}], {duration: 200, easing: 'ease-in-out', fill: 'forwards'});
setTimeout(() => modal.style.display = "none", 200);
}
}
return (
<div>
<div className='modal' id='modal' onClick={toggleModal}>
<div className='card' id='card'>
{props.title}
{state.showLogin
? <Login toggleLogin={toggleLogin} toggleModal={toggleModal}/>
: <Join toggleLogin={toggleLogin} toggleModal={toggleModal}/>}
</div>
</div>
<div onClick={toggleModal} className='modal-title' id='modal'> {props.title}</div>
</div>
);
}
export {Modal};
Because there are 2 of this component now in the dom when I use
const modal = document.getElementById('modal');
the first instance of the component works as expected but the second instance is selecting the first instance not the second instance.
Is there a way to getElementById but only in this component?
You should only ever have one element with a given id in the same page, in your case you may want to use classes and use document.getElementsByClassName("classname") which is going to return an array of elements with the given class name.
Hope this helps
Instead of setting the Id as 'modal', you can pass id of your field as props and then set its id attribute.Then use getElementById for them.
Your can do like this:
<Modal title='Join' UniqId="modal1"/>
<Modal title='Login' UniqId="modal2"/>
Then in your component, you can do something like this:
<div className='modal' id={props.UniqId} onClick={toggleModal}>
After that, in your JS file, you can use this id:
const modal1 = document.getElementById('modal1');
const modal2 = document.getElementById('modal2');
The way that you are doing it looks like you are still using the mindset of using vanilla JavaScript to manipulate DOM elements after events.
Generally in the React world, you should be linking everything into your render() methods which are raised every time the state or the properties change within a react component.
So what I would do in your situation is remove all your code which is grabbing DOM elements, e.g. your document.getElementById code, and purely work from your state change. I am sorry that this won't be complete code, as I am not sure what your UI is supposed to look like.
const Modal: React.FC<propsInterface> = (props) => {
const [state, setState] = useState({ showLogin: props.title === "login" ? false : true, showModal: false, showCard: false });
function toggleLogin() {
setState({ showLogin: !state.showLogin });
}
function toggleModal() {
setState({ showModal: !state.showModal, showCard: !state.showCard });
}
const modalClassName = state.showModal ? 'modal show' : 'modal';
const cardClassName = state.showLogin ? 'card show' : 'card';
return (
<div>
<div className={modalClassName} onClick={toggleModal}>
<div className={cardClassName}>
{props.title}
{state.showLogin
? <Login toggleLogin={toggleLogin} toggleModal={toggleModal}/>
: <Join toggleLogin={toggleLogin} toggleModal={toggleModal}/>}
</div>
</div>
<div onClick={toggleModal} className='modal-title' id='modal'> {props.title}</div>
</div>
);
}
Effectively what happens is that when the toggleModal method is called, it just toggles the two state properties between true/false. This causes the component to re-render, and the class names change on the div elements.
From this, you will need to move all your animations into your css file and probably make use of animations in there (css animations are not my greatest skill). It's not a simple change to what you have, but I would always keep my styling and animations outside of a component and in a css file, and always work from rendering rather than trying to manipulate react nodes after render.

How to create a blinking react View

Can someone help me out how I can create a blink-able react-native component?
So basically, this is what I have done
class Blinkable extends PureComponent {
state = {
blinkComponentVisibility: false
}
blink () {
this.setState({blinkComponentVisibility: ! blinkComponentVisibility})
console.log(this.state)
}
componentDidMount = () => {
setTimeout(() => {this.blink}, 3000)
}
render () {
if (i === currentProgress) {
if (this.state.blinkComponentVisibility) {
progressBarArray.push(
<View
style={{
width: widthOfIndividualBlog,
backgroundColor: colorOfProgressBar,
height: heightOfProgressBar
}}
key={i}
></View>)
}
}
return (
<View>
<View style={{display: 'flex', flexDirection: 'row'}}>{progressBarArray}</View>
</View>
)
}
}
With the above code, I was expecting my component to blink but nothing happens rather I see the following logs in console
RCTLog.js:47 Could not locate shadow view with tag #363, this is
probably caused by a temporary inconsistency between native views and
shadow views
Can someone please help me in figuring out what I could be doing wrong?
this.setState({blinkComponentVisibility: ! blinkComponentVisibility}) should be this.setState({blinkComponentVisibility: ! this.state.blinkComponentVisibility})
and in your set timout you need to call the function
setTimeout(() => this.blink(), 3000)

How to show/hide spinners, snackbars, or other transient components with React Function Components

Is it better to render spinners, snackbars, etc. in separate DOM elements instead of adding them to the main application component tree? In React class components, it was really easy to get a reference to the class components methods to show/hide the spinner. With the new React Hooks function components, it's not so easy anymore. If I put the spinner in the main component tree, could I use the new "useContext" hook to show/hide the spinner?
Below is a React Hooks global spinner using Material-UI that works but is very hacky. How can this be made more elegant?
namespace Spinner {
'use strict';
export let show: any; // Show method ref.
export let hide: any; // Hide method ref.
export function Render() {
const [visible, setVisible] = React.useState(false); //Set refresh method.
function showIt() {
setVisible(true); // Show spinner.
}
function hideIt() {
setVisible(false); // Hide spinner.
}
const styles: any = createStyles({
col1Container: { display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column' },
});
return (
<div>
{visible && <div style={styles.col1Container}>
<CircularProgress key={Util.uid()}
color='secondary'
size={30}
thickness={3.6}
/>
</div>}
<SetSpinnerRefs showRef={showIt} hideRef={hideIt} />
</div>
); // end return.
} // end function.
const mounted: boolean = true;
interface iProps {
showRef();
hideRef();
}
function SetSpinnerRefs(props: iProps) {
// ComponentDidMount.
React.useEffect(() => {
Spinner.show = props.showRef;
Spinner.hide = props.hideRef;
}, [mounted]);
return (<span />);
}
} // end module.
The problem is similar to this one, and a solution for spinners would be the same as the one for modal windows. React hooks don't change the way it works but can make it more concise.
There is supposed to be single spinner instance in component hierarchy:
const SpinnerContext = React.createContext();
const SpinnerContainer = props => {
const [visible, setVisible] = React.useState(false);
const spinner = useMemo(() => ({
show: () => setVisible(true),
hide: () => setVisible(false),
}), []);
render() {
return <>
{visible && <Spinner />}
<SpinnerContext.Provider value={spinner}>
{props.children}
</SpinnerContext.Provider>
</>;
}
}
Which is passed with a context:
const ComponentThatUsesSpinner = props => {
const spinner = useContext(SpinnerContext);
...
spinner.hide();
...
}
<SpinnerContainer>
...
<ComponentThatUsesSpinner />
...
</SpinnerContainer>
In React class components, it was really easy to get a reference to the class components methods to show/hide the spinner
You can continue to use class components. They are not going anywhere 🌹
The not so good way
It is actually poor practice in my opinion to use class methods to show and hide a spinner. Assuming your api looks like
<Spinner {ref=>this.something=ref}/>
And you use
this.something.show(); // or .hide
The better way
<Spinner shown={state.shown}/>
Now you get to change state.shown instead of storing the ref and using show / hide.
Although I think that Basarat's answer is the modern way of solving this problem, the below code is the way I ended up doing it. This way I only need one line of code to build the spinner and only one line of code to show/hide it.
<Spinner.Render /> {/* Build spinner component */}
Spinner.show(); //Show spinner.
namespace Spinner {
'use strict';
export let show: any; //Ref to showIt method.
export let hide: any; //Ref to hideIt method.
export function Render() {
const [visible, setVisible] = React.useState(false); //Set refresh method.
function showIt() {
setVisible(true); //Show spinner.
}
function hideIt() {
setVisible(false); //Hide spinner.
}
const showRef: any = React.useRef(showIt);
const hideRef: any = React.useRef(hideIt);
//Component did mount.
React.useEffect(() => {
Spinner.show = showRef.current;
Spinner.hide = hideRef.current;
}, []);
const styles: any = createStyles({
row1Container: { display: 'flex', alignItems: 'center', justifyContent: 'center' },
});
return (
<div>
{visible && <div style={styles.row1Container}>
<CircularProgress
color='secondary'
size={30}
thickness={3.6}
/>
</div>}
</div>
); //end return.
} //end function.
} //end module.

Rearrange react components on resize

Is this even possible(title)? I need it, because in css #media rule needs to change some element's css. CSS part works. Problem occurs because it needed to be followed by rearranging react components. I have these conditions whom(both of them), pass. It should be, when window resizes, css get applied and after getting window width, components get rearranged following css change.
I have this in constructor:
this.myInput = React.createRef();
and this:
componentDidMount () {
this.setState({
myWidth: this.state.myWidth=this.myInput.current.offsetWidth
});
}
and this in Render():
render(){
const btnText = this.state.erase ? "Populate" : "Erase" ;
const handleClick = e => this.fullScreen(e.target.id);
const EditorHead1 = <EditorHead id={"item1"} style={this.state.stilEditor} className={this.state.headEdKlasa} onClick={handleClick} title={this.state.attr}/>;
const PreviewHead1 = <PreviewHead id={"item2"} style={this.state.stilPreview} className={this.state.headViewKlasa} onClick={handleClick} title={this.state.attr}/>;
const BtnEraser1 = <BtnEraser id={"eraser"} onClick={this.eraseFields} type={"button"} className={"btn btn-danger btn-lg"} title={"Erase & populate both fields"} value={btnText}/>;
const Editor1 = <Editor id={"editor"} onChange={this.handleChange} className={this.state.editorKlasa} value={this.state.markdown} placeholder={"Enter ... some kind a text!? ..."} title={"This is rather obvious isnt it? Its editor window Sherlock :D"}/>;
const Preview1 = <Preview id={"preview"} className={this.state.previewKlasa} dangerouslySetInnerHTML={{__html: marked(this.state.markdown, { renderer: renderer })}} title={"Its a preview window, Sherlock ;)"}/>;
const Arrow1 = <Arrow id={"arrow"}/>;
if(this.state.myWidth<=768){
alert("Alternative");
alert(this.state.myWidth);
return (
<div id="inner2" ref={this.myInput} className="grid-container animated zoomIn" style={{height: this.state.inner2H}} onDoubleClick={this.inner2Height}>
{EditorHead1}
{Editor1}
{PreviewHead1}
{Preview1}
{BtnEraser1}
{Arrow1}
</div>
);
}
if(this.state.myWidth>768){
alert("Normal");
alert(this.state.myWidth);
return (
<div id="inner2" ref={this.myInput} className="grid-container animated zoomIn" style={{height: this.state.inner2H}} onDoubleClick={this.inner2Height}>
{EditorHead1}
{PreviewHead1}
{BtnEraser1}
{Editor1}
{Preview1}
{Arrow1}
</div>
);
}
}
Currently rearranging only works if you, after resize, refresh browser or “run” again codepen.
resize event should be listened in order to keep track of element width changes. It's preferable to debounce event handlers for events that can be fired often, which resize is:
import debounce from 'lodash.debounce';
...
myInput = React.createRef();
setMyWidth = () => {
this.setState({
myWidth: this.myInput.current.offsetWidth
});
}
onResize = debounce(this.setMyWidth, 100);
componentDidMount() {
this.setMyWidth();
window.addEventListener('resize', this.onResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
Depending on what element offsetWidth is, element reference may be redundant, document.body.clientWidth can be tracked instead.
Also, this.state.myWidth=... is a mistake, this.state shouldn't be changed directly outside component constructor.

Resources