setState in react can only update a mounted or mounting component - reactjs

I'm getting an error stating that setstate can only be update on mounted or mounting component. I actually want to toggle display of some content. Please help me out. This is the code.
I want to toggle the boolean "show" to true or false. if true, it should show Todolist, if false, it should not show Todolist, instead it should show Footer. Pardon me for framing the question badly. But Please tell me what to do and how to do. Thank you!
import React, {Component} from "react";
import ReactDOM from "react-dom";
import "./index.css";
import TodoList from "./TodoList";
import NavbarTodo from "./NavbarTodo";
import Footer from "./Footer";
import "bootstrap/dist/css/bootstrap.css";
var destination = document.querySelector("#container");
var navdest = document.querySelector(".putnavbarhere");
var clicked=false;
class index extends Component{
constructor() {
super();
this.state = {show: true};
this.handleAbout = this.handleAbout.bind(this);
}
handleAbout(){
this.setState({
show:false
})
clicked=!clicked
if(clicked){
ReactDOM.render(
<Footer />,
document.querySelector("#toast")
)
}
else{
ReactDOM.render(
<p> </p>,
document.querySelector("#toast")
)
};
}
}
var indObj = new index();
ReactDOM.render(
<div>
<TodoList show={indObj.state.show}/>
</div>,
destination
);
ReactDOM.render(
<NavbarTodo clicked={indObj.handleAbout}/>,
navdest
);

What you need is some conditional rendering.
https://reactjs.org/docs/conditional-rendering.html
Your onClick function will toggle this.state.show between true/false.
class Index extends Component {
constructor () {
super()
this.state = {show: true}
}
handleClick () {
this.setState({show: !this.state.show})
}
render () {
return (
<div>
{this.state.show &&
<Todolist component here>
}
{!this.state.show &&
<Footer component here>
}
</div>
)
}
}

Related

How to call functional child component method from class parent component

I have a class based parent component like below
import React from "react";
import ReactDOM from "react-dom";
import FunChild from "./FunChild";
class App extends React.Component {
constructor(props) {
super(props);
this.childRef = React.createRef();
this.parentmethodFun = this.parentmethodFun.bind(this);
}
parentmethodFun() {
this.childRef.current.childmethod();
}
render() {
return (
<div>
<FunChild />
<button type="button" onClick={this.parentmethodFun}>
function
</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("container"));
the funChild.js file
import React from "react";
function FunChild(props) {
childmethod() {
console.log("child method is called");
}
return (<div>This is child ...!</div>);
}
export default FunChild;
if that child was a class component I can very easily use ref={this.childRef} to access child method.
But, it's a functional component and It was giving lot of problems. Can anyone please help me on this.
reference project link https://codesandbox.io/s/react-playground-forked-74xzn?file=/index.js
You should avoid this kind of relation because it is not the way how react works. In React you should pass everything from up to bottom. But if you reale want to achieve something like this you can use reference forwarding and imperative handler hook. E.g:
import { Component, forwardRef, createRef, useImperativeHandle } from "react";
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
childMethod() {
console.log("child method is called");
}
}));
return <div>This is child ...!</div>;
});
class App extends Component {
constructor(props) {
super(props);
this.childRef = createRef();
this.parentmethodFun = this.parentmethodFun.bind(this);
}
parentmethodFun() {
this.childRef.childMethod();
}
render() {
return (
<div>
<Child ref={(ref) => (this.childRef = ref)} />
<button type="button" onClick={this.parentmethodFun}>
function
</button>
</div>
);
}
}
Personally, i think you should rethink your application structure as it is very likely that there is a better solution than this trick.

React JSX with component in variable does not sync props

