setState Warning in child component. Warning: Can only update a mounted or mounting component - reactjs

I have a page which presents a list of projects, when a user clicks on that particular project a view component is called in the render().
Parent :
constructor(props) {
super(props);
this.state = {
showData : [],
view : false,
projectId: ''
};
this.buttonHandler = this.buttonHandler.bind(this);
this.back = this.back.bind(this);
};
// change view state to true to render diff component
buttonHandler(){
this.setState({view:true})
};
back(){
this.setState({view:true})
}
render(){
let compA = (
<Paper>
<List>
<Subheader >New
Projects</Subheader>
{this.state.showData.map(item =>
<div key={item.title}>
<ListItem onClick={()=>
this.buttonHandler()} leftAvatar=
{<Avatar icon={<Wallpaper />} />} primaryText=
"test" secondaryText="test" />
<Divider inset={true} />
</div>
)}
</List>
</Paper>
);
let compB = (
<ReviewProject
back={this.back}/>
);
return(
<div>
{this.state.view?compB:compA}
</div>
);
}
child comp B :
constructor(props) {
super(props);
this.state = {
//some code
};
}
//calls function back from parent which sets state the "view" to false
dismiss() {
this.props.back();
};
When the dismiss() function is called at the child to render back the list component compA, a warning pops out:
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 compB component.
Is there a way to solve this? and what is a good practice to navigate from one component to the other back and forth

Because actually on first render, it's kind of if compA is rendered, then compB is missing, therefore when the react tries to re-render, it cannot find the missing element.
==> SOLUTION:
render(){
let returnedComp = (
<Paper>
<List>
<Subheader >New Projects</Subheader>
{this.state.showData.map(item =>
<div key={item.title}>
<ListItem onClick={()=>
this.buttonHandler()} leftAvatar=
{<Avatar icon={<Wallpaper />} />} primaryText=
"test" secondaryText="test" />
<Divider inset={true} />
</div>
)}
</List>
</Paper>
);
if (this.state.view) {
returnedComp = (
<ReviewProject back={this.back}/>
);
}
return(
<div>
{returnedComp}
</div>
);
}

You can try using ref also before setting your state as defined below
function(){
if (this.refs.ref)
this.setState({view: true});
}
render() {
return (
<div ref="ref">{this.state.view}</div>
);
}

The solution I found is:
To create a third component called viewProject component to handle the switching between A and B.
class A extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
This is component A
<button onClick={this.props.onGoBClick}>Go to B</button>
</div>
)
}
}
class B extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
This is component B
<button onClick={this.props.onGoAClick}>Go to A</button>
</div>
)
}
}
class ViewContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
view: false,
}
this.goToA = this.goToA.bind(this);
this.goToB = this.goToB.bind(this);
}
goToA() {
this.setState({view: false})
}
goToB() {
this.setState({view: true})
}
render() {
return (
this.state.view ?
<B onGoAClick={this.goToA}/>
:
<A onGoBClick={this.goToB}/>
)
}
}
ReactDOM.render(<ViewContainer />, document.getElementById('app'))

Related

Why is my class component not rendering correctly?

I've been at this awhile and I can't get my head around it. I feel so stupid. Can anyone tell me what's wrong?
The console log works currently, and the console.log of the Object states that the state has been updated from false to true.
class ChoiceBar extends React.Component {
constructor() {
super();
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
handleFirstQuestion() {
console.log("Start Rendering First Question")
this.setState({handleFirstQuestion: true}, () => {console.log(this.state);
});
}
render() {
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion.bind(this)}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
}
}
The first thing is that you are binding the function twice, one in constructor and one in onClick event handler.
Second pass props component in constructor and super
constructor(props) {
super(props);
this.state = {
handleFirstQuestion: false,
};
this.handleFirstQuestion = this.handleFirstQuestion.bind(this)
}
return (
<div>
<center>
<div>
<button onClick={this.handleFirstQuestion}> Start! </button>
<p> </p>
{this.state.handleFirstQuestion
? <FirstQuestionBox />
: null
}
</div>
</center>
</div>
)
I don't think you are exporting the class component
write this after the class component
export default ChoiceBar;
you should probably use aero functions and useState hook instead of the class component
for example:
const ChoiceBar = () => {
const [handleFirstQuestion, setHandleFirstQuestion] = useState(false)
}

How to pass an user-created array to another component?

