Why handler calls render method 3 times? - reactjs

I'm working with Semantic-ui and have such component with list of
items. Every item has group of checkboxes:
import React, { Component } from 'react'
import { Divider,Label,List,Checkbox,Header } from 'semantic-ui-react'
export default class Menu extends Component {
constructor(props) {
super(props);
this.state = {
checkboxes: []
}
}
render() {
let { tags } = this.props;
return (
<div className="ui segment basic" >
{typeof tags === "undefined" ?
<div>Select partner and process</div>
:
this.getTagListItems(tags)
}
</div>
)
}
getTagListItems = tagsData => {
let tags = [];
for(let i=0; i<tagsData.length; i++){
if ( tagsData[i].children.length !==0 ) {
tags.push(
<div className="container" key = { i }>
<Header as="h3">{ tagsData[i].name }</Header>
<Divider/>
<List>
{this.getTagCheckboxes(tagsData[i].children)}
</List>
</div>
);
}
}
return tags;
};
getTagCheckboxes = checkboxData => {
let checkboxes = [];
for(let i=0; i<checkboxData.length; i++) {
checkboxes.push(
<List.Item key = { checkboxData[i].id }>
<Checkbox label = { checkboxData[i].name }
id = { checkboxData[i].id }
onClick = { this.setCheckbox }
// checked = { this.state.data.id === checkboxData[i].id }
/>
<List.Content floated="right" >
<Label>
0
</Label>
</List.Content>
</List.Item>
);
}
return checkboxes;
};
setCheckbox = (e, itemData) => {
let { checkboxes } = this.state;
console.log('ITEMM!!!', itemData)
}
}
How it can be seen on every checkbox I set onClick, which calls setCheckbox. When I check just 1 checkbox, it calls setCheckbox 3 times and I get in console console.log for 3 times. What's wrong, how can I correct it in order setCheckbox works only 1 time per 1 check?

Problem was in onClick. I changed it to onChange() and now it works

Related

delete three level component(a component have an array of component, each have an array of compoent)

