Change parent state on a child envent - reactjs

I want to put all my 'isSelected' to false expect the clicked one.
Code :
class Parent extends Component {
state= {
menu:[
{isSelected:true},
{isSelected:false},
{isSelected:false}
]
}
render() {
let menus=this.state.menu.map((menu,i)=>(
<MenuElem isSelected={menu.isSelected} />
))
return
{menus}
);
}
}
class MenuElem extends Component {
state = {
isSelected: this.props.isSelected
}
render() {
const {isSelected} = this.state;
let clickHandler = ()=>{
this.setState({ isSelected: true })
//I want to put all the MenuElem to false : except the clicked one
// let parent = this._reactInternalInstance._currentElement._owner._instance; ??
// then foreach MenuElem in my parent I change the isSelected ?
}
return (
<li onClick={clickHandler} className={isSelected ? "is-active" : ""}></li>
);
}
}
I'am not sure my logic is the good one.
The this._reactInternalInstance._currentElement._owner._instance looks like Im in the wrong way.

There are all sorts of cleaning that could be done to this code but below I rewrote what you had and allowed the child to update the parent's state. I trust you have a valid reason to do this and not manage the state of the li at the li level. Hope this helps.
class Parent extends Component {
state = {
menu:[
{ isSelected: true },
{ isSelected: false },
{ isSelected: false }
]
}
render() {
const { menu } = this.state;
return (
{ menu.map((menu,i) => {
return <MenuElem
isSelected={menu.isSelected}
onClick={() => {
const newMenu = [...menu];
newMenu[i] = !menu[i]
this.setState({ menu: newMenu }) }
}
/>
})};
);
}
}
class MenuElem extends Component {
render() {
const { onClick, isSelected } = this.props;
return (
<li onClick={onClick} className={isSelected ? "is-active" : ""}></li>
);
}
}
Edit: In the case where you don't really need the state stored in the parent you could just as easily do:
class Parent extends Component {
render() {
return (
{ menu.map((menu,i) => <MenuElem key={i}/> }
);
}
}
class MenuElem extends Component {
state = { isSelected: false }
render() {
const { isSelected } = this.state
return (
<li onClick={() => {this.setState({ isSelected: !isSelected })}} className={isSelected ? "is-active" : ""}></li>
);
}
}

Related

What is causing the React not a function error?

I'm trying to pass a state from a child to a parent using class based components. Why do I keep getting the this.props.onSortSelect is not a function? I have tried decoding similar questions but can't come to a fix. Thanks in advance.
Child:
class Sort extends React.Component {
state = { descending: true };
onSortSelect = () => {
if(this.state.descending) {
this.setState({ descending: false });
} else {
this.setState({ descending: true });
}
this.props.onSortSelect(this.state.descending);
}
render() {
const buttonText = this.state.descending ? 'Sort Descending' : 'Sort Ascending';
return(
<div class="sort">
<a onClick={this.onSortSelect}>{buttonText}</a>
</div>
);
}
}
Parent:
class FiltersAndSort extends React.Component {
onSortSelect = (sort) => {
console.log(sort);
}
render() {
return(
<div class="filters-and-sort">
<Filter />
<Sort />
</div>
);
}
}
You need to pass the function down
<Sort />
should become
<Sort onSortSelect={ this.onSortSelect } />
But you will also have to fix the onSortSelect of the Sort component.
As the this.state.descending you are passing to the method is not the updated one.
onSortSelect = () => {
this.setState((currentState) => ({
descending: !currentState.descending
}), () => {
this.props.onSortSelect(this.state.descending);
}
}
}

Why is shouldComponentUpdate not firing despite my props changing?

I'm trying to figure out why the shouldComponentUpdate method doesn't fire.
Here are what my props looks like:
Object
children: [Object, Object, Object, Object, Object] (5)
data: Array (2)
0 {id: 1, activated: true}
1 {id: 2, activated: true}
key: undefined
rowRender: function()
I have the following snippet.
export function withState(WrappedGrid) {
return class StatefulGrid extends React.Component {
constructor(props) {
super(props);
setInterval(() => console.log(this.props), 5000)
}
shouldComponentUpdate(){
console.log("props updated")
return true
}
componentDidUpdate(prevProps, prevState) {
console.log("update")
}
render() { ... }
}
}
Creating StatefulGrid component :
const StatefulGrid = withState(Grid);
class NFTTable extends React.Component {
constructor({rules}){
super()
this.rules = rules
this.renderers = new Renderers()
this.contextMenu = false
}
render(){
return([
<StatefulGrid
key={"table"}
data={this.rules}
rowRender={this.renderers.rowRender}
>
// GridColumn ...
</StatefulGrid>,
<ContextMenu
key={"context_menu"}
caller={this.renderers.caller}
/>
])
}
}
Updating the data :
export default class ContextMenu extends React.Component{
constructor({caller}){
super()
this.state = {
show: false
}
caller(this)
}
handleContextMenu = (e, dataItem) => {
e.preventDefault()
this.dataItem = dataItem
this.offSet = { left: e.clientX, top: e.clientY }
this.activated = dataItem.dataItem.activated
this.setState({ show: true })
}
componentDidMount() {
document.addEventListener('click', () => {
if(this.state.show)
this.setState({ show: false })
})
}
handleSelect = (e) => {
switch(e.item.data){
case "activation":
this.toggleActivation()
break
default:
console.log("Error, non registered event " + e.data)
}
}
toggleActivation(){
this.dataItem.dataItem.activated = !this.dataItem.dataItem.activated;
}
render() {
return (
<Popup show={this.state.show} offset={this.offSet}>
<Menu vertical={true} style={{ display: 'inline-block' }} onSelect={this.handleSelect}>
<MenuItem data="delete" text="Delete rule"/>
<MenuItem data="activation" text={this.activated ? "Deactivate Rule" : "Activate rule"}/>
</Menu>
</Popup>
);
}
}
Calling handleContextMenu :
export default class Renderers {
rowRender = (trElement, dataItem) => {
let greyed = { backgroundColor: "rgb(235,235,235)" }
let white = { backgroundColor: "rgb(255,255,255)" }
const trProps = {
...trElement.props,
style: dataItem.dataItem.activated ? white : greyed,
onContextMenu : (e) => {
this.contextMenu.handleContextMenu(e, dataItem)
}
};
return React.cloneElement(trElement, { ...trProps }, trElement.props.children);
}
caller = (contextMenu) => {
this.contextMenu = contextMenu
}
}
For debugging purpose, I've added a setInterval method. Through another snippet in my code, I do change props.data[0].activated to false and the changes do reflect in the console log. So why is shouldComponentUpdate not triggered and how to get it to trigger ?

Is using getDerivedStateFromProps in a uncontrolled Modal good practice

I want to be able to have all the hide and show logic within the error component, and only passing the initial show of the error modal when theres an error in my app like this.
class App extends React.Component {
render() {
return (
<div>
<ErrorModal show={!!this.state.error} message={this.state.error.message/>
</div>
)
}
}
Then in my ErrorModal, handle the closing of the error modal by using getDerivedStateFromProps.
class ErrorModal extends React.Component {
static defaultProps = {
title: 'Error',
onClose: () => {}
};
state = { show: this.props.show };
static getDerivedStateFromProps(nextProps, previousState) {
if(nextProps.show !== previousState.show) {
return {show: nextProps.show};
}
return { show: previousState.show };
}
onClose = () => {
this.setState({
show: false
}, this.props.onClose());
};
render() {
const { message, children, title } = this.props;
return (
<GenericPopup show={this.state.show} className='error-modal'>
<GenericPopup.Header>
<GenericPopup.Title>{title}</GenericPopup.Title>
</GenericPopup.Header>
<GenericPopup.Body>
{ message ? message : children }
</GenericPopup.Body>
<GenericPopup.Footer>
<a href="javascript:void(0)" className='btn-primary close' onClick={this.onClose}>Okay, Got it.</a>
</GenericPopup.Footer>
</GenericPopup>
)
}
}
Is this the correct usage of getDerivedStateFromProps? Is there anotherway without having to make my component fully controlled?

React setState called multiple times on the same state object

I have the following:
import React from 'react';
import ReactDOM from 'react-dom'
import {render} from 'react-dom';
import Forms from './forms/forms.jsx';
class Option1 extends React.Component {
render () {
return (
<p>Icon 1</p>
)
}
}
class TShirt extends React.Component {
render () {
console.log(this.props.currentState);
return <div className="thsirt">
<h1>{this.props.name}</h1>
<p>{this.props.iconID}</p>
{this.props.optionA ? <Option1 /> : ''}
</div>;
}
}
class Link extends React.Component {
render () {
return (
<li
data-id={this.props.el}
onClick={this.props.onClick}
className={this.props.activeClass}>{this.props.el}
</li>
);
}
}
class Nav extends React.Component {
getComponentID (id) {
switch(id) {
case 'name':
return 1;
break;
case 'color':
return 2;
break;
case 'design':
return 3;
break;
case 'share':
return 4;
break;
}
}
handleClick (event) {
// setting active class
var id = event.target.getAttribute("data-id");
this.props.action(id);
// switching coomponent based on active class
var component = this.getComponentID(id);
this.props.switchComponent(component);
}
render () {
var links = ['name', 'color', 'design', 'share'],
newLinks = [],
that = this;
links.forEach(function(el){
newLinks.push(<Link
onClick={that.handleClick.bind(that)}
activeClass={that.props.active == el ? 'active': ''}
key={el}
el={el}
/>
);
});
return (
<ol>
{newLinks}
</ol>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
color: '',
active: '',
formId: 1,
optionA: {
on: false,
icon_id: '',
option_id: '',
name: ''
}
};
this.setName = this.setName.bind(this);
this.setColor = this.setColor.bind(this);
this.setAtciveNavEl = this.setAtciveNavEl.bind(this);
this.setFormId = this.setFormId.bind(this);
this.setOptionA = this.setOptionA.bind(this);
this.setOptionAVisibility = this.setOptionAVisibility.bind(this);
}
setName (tshirt) {
this.setState({ name:tshirt })
}
setColor (color) {
this.setState({ color:color })
}
setAtciveNavEl (el) {
this.setState({ active:el })
}
setFormId (id) {
this.setState({ formId:id })
}
setOptionA (iconID, iconName) {
this.setState({
optionA:
{
icon_id: iconID,
name: iconName
}
})
}
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
option_id: optionID,
on: onOff
}
})
}
render () {
return (
<section className={this.state.color}>
<Nav
active={this.state.active}
action={this.setAtciveNavEl}
switchComponent={this.setFormId}
/>
<TShirt
name={this.state.name}
icons={this.state.options}
optionA={this.state.optionA.on}
currentState={this.state}
/>
<Forms
name={this.state.name}
action={this.setName}
colorVal={this.setColor}
activeNav={this.setAtciveNavEl}
switchComponent={this.setFormId}
formID={this.state.formId}
setOptionA={this.setOptionA}
setOptionAVisibility={this.setOptionAVisibility}
/>
</section>
);
}
}
render(<App/>, document.getElementById('app'));
I need to populate this object at different times like this:
setOptionA (iconID, iconName) {
this.setState({
optionA:
{
icon_id: iconID,
name: iconName
}
})
}
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
option_id: optionID,
on: onOff
}
})
}
The problem I have is taht when I console.log my state at:
class TShirt extends React.Component {
render () {
console.log(this.props.currentState);
return <div className="thsirt">
<h1>{this.props.name}</h1>
<p>{this.props.iconID}</p>
{this.props.optionA ? <Option1 /> : ''}
</div>;
}
}
after all my click events it seems like I loose the "on" and "option_id" from the optionA object.
Does calling setState on the same object override the previous setState?
If you are writing ES2015, you can use the spread operator to copy the whole object and just modify one of it's properties:
setOptionAVisibility (onOff, optionID) {
this.setState({
optionA:
{
...this.state.optionA,
option_id: optionID,
on: onOff
}
})
}
Can be very useful when modifying single properties of complex objects on the state tree.