I am new to React. In fact I am new to any frontend programming lanugage. Therefore I encounter many really weird and sometimes even hilarious problems. I am struggling with sending an array to another compontent. The problem is user creates that array, and it's created dynamically inside render(){return(..)}
class Home extends Component {
constructor(props) {
super(props)
this.chosenItems = [];
}
state = {
items: [],
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.chosenItems.push(item);
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items} = this.state;
return (
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket dataFromParent = {this.getItems} />
</div>
and Basket class
class Basket extends React.Component {
constructor(props) {
super(props);
this.chosenItems = [];
}
state = {
items: []
};
componentDidUpdate()
{
this.chosenItems = this.props.dataFromParent;
console.log(this.props.dataFromParent);
}
render() {
return (
<div>
<h2>{this.chosenItems}</h2>
<h2>{this.props.dataFromParent}</h2>
</div>
);
}
}
export default Basket;
the problem is console log shows "undefined". Could you tell me what I am doing wrong? Or maybe my entire approach is incorrect and I should look for another solution?
Update
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items, chosenItems} = this.state;
return (
<div>
<div><Basket chosenItems ={this.state.chosenItems} /></div>
<Router>
<div className="container">
<ul>
<Link to="/login">login</Link>
<Link to="/basket">basket</Link>
</ul>
<Route path="/login" component={Login} />
<Route path="/basket" component={Basket} />
</div>
</Router>
<div>
{items.map(item =>
<div key={item.id}>
{item.name} {item.price} {item.quantity} <img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>Add!</button>
</div>
)}
</div>
</div>
);
}
}
class Basket extends React.Component {
render() {
return (
<div>
{this.props.chosenItems.map(item =>
<div key={item.id}>
{item.name}{item.price}
</div>
)}
</div>
);
}
}
and that works, but the chosenItems array is printed immediatelty where
<Basket chosenItems ={this.state.chosenItems} />
is located after the button is pressed. And when I click on basket redirection I get
TypeError: Cannot read property 'map' of undefined
Firstly, you must understand that things that you don't set in state don't cause a re-render and hence an updated data isn't reflected on to the UI or passed onto the children.
Secondly, you do not need to store the data passed from parent in child again, you can directly use it from props
class Home extends Component {
state = {
items: [],
chosenItems []
};
// code to import JSON from backend API, irrelevant, that part works fine
addItem(item){
this.setState(prev => ({
chosenItems: [...prev.chosenItems, item]
}))
}
render() {
const {items} = this.state;
return (
<div>
//some code
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/><button onClick={() => this.addItem(item)}>ADD</button>
</div>
<Basket chosenItems ={this.state.chosenItems} />
/div>
)
}
}
class Basket extends React.Component {
render() {
return (
<div>
<h2>{this.props.chosenItems.map(item=> <div>{item.name}</div>)}</h2>
</div>
);
}
}
export default Basket;
I see a couple of problems in the snippet -
You are passing this.getItems to your child component as props. It's never defined in the parent. I think it should have been items array state that you have created.
chosenItems should have been a state and you should dig deeper on how to update a state. There is a setState function, learn abt it.
In child, again the the constructor is written like parent's with chosenItems and items which is not needed. You can use them from props.
Please have a look on https://reactjs.org/docs/react-component.html#setstate how to mutate the state of a component. You will find few more basics over this document.
The reason you are getting undefined in the log is because the this.getItems() in Home component is returning undefined either there is no such method or probably the state variable itself is undefined.
In a nutshell few things:
When you want to pass an array to child component, it is as simple as passing any object or property For eg. (I am hoping you want to pass the choosen items to Basket component)
Always initialise state in constructor.
so chooseItems and items should be a part of state and inside constructor.
Your code should look like:
class Home extends Component {
constructor(props) {
super(props)
this.state = {
chosenItems: [],
items: [],
}
}
addItem(item){
this.setState({
chosenItems: [...this.state.chosenItems, item]
})
console.log(this.chosenItems); //logs created array, everything works like a charm
}
render() {
const {items, chooseItems} = this.state;
return (
items.map(item => {
return (
<div key={item.id}>
{item.name} {item.price}<img src = {item.url} className="photo"/>
<button onClick={() => this.addItem(item)}>ADD</button>
</div>
)
})
<Basket dataFromParent={chooseItems} />
div>
)
}
}
and the Basket component would not need constructor since the required data is coming from parent component:
class Basket extends React.Component {
render() {
return (
<div>
{this.props.dataFromParent.map(item => <h2>{this.props.dataFromParent}</h2>)}
</div>
);
}
}
export default Basket;

Learning react with a click game, onClick won't fire. I think I'm just passing a prop *called* onClick

