list jsx not re-rendered on state change - reactjs

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>

Related

React: Get element size in componentDidMount

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>

Why isn't this child react component rendering?

I'm trying to get a child component to render in react and it's not rendering. If I write the actual JSX composed in the child component in the parent, it renders, Why is that? The examples in the documentation show that this is possible, I don't know what I'm doing wrong.
Example here: https://jsfiddle.net/69z2wepo/309969/
class App extends React.Component {
render() {
return(
<Rectangle />
);
}
}
function Rectangle(){
return (
<div className="Rectangle">
<square />
</div>
);
}
function square(){
return (
<div className="square">
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
.Rectangle {
position: relative;
background-color: #222;
height: 760px;
width: 40px;
}
.square {
position: absolute;
background-color: green;
top: 0px;
left: 0px;
height: 20px;
width: 20px;
}
<!Doctype>
<html>
<head>
<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>
</head>
<body>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
</body>
The end result should show a dark grey rectangle, with a 20x20 pixel green square affixed to the top left of it.
Name your components starting with a capital letter.
function Square(){
return (
<div className="square">
</div>
);
}
function Rectangle(){
return (
<div className="Rectangle">
<Square />
</div>
);
}
If you are trying to render the child component "square" then change the name square to Square and also change the function name square to Square. Component name always needs to start with Capital Letter. Otherwise it will not render.
There are 2 issues here:
Components name should start with capital letters.
In your fiddle you are using the logoutBox className but you wrote a .square css class.
class App extends React.Component {
render() {
return (
<Rectangle />
);
}
}
function Rectangle() {
return (
<div className="Rectangle">
<Square />
</div>
);
}
function Square() {
return (
<div className="square">
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
.Rectangle {
position: relative;
background-color: #222;
height: 760px;
width: 40px;
}
.square {
position: absolute;
background-color: green;
top: 0px;
left: 0px;
height: 20px;
width: 20px;
}
<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>
<div id="root" />

How to open a particular accordion based click event in reactjs and autoclose remaining accordion?

I want to open a particular Section on clicking on that. How to do that by using click event? And also auto close the remaining accordion if I click on another accordion. Here is my code.
Accordion Component
import React, { Component } from 'react';
import Section from './section';
class Accordion extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
open: false,
headingClassName: 'accordion-heading',
className: 'accordion-content accordion-close',
Label: 'label-close',
icon: "+",
selectedItem: null,
};
}
handleClick = () => {
const open = this.state.open;
if (open) {
this.setState({
open: false,
className: "accordion-content accordion-close",
headingClassName: "accordion-heading",
Label: 'label-close',
icon: "+",
})
} else {
this.setState({
open: true,
className: "accordion-content accordion-open",
headingClassName: "accordion-heading clicked",
Label: 'label-open',
icon: "-",
})
}
}
render() {
return (
<div className="accordion-container">
<h1>Accordian Component</h1>
How to pass id as parameter in each section in onClick event to open
particular accordion and to autoclose remaining.
<Section>
<div className={this.state.headingClassName} onClick={this.handleClick} id="1">
<h3>One</h3> <label className={this.state.Label}>{this.state.icon}</label>
</div>
<div className={this.state.className}>
<p>This is paragraph</p>
</div>
</Section>
<Section>
<div className={this.state.headingClassName} onClick={this.handleClick} id="2">
<h3>Two</h3> <label className={this.state.Label}>{this.state.icon}</label>
</div>
<div className={this.state.className}>
<p>This is paragraph</p>
</div>
</Section>
<Section>
<div className={this.state.headingClassName} onClick={this.handleClick} id="3">
<h3>Three</h3> <label className={this.state.Label}>{this.state.icon}</label>
</div>
<div className={this.state.className}>
<p>This is paragraph</p>
</div>
</Section>
</div>
);
}
}
export default Accordion;
Section Component
import React, { Component } from 'react';
class Section extends Component {
render() {
return (
<div className="parent-accordion">
{this.props.children}
</div>
);
}
}
export default Section;
CSS
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
.accordion-container {
margin: auto;
width: 700px;
}
.accordion-container h1 {
color: #0000007a;
text-align: center;
font-family: sans-serif;
}
.parent-accordion {
width: 100%;
border: 1.5px solid #00000017;
}
.accordion-heading {
padding: 5px 5px;
background-color: #e2e2e254;
cursor: pointer;
text-transform: capitalize;
/* font-size: 17px; */
/* font-weight: 600; */
color: #2b2b41;
/* font-family: sans-serif; */
transition: background-color 1s;
}
.accordion-heading:hover {
background-color: #000000c7;
color: white;
First of all i've encompased a minimal (no css) example of how an accordion would behave on codesandbox, here.
This can be done multiple ways. In the example above the body of the tab is hidden with display:none if the tab is not active.Basically you iterate over your data in the render function and that's where you set your classes based on whatever flags you want (in your case isActive). You could render a Section for each tab and pass props to it.
The click handler updates your state with the id of the active tab.

Forward Parent onWheel to Scroll Table

I have an outer parent <div /> container that contains a <Table /> element. I'd like to be able to scroll the <Table /> on the onWheel event of the parent <div /> element, even if the mouse is not currently over the <Table />.
I have a ref to the table element and an event handler listening for the onWheel event but I haven't been able to figure out how to forward that event down to the table.
Because I guess that you want to scroll the table body, you can try with this.
class Table extends React.Component {
constructor() {
super();
this.callback = this.callback.bind(this);
this.body = null;
}
callback(ev) {
this.body.scrollTop += ev.deltaY;
}
render() {
return <div onWheel={this.callback}>
<table>
<tbody ref={c => this.body = c}>
{[1, 2, 3, 4, 5, 6,].map(i => <tr>
<td>{i}</td>
</tr>)}
</tbody>
</table>
</div>;
}
}
ReactDOM.render(<Table />, document.getElementById('root'));
tbody {
display: block;
height: 3rem;
overflow: scroll;
}
tr:nth-child(2n+1) {
background-color: #ddf;
}
tr:nth-child(2n) {
background-color: #eef;
}
table {
margin: auto;
border-collapse: collapse;
}
td {
width: 5rem;
text-align: center;
font-family: sans-serif;
color: #00f;
}
div {
width: 100%;
display: block;
text-align: center;
background-color: #99f;
}
<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>
<div id="root"></div>
I made a codePen illustrating a scroll redirection
This will listen wheelEvent on a parent <div>(the one with a red background), disable the default scrolling behavior (evt.preventDefault()) then set the scrollTop position of another <div>.
Here's the component code :
class RedirectScroll extends React.Component {
parentOnScroll = (evt) => {
evt.preventDefault();
const scrollTo= (evt.deltaY) + this.box.scrollTop;
this.box.scrollTop = scrollTo;
}
render() {
return (
<div className="parent" onWheel={this.parentOnScroll}> // Listen scrolls on this div
<div className="scrollablebox" ref={(box) => this.box = box}>
// Some content
</div>
</div>
);
}
}
I hope this is what you're looking for.

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.

Resources