The Scegli component is assigned to the App comp variable and then rendered in App render as a variable. However, the props assigned don't work: if I type in the input box, the value is frozen.
What am I doing wrong?
If I just move the <Scegli>...</Scegli> directly into the render (without assigning to a variable) it works as expected.
App.js
import React, { Component } from 'react';
import './App.css';
import Scegli from './components/Scegli';
class App extends Component {
constructor() {
super();
this.state = {
valore: 'Single'
}
this.comp = <Scegli value={this.state.valore} handleChange={this.setValoreHandler} />;
}
setValoreHandler = e => {
this.setState({
valore: e.target.value
});
}
render() {
return (
<div>
{this.comp}
</div>
);
}
}
export default App;
Scegli.js
import React, { Component } from 'react';
class Scegli extends Component {
render() {
return (
<div>
<input value={this.props.value} onChange={this.props.handleChange} />
Valore scelto: {this.props.value}
</div>
);
}
}
export default Scegli;
this.comp is declared once, at the component's mount and stays in that state. You are not updating/re-rendering it anywhere, that's why it remains unchanged.
You could either:
move the JSX component directly to render:
<div>
<Scegli value={this.state.valore} handleChange={this.setValoreHandler} />
</div>
or
update the class variable with every input change (not recommended though):
setValoreHandler = (e) => {
this.comp = <Scegli value={e.target.value} handleChange={this.setValoreHandler} />;
this.forceUpdate();
}
(I could be wrong) but when you define this.comp in the constructor() it is only loaded once with the default state. The constructor() is not called on re-render (similar to componentWillMount()). So that is why it is frozen as the updated state is never sent this.comp
Instead of this.comp in render do
return (
<div>
<Scegli value={this.state.valore} handleChange={this.setValoreHandler}/>
</div>
);

Which is the react way of complex conditional rendering?

Issue
I have a problem with conditional rendering of a component. As far as I can see, there are 2 approaches to doing this. First approach is ugly as it becomes difficult when I have to do multiple && conditions. The second way is clear, but it adds the component itself to the state and further computations with the state value is difficult. E.g checking what is the message value for error.
I have given both the approaches below. Please let me know which would be better. Is there a another approach than both of them?
Application
This is a simple application that renders either 'Main' component or 'Err' component, based on the state of 'err' attribute in first approach and content of the comp attribute in second approach.
Initially Main component is rendered. The err attribute is updated to some value after 2 seconds, which triggers rerendering. At this time, I want Err component to render.
The real application is I have an external api call on componentDidMount and it can either fail or succeed. I have to display different components based on result. It is a little more complicated with multiple state values being updated. I have simplified the issue below for the purpose of demonstration.
Common steps for both types
npx create-react-app react-oop
component/Err.js
import React,{Component} from 'react'
class Err extends Component {
render(){
return(
<div>
Error Component
</div>
)
}
}
export default Err
component/Main.js
import React, {Component} from 'react'
class Main extends Component {
render(){
return(
<div>
Main Component
</div>
)
}
}
export default Main
First approach
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Err from './components/Err'
import Main from './components/Main'
class App extends Component {
constructor(props){
super(props)
this.state = {
err: null
}
this.setError = this.setError.bind(this)
}
setError(){
return(
this.setState(() => {
return({
err: 'Error'
})
})
)
}
componentDidMount(){
setTimeout(this.setError, 2000)
}
render() {
return (
<div className="App">
{
this.state.err ? <Err /> : <Main />
}
</div>
);
}
}
export default App;
Second approach
App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Err from './components/Err'
import Main from './components/Main'
class App extends Component {
constructor(props){
super(props)
this.state = {
comp: <Main />
}
this.setError = this.setError.bind(this)
}
setError(){
return(
this.setState(() => {
return({
comp: <Err />
})
})
)
}
componentDidMount(){
setTimeout(this.setError, 2000)
}
render() {
return (
<div className="App">
{this.state.comp}
</div>
);
}
}
export default App;
I definitely recommend the 1st approach. Store data (json), not views (jsx) in your component's state.
Actually there is a 3rd approach that takes the best of both:
use a jsx variable to edit the view (with your logic) before rendering
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import Err from './components/Err';
import Main from './components/Main';
class App extends Component {
constructor(props){
super(props);
this.state = {
err: null
};
}
// This way of writing functions saves you the binding
setError = () => this.setState({err: 'Error'})
componentDidMount(){
setTimeout(this.setError, 2000);
}
render() {
let comp = <Main />;
// Put your logic here so your returned JSX is clear
if (this.state.err)
comp = <Err />;
return (
<div className="App">
{comp}
</div>
);
}
}
export default App;
Both approaches are essentially the same but I prefer option 1 as it's simpler to grasp. You can also use something like babel-plugin-jsx-control-statements#choose which makes the React component look simpler:
<Choose>
<When condition={ test1 }>
<Main />
</When>
<When condition={ test2 }>
<AnotherMain />
</When>
<Otherwise>
<Err />
</Otherwise>
</Choose>

Watching state from child component React with Material UI