The Bit component is supposed to be my clickable, which should be incrementing the state due to my mine function in the Mine component.
function Bit(props) {
return (
<img src={logo} className="App-logo" alt="logo" onClick={props.onClick} />
)
}
class Mine extends React.Component {
constructor(props) {
super(props);
this.state = {
bitCoins: 0,
clickBonus: 1,
cps: 1,
}
}
mine() {
alert('here')
this.setState({
bitCoins: this.state.bitCoins + 1
})
console.log(this.state.bitCoins);
}
render() {
let status;
status = this.state.bitCoins
return (
<div>
<Bit onClick={() => this.mine()} />
</div>
<div className="text-primary">{status}</div>
)
}
}
What is returned from render in React cannot have sibling elements at the top level. So just wrapping what you're returning with <React.Fragment> (or a div or whatever else you choose) fixed it.
Also note that setState is asynchronous, so when you console.log immediately after calling it, you may not get the most up to date values.
class Mine extends React.Component {
constructor(props) {
super(props);
this.state = {
bitCoins: 0,
clickBonus: 1,
cps: 1,
}
}
mine() {
alert('here')
this.setState({
bitCoins: this.state.bitCoins + 1
})
console.log(this.state.bitCoins);
}
render() {
let status;
status = this.state.bitCoins
return (
<React.Fragment>
<div>
<button onClick={() => this.mine()}>Mine</button>
</div>
<div className="text-primary">{status}</div>
</React.Fragment>
)
}
}
ReactDOM.render(
<Mine />,
document.getElementById('root')
);
<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>

How do I set the state of a parent component in a child component using an onClick function?

I will only show code that is relevant. This is the code from the parent component:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
activeScreen: 'dashboard',
authenticatedUser: this.getAuthenticatedUser(),
}
}
setActiveScreen(key) {
this.setState({
activeScreen: key
})
}
style = (label) => {
if (label === 'Dashboard') {
return {'border-top': '5px solid red'}
}
}
render() {
return (
<div>
{/* {this.serviceWorker()} */}
<ToastContainer transition={Slide} />
<Router>
<Switch>
<NavBar
authenticatedUser={this.state.authenticatedUser} activeScreen={this.state.activeScreen}
setActiveScreen={this.setActiveScreen()} />
</Switch>
</Router>
</div>
);
}
}
The code from the child component Navbar is:
class NavBar extends Component {
constructor(props) {
super(props);
}
adminMenu() {
return (
<div className='admin-menu'>
{this.adminNav().map((i, key) => (
<li className="nav-item" style={this.props.style(i.label)} key={key}>
<Link className={`nav-link ${this.props.activeScreen == i.key ? 'active' : ''}`} to={i.to} onClick={() => this.props.setActiveScreen(i.key)}>
<i className={`fa fa-fw fa-${i.icon}`} aria-hidden="true"></i>
{i.label}
{this.props.activeScreen == i.key &&
<span className="sr-only">(current)</span>
}
</Link>
</li>
))}
</div>
)
}
render() {
return (
{this.adminMenu()}
);
}
adminNav() {
return [
{
label: 'Dashboard',
icon: 'tachometer',
key: 'dashboard',
to: '/',
active: true,
admin: false,
sales: false
},
there are more of these objects
]
}
}
export default NavBar;
The error I get is Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount. The stack trace says it is in setActiveScreen. I am unable to fix this problem.
My syntax is all fine but may not seem so as I have deleted lots of unrelated code.
You are invoking the function with no parameters. Update your code with this;
setActiveScreen={this.setActiveScreen}
OR
setActiveScreen={(key) => this.setActiveScreen(key)}

Reactjs - how to append a font-awesome icon to ref

I'm trying to use appendChild to add a fontawesome icon to a ref in react. I get the error:
Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
Using append instead of appendChild can only add text.
class Example extends React.Component {
handle = (e) => {
e.target.appendChild('<i className="fas fa-minus-circle"/>')
}
render() {
return (
<div className="container">
<button onClick={this.handle}>CLICK HERE</button>
</div>
)
}
}
Consider changing your approach a little. You can achieve the same result you're expecting with the code below, and it's a little more common to use state and conditional JSX in React:
class Example extends React.Component {
state = { images: [] }
handle = () => {
this.setState({ images: [...images, 'fas fa-minus-circle'] });
}
render() {
return (
<div className="container">
<button onClick={this.handle}>CLICK HERE</button>
{this.state.images.map((image, index) => {
<i key={index} className={image} />
})}
</div>
);
}
}
You can create ref and attach it to the element you want to add the icon.
class Example extends React.Component{
constructor(props){
super(props);
this.iconRef= React.createRef();
}
handle = (e) => {
e.prenventDefault();
this.iconRef.current.appendChild('<i className="fas fa-minus-circle"/>')
}
render() {
return (
<div className="container">
<button ref={this.iconRef} onClick={this.handle}>CLICK HERE</button>
</div>
)
}
}

Resources