React: Get element size in componentDidMount - reactjs

I'm trying to implement the solution provided in this answer.
In the second step, I need to get the size of a div defined in my component in my component's componentDidMount. Many threads on StackOverflow have proposed using refs for this purpose. However, I have difficulty understanding how to implement it. Would you please give me a piece of code as an example to learn how to get the element's size after it is mounted in componentDidMount?

You can create a ref with createRef and store it in an instance variable which you pass to the ref prop of the element you want a reference to.
This ref will then have a reference to the node in the current property after the component has mounted.
Example
class App extends React.Component {
ref = React.createRef();
componentDidMount() {
const { current } = this.ref;
console.log(`${current.offsetWidth}, ${current.offsetHeight}`);
}
render() {
return <div ref={this.ref} style={{ width: 200, height: 200 }} />;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>

By creating a ref you have access to all element's properties like offsetWidth.
https://developer.mozilla.org/en-US/docs/Web/API/Element
class TodoApp extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [
{ text: "Learn JavaScript" },
{ text: "Learn React" },
{ text: "Play around in JSFiddle" },
{ text: "Build something awesome" }
]
}
this.myRef = React.createRef();
}
componentDidMount() {
console.log(this.myRef.current.offsetWidth);
}
render() {
return (
<div ref={this.myRef}>
{this.state.items.map((item, key) => (
<div className="rootContainer" key={key}>
<div className="item">{item.text}</div>
</div>
))}
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
.rootContainer {
display: inline-block;
}
.item {
display: inline-block;
width: auto;
height: 100px;
background: red;
border: 1px solid green;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="app"></div>

Related

Apply CSS transition to styled-component when React state changes

I have some state that changes after button click. The state changes the size of a sidebar.
Here is my CSS made with styled-components and conditional rendering:
const SidebarStyled = styled.div`
width: ${this.state.sidebarOpen ? '200px' : '70px'};
position: fixed;
left: 0px;
top: 0px;
height: 100vh;
background-color: #0c1635;
display: flex;
flex-direction: column;
`;
Any idea how I can apply a transition 0.2s on the conditional rendering?
I have tried to add transition: all 0.2s ease-in-out; and it didn't work.
As I mentioned in my comment, you need to pass in a prop and interpolate it out to change your CSS. Otherwise your component will re-render, and the CSS transition won't be applied.
const { React, ReactDOM, styled } = window;
class WontWork extends React.Component {
constructor(props) {
super(props);
this.state = { sidebarOpen: false };
}
render() {
const Sidebar = styled.div`
width: ${this.state.sidebarOpen ? "200px" : "70px"};
height: 20px;
background: red;
transition: width 1s;
`;
return (
<main>
<p>This won't work:</p>
<Sidebar />
<button
onClick={() => this.setState({ sidebarOpen: !this.state.sidebarOpen })}
>
Expand
</button>
</main>
);
}
}
const WorkingSidebar = styled.div`
width: ${(props) => (props.open ? "200px" : "70px")};
height: 20px;
background: green;
transition: width 1s;
`;
class WillWork extends React.Component {
constructor(props) {
super(props);
this.state = { sidebarOpen: false };
}
render() {
return (
<main>
<p>You need to pass in a <b>prop</b> to a predefined styled-component:</p>
<WorkingSidebar open={this.state.sidebarOpen} />
<button
onClick={() => this.setState({ sidebarOpen: !this.state.sidebarOpen })}
>
Expand
</button>
</main>
);
}
}
ReactDOM.render(
<div>
<WontWork />
<hr />
<WillWork />
</div>,
document.getElementById("app")
);
<script crossorigin src="https://unpkg.com/react#16.14.0/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16.14.0/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/react-is#16.13.1/umd/react-is.production.min.js"></script>
<script crossorigin src="https://unpkg.com/styled-components#5.3.3/dist/styled-components.min.js"></script>
<div id="app"></div>
Try this:
You can passe sidebarOpen as props:
<SidebarStyle sidebarOpen = {this.state.sidebarOpen}>
Then:
const sidebarStyle = styled.div`
width: 70px;
transition: transform .2s ease-in-out;
transform: ${props.sidebarOpen ? "scaleX(3)" : "scaleX(1)"};
.
.
.
`
I helped from here:
See here: Adding transitions to styled components
Does it work In this case?
Add a transition property like: transition:all 200ms ease-in;

Animate width of a div in React

I have a div whose content changes. Whenever the content changes, the div should animate its width. I tried the following and it does not work. Any pointers will be helpful.
class App extends React.Component {
constructor() {
super();
this.state = {
text: 'Hello world'
}
}
changeText() {
this.setState({
text: 'Very long text stretching across screen'
});
}
render() {
return <div>
<div className="text">{this.state.text}</div>
<button onClick={this.changeText.bind(this)}>Change</button>
</div>;
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
.text {
display: inline-block;
background-color: cyan;
transition: width 2000s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I could do it like this:
class App extends React.Component {
constructor() {
super();
this.state = {
text: 'Hello world'
}
this.myRef = React.createRef();
}
changeText() {
this.setState({
text: 'Very long text stretching across screen'
});
this.myRef.current.style.width = '250px';
}
render() {
return <div>
<div ref={this.myRef} className="text">{this.state.text}</div>
<button onClick={this.changeText.bind(this)}>Change</button>
</div>;
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
.text {
display: inline-block;
background-color: cyan;
overflow: hidden;
height: 18px;
width: 80px;
transition: width 1.5s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can do this with your current code by specifying initial and final width and setting the style class dynamically based on button click.Please find code below:
https://codesandbox.io/s/fervent-dawn-32bfl
Also note that you have given the transition as "2000s" which is 2000 seconds :-)
Note: Marudhupandiyan's comment in your original question also provides a solution where the width is determined dynamically, which is a much more optimal solution if you don't know the width beforehand. You can integrate that logic into the code I have provided above, if required.

list jsx not re-rendered on state change

The below code snipped includes a clear explanation of the issues.
The explanation will appear if you run the code snipped by clicking the button
The code triggering the problem is
[0,1].map((i) => <Star src={this.state.src[i]} changeIcon={this.changeIcon} key={i} id={i} />)
{this.state.src[i]} does not trigger the re-rendering of the jsx when the state is updated
const host = 'https://s3.eu-central-1.amazonaws.com/moviedatabase1/'
const noStarIcon = host + 'no_star.png'
const StarIcon = host + 'star.png'
class Feedback extends React.Component {
constructor(props){
super(props);
this.state = { src: [noStarIcon, noStarIcon, noStarIcon], text: "" }
this.changeIcon = this.changeIcon.bind(this)
this.list = [0,1].map((i) => <Star src={this.state.src[i]} changeIcon={this.changeIcon} key={i} id={i} />)
}
changeIcon(){
this.setState({text: this.state.text + " state updated!"})
this.setState({src: [StarIcon, StarIcon, StarIcon]})
}
render() {
return (
<React.Fragment>
<p id="console">{this.state.text}</p>
<div class="box">{this.list}</div>
<div class="box box-blue flex-box">
<p>If I use the normal <b>jsx</b> tag, <b>setState</b> will trigger re-rendering of the <b>jsx</b> element</p> and the star will become yellow!
</div>
<div class="box">
<Star changeIcon={this.changeIcon} src={this.state.src[2]} key={2} id={2}/>
</div>
</React.Fragment>
);
}
}
class Star extends React.Component {
render() {
return <img src={this.props.src} onClick={this.props.changeIcon} style={this.props.style} id={this.props.id} />;
}
}
ReactDOM.render(
<Feedback />,
document.getElementById("react")
);
.box {
margin: 2vh 2vw;
color: white;
}
.box-blue {
border: solid 2px red;
background-color: blue;
border: solid 1px red;
margin: 2vh 2vw;
padding: 2vh 2vw;
color: white;
}
.flex-box {
display: flex;
flex-direction: column;
justify-content: space-around;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div class="box box-blue flex-box">
<p>The two stars below are multiple <b>Components</b> rendered using the Javascript <b>.map</b> function, which saves an array of two <b>Star</b> components.</p>
<p>If you click on this two stars, the function <b>changeIcon</b> is called and updates the <b>state</b>, but the <b>jsx</b> is not re-rendered</p>
</div>
<div id="react"></div>
<hr>
<p>Credits for the icons</p>
<div>Icons made by Smashicons from www.flaticon.com is licensed by CC 3.0 BY</div>
It is because you are setting up your this.list in your constructor. Calling this.setState will not call your constructor again so it won't update it. It only triggers lifecycle events (such as render). See below.
const host = 'https://s3.eu-central-1.amazonaws.com/moviedatabase1/'
const noStarIcon = host + 'no_star.png'
const StarIcon = host + 'star.png'
class Feedback extends React.Component {
constructor(props){
super(props);
this.state = { src: [noStarIcon, noStarIcon, noStarIcon], text: "" }
this.changeIcon = this.changeIcon.bind(this)
}
changeIcon(){
this.setState({text: this.state.text + " state updated!"})
this.setState({src: [StarIcon, StarIcon, StarIcon]})
}
render() {
return (
<React.Fragment>
<p id="console">{this.state.text}</p>
<div class="box">{[0,1].map((i) => <Star src={this.state.src[i]} changeIcon={this.changeIcon} key={i} id={i} />) }</div>
<div class="box box-blue flex-box">
<p>If I use the normal <b>jsx</b> tag, <b>setState</b> will trigger re-rendering of the <b>jsx</b> element</p> and the star will become yellow!
</div>
<div class="box">
<Star changeIcon={this.changeIcon} src={this.state.src[2]} key={2} id={2}/>
</div>
</React.Fragment>
);
}
}
class Star extends React.Component {
render() {
return <img src={this.props.src} onClick={this.props.changeIcon} style={this.props.style} id={this.props.id} />;
}
}
ReactDOM.render(
<Feedback />,
document.getElementById("react")
);
.box {
margin: 2vh 2vw;
color: white;
}
.box-blue {
border: solid 2px red;
background-color: blue;
border: solid 1px red;
margin: 2vh 2vw;
padding: 2vh 2vw;
color: white;
}
.flex-box {
display: flex;
flex-direction: column;
justify-content: space-around;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div class="box box-blue flex-box">
<p>The two stars below are multiple <b>Components</b> rendered using the Javascript <b>.map</b> function, which saves an array of two <b>Star</b> components.</p>
<p>If you click on this two stars, the function <b>changeIcon</b> is called and updates the <b>state</b>, but the <b>jsx</b> is not re-rendered</p>
</div>
<div id="react"></div>
<hr>
<p>Credits for the icons</p>
<div>Icons made by Smashicons from www.flaticon.com is licensed by CC 3.0 BY</div>

React sharable panel url

Consider my following snippet. Right now on button click it opens a div-three that loads AnotherComponent.The url is simply 'http://localhost:3000/de' i.e. Indexroot
What I want to achieve is: If I hit 'http://localhost:3000/de/?open' then I want the panel i.e. div-three already open.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showThird: false
}
this.showDivThree = this.showDivThree.bind(this)
/*if(props.location.search=="?open"){
this.showDivThree()
}*/
}
showDivThree() {
this.setState(prevState => ({ showSecond: false, showThird: !prevState.showThird}))
console.log(this.state)
}
render() {
return (
<div className={'wrapper' + ( this.state.showThird ? ' show' : '')}>
<div className="one">one
{/* Show third */}
<div>
<button onClick={this.showDivThree}>{this.state.showThird ? 'hideThird' : 'showThird'}</button>
</div>
</div>
<div className="three">three
<div>
<button onClick={this.showDivThree}>{this.state.showThird ? 'hideThird' : 'showThird'}</button>
<AnotherComponent />
</div>
</div>
</div>
)
}
}
class AnotherComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
<h4>Another component</h4>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
.wrapper {
overflow: hidden;
white-space: nowrap;
}
.one, .two, .three {
background: #333;
border: 2px solid #787567;
box-sizing: border-box;
color: #fff;
display: inline-block;
font-family: arial;
overflow: hidden;
padding: 20px;
text-align: center;
transition: border 0.2s, padding 0.2s, width 0.2s;
min-height: 50vh;
}
.one {
width: 100%;
}
.two {
border-width: 2px 0;
padding: 20px 0;
width: 0;
}
.three {
border-width: 2px 0;
padding: 20px 0;
width: 0;
}
.show .one, .show .two, .show .three {
border-width: 2px;
padding: 20px;
width: 50%;
}
<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>
<script src="https://unpkg.com/react-router/umd/react-router.min.js"></script>
<div id="root"></div>
I have commented a code where I read search string from props.location, if it is present then I simply call the function that opens the div-three. But as I have mixed conditions to open divs it somehow is not working.
How can I fix this?
You can't call setState (showDivThree method calls setState) in contructor since when constructor is called component hasn't been mounted yet. Please check this SO answer for more details.
You should move if statement checking URL search string from constructor to componentDidMount method which is called immediately after a component is mounted and in which you can safely use setState:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
showThird: false
};
this.showDivThree = this.showDivThree.bind(this)
}
componentDidMount() {
if (props.location.search == "?open") {
this.showDivThree();
}
}
...
}
Besides, I think that your URL should be without slash before search query. So it should be http://localhost:3000/de?open instead of http://localhost:3000/de/?open.

React - componentWillReceiveProps method: new vs updated instance

I am new to React and programming in general and have a question regarding componentWillReceiveProps method. Inside the LikesComponent class there is updateLikes method that does ReactDOM.render(<LikesComponent likes={this.props.likes+1} />, document.getElementById("app")). I thought that every time <ComponentName /> is called a new instance is going to be created, that's why I was very surprised when I saw a message logged by componentWillReceiveProps method. Instead, I was expecting to see messages logged by getDefaultProps, getInitialState and render methods. So, doesn't calling <ComponentName /> always create a new instance of that class? Could you please elaborate on when a new instance is created and when the instance is just updated? I am quite confused now. Thanks in advance.
body {
padding: 40px;
font-family: "helvetica neue", sans-serif;
}
.container {
width: 600px;
margin: auto;
color: black;
padding: 20px;
text-align: center;
}
.container h1 {
margin: 0;
padding: 20px;
font-size: 36px;
}
.container .btn {
border: 0;
padding: 15px;
margin: 10px;
width: 20%;
font-size: 15px;
outline: none;
border-radius: 3px;
color: #FFF;
font-weight: bold;
}
.btn.blue-btn {
background-color: #55acee;
box-shadow: 0px 5px 0px 0px #3C93D5;
}
.btn.blue-btn:hover {
background-color: #6FC6FF;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React tutorial</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
var LikesComponent = React.createClass({
updateLikes: function() {
ReactDOM.render(
<LikesComponent likes={this.props.likes+1}/>,
document.getElementById("app")
)
},
getDefaultProps: function() {
console.log("getDefaultProps");
return({
likes: 0
})
},
getInitialState: function() {
console.log("getInitialState");
return({
popular: false
})
},
componentWillReceiveProps: function(nextProps) {
console.log(nextProps);
console.log("Componentwillreceiveprops");
this.setState({
popular: nextProps.likes >= 10
})
},
render: function() {
console.log("Component Rendered");
return (
<div className="container">
<h1>{this.state.popular ? "I'm popular" : null}</h1>
<button className="btn blue-btn" onClick={this.updateLikes}>Likes: {this.props.likes}</button>
</div>
)
}
});
ReactDOM.render(
<LikesComponent />,
document.getElementById("app")
);
</script>
</body>
</html>
All <LikesComponent likes={5} /> does it create an object that means "we want to render a LikesComponent instance here with these props (likes=5)".
React will compare this new set of things to render with the set of things it has already rendered and think "hey I am already rendering a LikesComponent with (likes=4)".
Since the component type is the same and it is in the same place in the hierarchy, React will just update the instance with the new prop values instead of destroying the old instance and creating a new instance.
So basically:
New instances are created (mounted) when a component is rendered in a location in the hierarchy where an instance of that component currently does not exist.
Instances are reused when a component is rendered in a location in the hierarchy where an instance of the component already exists.
Instances are destroyed (unmounted) when, on a new render, a component of that type is not rendered to the location where the instance exists.

Resources