why do I have "Warning: undefined(...): Cannot update during an existing state transition..." error?

so i have this in my code like this:
class TimersDashboard extends React.Component{
constructor() {
super(props);
this.state = {
timers: [
{id: uuid.v4(), text:'I am the first id' },
{ id:uuid.v4(), text:'I am the second text' }
]
};
}
clickEdit(id) {
this.openForm(id);
}
openForm(id) {
this.setState({
timers: this.state.timers.map((timer) => {
if(timer.id === id) {
return Object.assign({}, timer, { editFormOpen: true });
} else {
return timer;
}
})
});
}
handleCloseForm(id) {
this.closeForm(id);
}
closeForm(id) {
this.setState({
timers: this.state.timers.map((timer) => {
if(timer.id === id) {
return Object.assign({}, timer, { editFormOpen: false });
} else {
return timer;
}
})
});
}
}
render() {
return (
<Timer id={this.state.data[0].id} onEdit={this.clickEdit.bind(this)} onDelete = {this.handleCloseForm.bind(this)}/> // as if wroking on the first id
);
}
}
}
However, below, I passed the methods as props, the other component I tried to invoke these the same way, you can see their code is slightly similar in way.
class Timer extends React.Component {
constructor(props) {
super(props);
this.handleEditClick = this.handleEditClick.bind(this);
this.handleTrashClic = handleTrashClic.bind(this);
}
handleEditClick() {
this.props.onDelete(this.props.id);
}
handleTrashClick() {
this.props.onEdit(this.props.id);
}
render() {
return(
// ... onClick = {()=>this.handleEditClick(this.props.id)} ..
// ... onClick = {()=>this.handleTrashClick(this.props.id)} ..
);
}
}
}
I code them same way on other component, the delete method works on other component but I don't know why the Edit method does not and I can't make it work, I tried to pass the parentObj context, added .bind(this), But I cannot make it work. My error is "Warning: undefined(...): Cannot update during an existing state transition...". How do I make it work?
Created the same example in jsfiddle, its working. Try this:
Parent Component:
class TimersDashboard extends React.Component{
constructor(props) {
super(props);
this.state = {
timers: [
{id: 1, text:'I am the first text' },
{id: 2, text:'I am the second text' }
]
};
}
edit(id){
let timers = this.state.timers.map((timer) => {
if(timer.id === id) {
return Object.assign({}, timer, { editFormOpen: true });
} else {
return timer;
}
})
this.setState({timers});
}
remove(id){
let timers = this.state.timers.map((timer) => {
if(timer.id === id) {
return Object.assign({}, timer, { editFormOpen: false });
} else {
return timer;
}
})
this.setState({timers});
}
render() {
return (
<div>
<Timer id={1} edit={this.edit.bind(this)} remove={this.remove.bind(this)}/>
</div>
);
}
}
Child Component:
class Timer extends React.Component{
constructor(props) {
super(props);
this.state={};
}
edit(id){
this.props.edit(id);
}
remove(id){
this.props.remove(id);
}
render(){
return(
<div>
In Child Component:
<br/>
Id: {this.props.id}
<p onClick={this.edit.bind(this,this.props.id)}>Edit</p>
<p onClick={this.remove.bind(this,this.props.id)}>Remove</p>
*click on edit and remove to change the state
</div>
)
}
}
Check jsfiddle for working example: https://jsfiddle.net/wqkfqusk/

Resources