I would appreciate it greatly if you could let me know how to deal with this problem.
I hava a page component which has an array of company component, and each company has an array of contract.
If I delete any company, every company component will re-render, so I cant put array of contract under each company state, so I put it under page component state.
The question is If I delete one company, how can I correctly re-render all contracts under each component.
Thank you for reading my problem, and sorry for my poor English:(
Error Message is "TypeError: Cannot read property 'contractList' of undefined"
My page code is...
class IRPage extends Component {
// // initialize our state
state = {
companyList: [],
};
addCompanyArr = (newCompany) => {
this.setState(
state => {
const list = state.companyList.push(newCompany);
return {
list,
};
}
)
};
addContractArr = (index, newContract) => {
this.setState(
state => {
const list = state.companyList[index].contractList.push(newContract);
return {
list,
};
}
);
}
setCompanyArr = () => {
this.setState(
state => {
const list = state.companyList;
return {
list,
};
}
)
};
render() {
return (
<div className="container m-5">
<IRContent companyList={this.state.companyList} setCompanyArr={this.setCompanyArr} addCompanyArr={this.addCompanyArr} addContractArr={this.addContractArr}></IRContent>
</div>
)
}
}
export default IRPage;
My content code is ...
class IRContent extends React.Component {
constructor(props) {
super(props);
}
addCompany = () => {
const companyNode = <IRCompany companyList={this.props.companyList} setCompanyArr={this.props.setCompanyArr} index={this.props.companyList.length} addContractArr={this.props.addContractArr}/>;
const newCompany = {
node: companyNode,
contractList: [],
};
this.props.addCompanyArr(newCompany);
}
render() {
return(
<div className="container col-sm-12 justify-content-center">
<h1 align="center">data</h1>
<hr/>
{
this.props.companyList.map((element, index) => {
return <div key={"myCompanyKey_"+index+"_"+this.props.companyList.length} id={index}>{element.node}</div>;
})
}
<button color="primary" onClick = {this.addCompany}>
add new company
</button>
</div>
);
}
}
export default IRContent;
My company code is...
class IRCompany extends React.Component {
constructor(props) {
super(props);
}
deleteCompany = event => {
event.preventDefault();
var targetID = event.target.parentElement.parentElement.parentElement.parentElement.parentElement.getAttribute("id")
this.props.companyList.splice(targetID, 1);
this.props.setCompanyArr();
};
addContract = event => {
event.preventDefault();
var newContract = <IRContract/>;
var targetID = event.target.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.getAttribute("id");
this.props.addContractArr(targetID, newContract);
this.forceUpdate();
};
render() {
return(
<div>
{
this.props.companyList[this.props.index].contractList.map((element, index) => {
return <React.Fragment key={"myContractKey" + index + "_" +this.props.companyList[this.props.index].contractList.length}>{element}</React.Fragment>;
})
}
<button color="primary" onClick = {this.addContract}>主約</button>
</div>
);
}
}
export default IRCompany;
I can successively add company and contract, but there are some problem on deleing.
Thank you for reading my problem, and sorry for my poor English:(

Why am I not able to check/uncheck checkboxes?

I am not able to check/uncheck checkboxes, but their checkmark value is being populated by the api and either checking them or leaving them unchecked. But for some reason I am not able to check/uncheck.
Tried putting them into different components and using onClick/onChange. Nothing works.
Parent component - PublishPage:
<div id="all-roles">
{this.props.listOfRoles ? (
<ListOfRoles checkedIDs={this.state.roleIDs} roles={this.props.listOfRoles} /> ) : null}
</div>
List of Roles
export default class ListOfRoles extends Component {
state = {
checkedIDs : []
}
static getDerivedStateFromProps(props) {
let checkedIDs = null;
if (props.checkedIDs) {
checkedIDs = props.checkedIDs
}
return { checkedIDs: checkedIDs };
}
render() {
const roles = this.props.roles[0].roles;
console.log(this.state);
return (
<div id="assign-to-role">
{roles.map(role => {
return (
<div>
<RoleCheckbox roleName={role.roleName} roleID={role.roleID} checkedArray={this.state.checkedIDs} />
</div>
);
})}
</div>
);
}
}
RoleCheckbox
export default class RoleCheckbox extends Component {
constructor(props) {
super(props);
this.state = {
checkboxDefault: true
}
this.handleCheckbox = this.handleCheckbox.bind(this);
}
static getDerivedStateFromProps(props) {
let checked = true;
let roleID = props.roleID;
let checkedArray = props.checkedArray;
if (checkedArray.indexOf(roleID) !== -1) {
checked = true
} else {
checked = false;
}
return { checkboxDefault: checked };
}
handleCheckbox(e) {
this.setState({
[e.target.name]: !this.state[e.target.name]
})
}
render() {
return (
<div>
<label> {this.props.roleName}
<input
type="checkbox"
name="checkboxDefault"
onChange={this.handleCheckbox}
checked={this.state.checkboxDefault} />
</label>
</div>
)
}
}
I want to be able to check/uncheck and also I want to be able to load them as checked if the api says they should be checked.
Actually what's happening here is that you are injecting the HTML with the boolean value from this.state.checkboxDefault which is initialized default to true inside RoleCheckbox component and the function that handles the change is not effecting the state. Change the state and it will reflect on the UI as well
handleCheckbox(e) {
this.setState({
checkboxDefault: !this.state.checkboxDefault
})
}

How to show validation message on <TagsInput> react premade component on unique value

I have an input tag component from react-tagsinput as follows:
const onTagChange = (tags) => {
const noDuplicateTags = tags.filter((v, i) => tags.indexOf(v) === i);
const duplicateEntered = tags.length !== noDuplicateTags.length;
if (duplicateEntered) {
onTagChange(tags);
console.log('duplicate');
}
onTagChange(noDuplicateTags);
};
function TagContainer({
tags,
}) {
return (
<div>
<Header>Meta:</Header>
<TagsInput value={tags} onChange={onTagChange} />
</div>
);
}
TagContainer.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
};
TagContainer.defaultProps = {
tags: [],
};
export default TagContainer;
and the implementation on the onTagChange method which is passed as a prop to the <TagContainer> component in another component.
export class Modal extends React.Component {
...
...
onTagChange = (tags) => {
this.props.onTagChange(tags);
}
...
...
render() {
return(
<TagContainer
tags={tags}
onTagChange={this.onTagChange}
/>
);
}
}
Problem: onlyUnique prop in the <TagsInput> component is set to true to avoid duplicate entries. But I need to display an error message saying "duplicate values" as soon as user enters a duplicate value. How can this be done especially on the third party component.
I think you're going to have to handle dealing with duplicates in your component because you are getting no feedback from <TagInput /> component.
At a higher level, I would do something like this
class Example extends React.Component {
constructor() {
super();
this.state = {
showDuplicateError: false
};
}
handleTagChange(tags) {
const uniqueTags = removeDuplicates(tags);
const duplicateEntered = tags.length !== uniqueTags.length;
if (duplicateEntered) {
this.showDuplicateError();
}
// add unique tags regardless, as multiple tags could've been entered
const { onTagChange } = this.props;
onTagChange(uniqueTags);
}
showDuplicateError() {
this.setState({
showDuplicateError: true
});
}
render() {
const { showDuplicateError } = this.state;
const { tags } = this.props;
return (
<React.Fragment>
{ showDuplicateError && <div>Duplicate entered</div>}
<TagsInput value={ tags } onTagChange={ this.handleTagChange } />
</React.Fragment>
);
}
}

React container with render logic?

I have a container, which renders 3 components:
1. list of items
2. new item modal
3. edit item modal
In order to control the whole container functions, I need to call the list of items with column list. Is it ok that all will be inside the container?
Is it ok to render modal within the container? (The modal contains the 2 and 3 components)
class Items extends React.Component {
constructor(props) {
super(props)
this.state = {
modal: false
}
this.columns = [
...
]
this.closeModal = this.closeModal.bind(this)
}
openModal(type, item) {
this.setState({
modal: {
type,
item: item && item.toJS()
}
})
}
closeModal() {
this.setState({modal: false})
}
renderModal() {
const {item, type} = this.state.modal;
return (
<Modal onClose={this.closeModal}>
{type == modalTypes.NEW_ITEM &&
<ItemForm onCancel={this.closeModal}
onSubmit={...}/>}
{type == modalTypes.REMOVE_ITEM &&
<ConfirmationDialog text="Are you sure you want to remove?"
onSubmit={...} onCancel={this.closeModal}/>}
{type == modalTypes.EDIT_ITEM &&
<ItemForm onCancel={this.closeModal}
onSubmit={...}/>}
</Modal>
)
}
render() {
const {visibleItems, display_type} = this.props;
return (
<div>
<div className="_header_container">
<Header title="Items"/>
<div className="actions">
<Search />
<DisplayToggle />
<Button size="sm" color="primary"
onClick={() => ...}
</div>
</div>
{display_type == displayType.GRID &&
<Grid items={visibleItems} columns={this.columns}/>}
{display_type == displayType.TILE &&
<TileView items={visibleItems} titleKey="name" linkKey="url"/>}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
remove: (item) => dispatch(remove(item)),
edit: (item, ...) => dispatch(edit(item, ...)),
create: (name, val) => dispatch(create(name, url)),
}
}
const mapStateToProps = (state) => {
return {
visibleItems: filterItems(state.items, state.search),
display_type: state.display_type
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Items)
Thanks

set state of specific attribute inside of an object

I'm struggling to figure out the syntax for setting the state of an object inside of an array. I'm trying to access the fruits amount attribute. I'm familiar with concat for adding a new object and such but instead of adding a new updated object, how do I replace the value of an attribute inside of an object keeping everything the same except the attribute that changed and not adding a completely new object.
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
import Fruits from'./Fruits';
class App extends Component {
constructor(props) {
super(props);
this.state = {
fruits: [
{
type:'apple',
amount:10,
color:'green',
id: 0
},
{
type:'tomato',
amount:'25',
color:'red',
id: 1
}
]
};
}
renderFruits = () => {
const { fruits } = this.state;
return fruits.map((item, index) =>
<Fruits
key={index}
type={item.type}
amount={item.amount}
color={item.color}
id={item.id}
increment={this.increment}
/>
);
}
increment = (fruitId) => {
const { fruits } = this.state;
const incrementedFruit = fruits.filter((item) => {
return item.id == fruitId;
})
//{fruits: {...fruits, [fruits.amount]: [fruits.amount++]}}
}
render() {
return (
<div>
{this.renderFruits()}
</div>
);
}
}
render(<App />, document.getElementById('root'));
Fruits Component
import React, { Component } from 'react';
class Fruits extends Component {
increment = () => {
const { id } = this.props;
this.props.increment(id);
}
decrement = () => {
console.log("decremented");
}
render() {
const { type, id, amount, color} = this.props;
return(
<div>
<span>
{type}
<ul>
<li>amount: {amount} </li>
<li>color: {color} </li>
</ul>
<button onClick={this.increment}> + </button>
<button onClick={this.decrement}> - </button>
</span>
</div>
);
}
}
export default Fruits;
stackblitz project
First pass index from fruit, lets take as index prop :
<Fruits
key={item.id}
index = {index}
type={item.type}
amount={item.amount}
color={item.color}
id={item.id}
increment={this.increment}
/>
Then pass index, instead of id of fruit on increment :
increment = () => {
this.props.increment(this.props.index);
}
You can make your increment function 2 ways :
1 : with state mutation , Please read : State Mutation
increment = (index) => {
++this.state.fruits[index].amount;
this.forceUpdate();
//OR
this.setState({fruits : this.state.fruits});
}
2 : without state mutation
increment = (index) => {
let fruits = this.state.fruits.slice();
++fruits[index].amount;
this.setState({fruits});
}
P.S: Also found that you are using the array index as key. This is
deprecated. See
https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
So also change :
from key={index} to key={item.id}
just change your increment function to this:
increment = (fruitId) => {
const { fruits } = this.state;
fruits.forEach((fruit)=>{
if(fruit.id == fruitId){
fruit.amount = parseInt(fruit.amount)+ 1;
}
})
this.setState({fruits: fruits})
}
Edited

Resources