New to React. Just using create-react-app and Material UI, nothing else.
Coming from an Angular background.
I cannot communicate from a sibling component to open the sidebar.
I'm separating each part into their own files.
I can get the open button in the Header to talk to the parent App, but cannot get the parent App to communicate with the child LeftSidebar.
Header Component
import React, {Component} from 'react';
import AppBar from 'material-ui/AppBar';
import IconButton from 'material-ui/IconButton';
import NavigationMenu from 'material-ui/svg-icons/navigation/menu';
class Header extends Component {
openLeftBar = () => {
// calls parent method
this.props.onOpenLeftBar();
}
render() {
return (
<AppBar iconElementLeft={
<IconButton onClick={this.openLeftBar}>
<NavigationMenu />
</IconButton>
}
/>
);
}
}
export default Header;
App Component -- receives event from Header, but unsure how to pass dynamic 'watcher' down to LeftSidebar Component
import React, { Component } from 'react';
import darkBaseTheme from 'material-ui/styles/baseThemes/darkBaseTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import RaisedButton from 'material-ui/RaisedButton';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
// components
import Header from './Header/Header';
import Body from './Body/Body';
import Footer from './Footer/Footer';
import LeftSidebar from './LeftSidebar/LeftSidebar';
class App extends Component {
constructor() {
super() // gives component context of this instead of parent this
this.state = {
leftBarOpen : false
}
}
notifyOpen = () => {
console.log('opened') // works
this.setState({leftBarOpen: true});
/*** need to pass down to child component and $watch somehow... ***/
}
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(darkBaseTheme)}>
<div className="App">
<Header onOpenLeftBar={this.notifyOpen} />
<Body />
<LeftSidebar listenForOpen={this.state.leftBarOpen} />
<Footer />
</div>
</MuiThemeProvider>
);
}
}
export default App;
LeftSidebar Component - cannot get it to listen to parent App component - Angular would use $scope.$watch or $onChanges
// LeftSidebar
import React, { Component } from 'react';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';
import NavigationClose from 'material-ui/svg-icons/navigation/close';
class LeftNavBar extends Component {
/** unsure if necessary here **/
constructor(props, state) {
super(props, state)
this.state = {
leftBarOpen : this.props.leftBarOpen
}
}
/** closing functionality works **/
close = () => {
this.setState({leftBarOpen: false});
}
render() {
return (
<Drawer open={this.state.leftBarOpen}>
<IconButton onClick={this.close}>
<NavigationClose />
</IconButton>
<MenuItem>Menu Item</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
</Drawer>
);
}
}
export default LeftSidebar;
Free your mind of concepts like "watchers". In React there is only state and props. When a component's state changes via this.setState(..) it will update all of its children in render.
Your code is suffering from a typical anti-pattern of duplicating state. If both the header and the sibling components want to access or update the same piece of state, then they belong in a common ancestor (App, in your case) and no where else.
(some stuff removed / renamed for brevity)
class App extends Component {
// don't need `constructor` can just apply initial state here
state = { leftBarOpen: false }
// probably want 'toggle', but for demo purposes, have two methods
open = () => {
this.setState({ leftBarOpen: true })
}
close = () => {
this.setState({ leftBarOpen: false })
}
render() {
return (
<div className="App">
<Header onOpenLeftBar={this.open} />
<LeftSidebar
closeLeftBar={this.close}
leftBarOpen={this.state.leftBarOpen}
/>
</div>
)
}
}
Now Header and LeftSidebar do not need to be classes at all, and simply react to props, and call prop functions.
const LeftSideBar = props => (
<Drawer open={props.leftBarOpen}>
<IconButton onClick={props.closeLeftBar}>
<NavigationClose />
</IconButton>
</Drawer>
)
Now anytime the state in App changes, no matter who initiated the change, your LeftSideBar will react appropriately since it only knows the most recent props
Once you set the leftBarOpen prop as internal state of LeftNavBar you can't modify it externally anymore as you only read the prop in the constructor which only run once when the component initialize it self.
You can use the componentWillReceiveProps life cycle method and update the state respectively when a new prop is received.
That being said, i don't think a Drawer should be responsible for being closed or opened, but should be responsible on how it looks or what it does when its closed or opened.
A drawer can't close or open it self, same as a light-Ball can't turn it self on or off but a switch / button can and should.
Here is a small example to illustrate my point:
const LightBall = ({ on }) => {
return (
<div>{`The light is ${on ? 'On' : 'Off'}`}</div>
);
}
const MySwitch = ({ onClick, on }) => {
return (
<button onClick={onClick}>{`Turn the light ${!on ? 'On' : 'Off'}`}</button>
)
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
lightOn: false
};
}
toggleLight = () => this.setState({ lightOn: !this.state.lightOn });
render() {
const { lightOn } = this.state;
return (
<div>
<MySwitch onClick={this.toggleLight} on={lightOn} />
<LightBall on={lightOn} />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<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>

React twice mount component, but on second time doesn't receive props

import React, {Component} from 'react';
import { Link } from 'react-router';
class AsideNav extends Component{
constructor(props, context) {
super(props, context);
this.state = {
navigation: ''
};
}
componentWillMount(){
console.log(this.props.data.navigation); // data from parent received
this.setState({
navigation: this.props.data.navigation.map(function(el, index){
return(
<Link key={index} className={"aside-nav__link " + (el.modifier ? ("aside-nav__link" + el.modifier) : '')} to={el.url}>{el.name}</Link>
)
})
})
}
render() {
console.log(this.state.navigation); // JSX for navigation is ready
return (
<nav className="aside-nav">
<span className="aside-nav__title" >Категории</span>
{this.state.navigation}
</nav>
);
}
}
export default AsideNav
After (seemingly) successful implementation of code it displays me an empty block and
Uncaught TypeError: Cannot read property 'navigation' of undefined at AsideNav.componentWillMount...
Changes:
1. Never store the ui items in state variable, always store data only and then generate the ui elements dynamically.
2. Use componentWillReceiveProps method, it will get called if you do any changes in props values in parent component, update the state value of child component at that time.
3. Since you just want to create the items from props data, directly use props, instead of storing anything.
Write it like this:
import React, {Component} from 'react';
import { Link } from 'react-router';
class AsideNav extends Component{
constructor(props, context) {
super(props, context);
this.state = { };
}
renderLinks(){
let data = this.props.data;
return data && Array.isArray(data.navigation) && data.navigation.map((el, index) => {
return(
<Link
key={index}
className={"aside-nav__link " + (el.modifier ? ("aside-nav__link" + el.modifier) : '')}
to={el.url}
>
{el.name}
</Link>
)
})
}
render() {
console.log(this.props.data)
return (
<nav className="aside-nav">
<span className="aside-nav__title" >Категории</span>
{this.renderLinks()}
</nav>
);
}
}
se componentWillReceiveProps(newProps) to get props second time as constructor and componentDidMount methods get executed only once. And also add check if data available or not.
import React, {Component} from 'react';
import { Link } from 'react-router';
class AsideNav extends Component{
constructor(props, context) {
super(props, context);
this.state = {
navigation: ''
};
}
componentWillReceiveProps(newProps)
{
console.log(newProps.data.navigation); // data from parent received
this.setState({
navigation: newProps.data &&newProps.data.navigation.map(function(el, index){
return(
<Link key={index} className={"aside-nav__link " + (el.modifier ? ("aside-nav__link" + el.modifier) : '')} to={el.url}>{el.name}</Link>
)
})
})
}
componentWillMount(){
console.log(this.props.data.navigation); // data from parent received
this.setState({
navigation: this.props.data && this.props.data.navigation.map(function(el, index){
return(
<Link key={index} className={"aside-nav__link " + (el.modifier ? ("aside-nav__link" + el.modifier) : '')} to={el.url}>{el.name}</Link>
)
})
})
}
render() {
console.log(this.state.navigation); // JSX for navigation is ready
return (
<nav className="aside-nav">
<span className="aside-nav__title" >Категории</span>
{this.state.navigation}
</nav>
);
}
}
export default AsideNav
import React from 'react';
import {render} from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
import App from './app';
import PageDashboard from './page-templates/page-dashboard';
import PageRating from './page-templates/page-rating';
import './styles/css/style.css';
//render(<PageDashboard />, document.querySelector('.main-wrapper')); - before
render(
<Router history={browserHistory}>
//<Route path="/public/" component={App} /> - before
<Route path="/public/" component={PageDashboard} /> // - now
<Route path="/public/rating" component={PageRating} />
</Router>, document.querySelector('.main-wrapper')
);
A problem located in root file 'main.js'. I accidentally have mounted two components with common children.

Resources