React Simple Re-Rendering - setState is not re-rendering? - reactjs

I know there are a few questions out there exactly like this, but believe me none of them solved this problem. The main difference is that im tryng to update a nested component.
import React from 'react';
import Events from './Events.js'
import EventType from '../events/EventType.js'
import PropertiesPanelStyle from './css/PropertiesPanel.css';
import JointProperties from './properties/JointProperties.jsx';
export default class extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "Properties",
visible: false,
display: undefined
}
Events.on(EventType.JOINT_CLICK, this.onJointClick.bind(this));
}
onJointClick(clickedJoint) {
console.log("CLICKED ",clickedJoint);
this.setState({
display: <JointProperties joint={clickedJoint}/>,
visible: true
});
}
render () {
if(!this.state.visible)
return (<div></div>);
return (<div>
<div id="properties">
<strong>{this.state.name}</strong>
<div id="propertiescontent" key={1}>
{ this.state.display }
</div>
</div>
</div>);
}
}
Whenever i click, i get a new joint as a parameter, and set the display to be the JointProperties to display a that joint. Whenever i do the first click, it displays correctly, but whenever i do the second click, it still displays the first joint (and the constructor of the JointProperties component is not called again)
Depending on what i click i will get different rendering, at least that was my initial idea. But idk if what am i missing here :(

Related

React-toastify notification won't return in conditional

The notification works quite easily with a button. However, I'm trying to have it activate when a props passes through (true/false).
Basically, the user clicks on this tab, if they're not signed in, it'll pop up with the notification telling them to sign in.
However, I cannot make it work without being a button. The props passess through just fine, and I can console.log it. And the conditional returns... something, but it's like an odd bunch of letters, and each refresh it changes. And does not pop up like a notification. It's just obscure letters in the middle of the screen (because of {notify} placed above the form).
import React, {Component} from 'react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
class Alerts extends Component {
constructor(props){
super(props)
this.state = {
alertLogin: ''
}
}
render() {
const notify = () => toast("Please login before adding a recipe!");
// tried to make a conditional to check if prop alertLogin is true or false
// then calls notify function if false
if (!this.props.alertLogin) {
console.log('alert props received', this.props.alertLogin)
return notify()
}
return (
<div>
{/* <button onClick={notify}>Notify !</button> */}
{notify}
<ToastContainer />
</div>
);
}
}
export default Alerts;
import React, { Component } from "react";
import "./styles.css";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
class Alerts extends Component {
constructor(props){
super(props)
this.state = {
alertLogin: ''
}
}
componentDidMount() {
// tried to make a conditional to check if prop alertLogin is true or false
// then calls notify function if false
if (!this.props.alertLogin) {
console.log("alert props received", this.props.alertLogin);
this.notify();
}
}
notify = () => toast("Please login before adding a recipe!");
render() {
return (
<div>
<button onClick={(e) => this.notify()}>Notify !</button>
<ToastContainer />
</div>
);
}
}
export default Alerts;
Codepen for the solution
First you take the if statement with the function and put it in componentDidMount
cause i'm guessing is stopping the rendered elements themselves from rendering
second make the toast function accessible by component did mount and the button by declaring it before the render function hope i was clear enough

Reusable react component with Canvas doesn't render canvas with its props

I am trying to create a reusable stateful component (it shouldn't be functional component). I need to add this component in runtime so in my App i have an array of the component (CanvasComponent) in my state to render the list of component. I also generate a random size to render the size of canvas. The problem occurs when I create second canvas, Weirdly it is only render once.
I have this problem in ChartJS and since my code base is very big I decided to simplify it by a sample.
However if you uncomment CanvasComponent in the Array it works perfectly fine.
import React from 'react';
import logo from './logo.svg';
import './App.css';
import CanvasComponent from './CanvasComponent';
class App extends React.Component {
state = {
canvasList: [
// <CanvasComponent size={30}></CanvasComponent>,
// <CanvasComponent size={50}></CanvasComponent>
]
}
handleClick = () => {
const size = Math.floor(Math.random() * (100 - 50 + 1) + 50);
const newCanvas = <CanvasComponent size={size}></CanvasComponent>
this.setState({
canvasList: [newCanvas,
...this.state.canvasList]
})
}
render() {
return (
<div className="App">
<button onClick={this.handleClick}>Add canvas</button>
{ this.state.canvasList.map((item, i) => {
return <CanvasComponent {...item.props} key={i}></CanvasComponent>
})}
</div>
);
}
}
export default App;
And the component
import React from 'react'
class CanvasComponent extends React.Component {
constructor(props) {
super(props);
this.myCanvas = React.createRef();
}
componentDidMount() {
const ctx = this.myCanvas.current.getContext('2d');
ctx.fillRect(0, 0, 100, 100);
}
render() {
console.log(this.props);
return (
<div>
<p>Size should be {this.props.size}</p>
<canvas ref={this.myCanvas} width={this.props.size} height={this.props.size} />
</div>
)
}
}
export default CanvasComponent
I believe that your issue here is that you're rendering the canvas components programmatically. If something was not present when the page first loaded, then event listeners are not actively looking for it.
I'm sure there's a more elegant solution than mine, but I tend to get around this issue by writing something like.
state={ updated: false}
componentDidMount(){
this.setState({updated:true})
}
Updating the state forces a rerender, and the event listeners will know to pay attention to the relevant component.
The issue was here, I will share here in case someone have had same issue, can find it.
Instead of
this.setState({
canvasList: [newCanvas,
...this.state.canvasList]
})
You should write
this.setState({
canvasList: [...this.state.canvasList,
newCanvas]
})
I still don't know why, but it fixed the problem.

React with Redux Popup or Modal syntax and setup

I have a MonthContainer component that renders a MonthView component. The container passes data to the view and the view does the formatting and displays a table with link buttons corresponding to month and category.
What I want to do is when a link is clicked in MonthView, a pop up displays on the page with another set of data that is based on the year, category and month for the link that was clicked. I am using react-modal to accomplish this.
In my initial setup, MonthViewContainer was rendering MonthView and PopupContainer which in turn rendered PopupView. PopupContainer was passed a full list of transaction data (unfiltered) which it then passed to PopupView. When I clicked on a link in MonthView, it would set the displayModal flag to true and my PopupView component which was wrapped in react-modal would show up with the transactions filtered based on year, month, category. This worked fine except for my challenges with updating the parent component after saving and closing the modal. However, I don't like the idea of loading all my state into the PopupView and then filtering. Ideally, I would want to get the data when the PopView loads. I'm having trouble doing this.
I have several issues with my setup. Below is my code with comments in each section I'm having trouble with.
MonthViewContainer
import React from 'react';
import MonthView from './MonthView.js';
import { connect } from 'react-redux';
import { getTransactions } from '../actions/actions.js';
import TransactionsPopupContainer from './TransactionsPopupContainer.js';
MonthViewContainer Component
class MonthViewContainer extends React.Component {
constructor() {
super();
this.popupCategory;
this.popupMonth;
this.state = {
detailPopup : false
}
this.handleGetDetail = this.handleGetDetail.bind(this);
this.handleRefresh = this.handleRefresh.bind(this);
}
componentDidMount() {
this.props.getTransactions(2016);
// this.props.getTransactionsAll(2016);
}
render() {
return (
<div className="row">
<div className="col-md-12">
<MonthView
transactions={this.props.storeTransactions.transactions}
selectedYear={this.props.storeTransactions.selectedYear}
onHandleGetDetail={this.handleGetDetail}
/>
</div>
<div>
<PopupContainer
modalActive={this.state.detailPopup}
selectedYear={this.props.storeTransactions.selectedYear}
category={this.popupCategory}
month={this.popupMonth}
onRefresh={this.handleRefresh}
/>
</div>
</div>
)
}
handleRefresh() {
console.log("handle refresh entered")
this.props.getTransactions(this.props.storeTransactions.selectedYear);
}
handleGetDetail(year,category,month) {
this.popupCategory = category;
this.popupMonth = month;
this.setState({ detailPopup: true}, function () {});
}
}
const mapStateToProps = (state) => ({
storeTransactions: state.storeTransactions
});
export default connect(mapStateToProps, {getTransactions})(MonthViewContainer);
PopupContainer
import React from 'react';
import { connect } from 'react-redux';
import PopupView from './PopupView.js';
import { getPopupTransactions } from '../actions/actions.js';
import { saveTransactions } from '../actions/actions.js';
class PopupContainer extends React.Component {
constructor() {
super();
this.editedTrans = undefined;
this.handleSave = this.handleSave.bind(this);
}
componentWillUnmount(){
//when modalActive is true, I would like to get the popup data with params coming from props
//doing something like getPopupTransactions
//the problem is I can't do it here because the component is mounted when parent loads and
//is set to active/visible when a button is clicked on the parent
}
handleSave(transToSave) {
this.props.saveTransactions(transToSave);//use the action in redux store to save these transactions
//refresh the parent (MonthViewContainer/MonthView) after saving //not sure how to do this after closing the modal
//I would like the transactions that are saved after closing the modal to be reflected in the parent component
//what I attempted is to pass in a handler what will trigger set state and case the MonthViewContainer to rerender
this.props.handleRefresh();
}
render() {
return (
<PopupView
modalActive={this.props.modalActive}
transactions={this.props.storePopupTransactions.popuptransactions}
savePopupView={this.handleSave}
/>
);
}
}
const mapStateToProps = (state) => {
return {storePopupTransactions: state.storePopupTransactions//,
}
};
export default connect(mapStateToProps, {getPopupTransactions,saveTransactions})(PopupContainer);
Been working on it and it turns out that I was indeed able to call my parent component (MonthViewContainer) after calling closeModal and before setting the modalIsOpen to false for the react-modal component (PopupView in my case). Therefore, I was able to save the data from PopupView and then refresh my state in MonthViewContainer.

Getting React error: "setState(...): Can only update a mounted or mounting component" ONLY with Child Component

I've been chasing this bug all day. I have a dead-simple React entry point, and a dead-simple component state change example component. If I put the component into the entry point, like this:
import React from 'react';
import { render } from 'react-dom';
export default class Template extends React.Component {
constructor(props) {
super(props);
this.toggleNavbar = this.toggleNavbar.bind(this);
this.state = {
collapsed: true,
};
}
toggleNavbar() {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
return (
<div>
<p>Collapsed: { this.state.collapsed ? 'true' : 'false' }</p>
<button onClick={this.toggleNavbar}>Toggle</button>
</div>
);
}
}
render(
<Template />,
document.querySelector('#react-app'),
);
It works as expected. You click the toggle button, and the text changes back and forth between 'true' and 'false'. However, the minute I break it out into two separate files, giving me this for the entry point:
import React from 'react';
import { render } from 'react-dom';
import Template from './components/Template';
render(
<Template />,
document.querySelector('#react-app'),
);
and this for Template.jsx
import React from 'react';
export default class Template extends React.Component {
constructor(props) {
super(props);
this.toggleNavbar = this.toggleNavbar.bind(this);
this.state = {
collapsed: true,
};
}
toggleNavbar() {
this.setState({
collapsed: !this.state.collapsed,
});
}
render() {
return (
<div>
<p>Collapsed: { this.state.collapsed ? 'true' : 'false' }</p>
<button onClick={this.toggleNavbar}>Toggle</button>
</div>
);
}
}
Any time I click on the button I get the following error in the console:
build.js:23484 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Template component.
... I've checked all of the other Stack Overflow answers for that error (and also searched around a ton), and none of them seem to be applicable here. Anyone have any idea what I'm doing wrong?
side note: I've tried adding:
componentWillUnmount() {
this.isUnmounted = true;
}
and a !this.isUnmounted check before setState() and I still get the error.
Thanks!
I found the issue: my .babelrc contained this line:
"plugins": ["react-hot-loader/babel"]
And it was conflicting with the rest of my webpack hot-reloading setup. Removing that line did the trick. I think what was happening is that the component was getting rendered, but somehow react was getting confused about what was/wasn't mounted (maybe it was very quickly getting mounted, unmounted, and then re-mounted, and so the bound toggleClick function was trying to set state on an old version of the component? Unsure).
Anyway, the moral of the story is: the React code is fine. It was a problem with my config.

Unable to change state

Expected behaviour: The app should display John at first and then I change the state (name) to George (setTimeout) so that should be displayed.The state doesn't seem to change though.Any ideas?
import React, { Component } from 'react';
class App extends Component {
constructor(props){
super(props);
this.state={name:"John"};
}
render() {
setTimeout(() => {
this.setState=({name:"George"})
}, 2000)
return (
<div>
{this.state.name}
</div>
);
}
}
export default App;
In comments you got the way to make your code working. But I would like to add an answer here with the message that it is not good idea to change the state of your component in the render() function.
As when your state changes render() function will be called, which will call setTimeout, it will again change the state ... so you will go into infinite loop of rendering your component.
The right way of during this is to have you setTimeout in the componentDidMount() function of your component.

Resources