I'm currently facing the following issue: I'm working on a react web app which contains one component that displays products that are contained in the products array state. A product can be added by clicking on a table row of a react-table which contains different products. A product can be added multiple times by clicking on the same product.
getTdProps={(state, rowInfo, column, instance) => {
return {
onClick: (e, handleOriginal) => {
that.props.handleProductSelect(rowInfo.original);
}
};
}}
If a table row is clicked, the handleProductSelect function is called which passes the original data as parameter.
handleProductSelect(oSelProduct) {
oSelProduct["amount"] = 1;
oSelProduct["selectedVariantOptions"] = [];
this.setState(
prevStates => ({
products: [...prevStates.products, oSelProduct]
}),
() => {
...
}
);
}
The selected product will be added to the products array, the component will be re-rendered and the products will be shown in a standard html table.
Each rendered product contains a plus and minus button to change the amount of each product. The plus button e.g. calls the handlePlusButton function:
handlePlusButton(i) {
this.setState(state => {
const products = state.products.map((product, j) => {
if (j === i) {
product.amount = product.amount + 1;
return product;
} else {
return product;
}
});
return {
products
};
});
}
Problem: If I click on the same product multiple times, they will be added to the product array. Now if I click on the plus button to change the amount, somehow the amount will be changed for all products which where added. So somehow it looks like a reference is added to the products array. How can I avoid this behaviour?
The problem happens, becuase You are adding two references to the same product.
Try to change handleProductSelect to this:
handleProductSelect(oSelProduct) {
const newProduct = {
...oSelProduct,
amount: 1,
selectedVariantOptions: []
};
this.setState(
prevStates => ({
products: [...prevStates.products, newProduct]
}),
() => {
...
}
);
}
Related
why Sometimes data comes in dropdown and sometimes it doesn't comes?
dropdown's data comes on first time cloning, Sometimes it comes even after cloning for the second time. But when cloned for the third time, dropdown's doesn't get the options.
render() {
const { value, items, selectedOption, mode, open } = this.state;
const { data, userPrivileges, handleClose, options } = this.props;
//console.log('test',options);
const topics = data.topics;
const isDisabled = !userPrivileges.includes("Create_Note");
const defaultOption = options.filter((x) => x.productId === data.productId);
i tried to console the value and i got
clicking on 1st clone getting 2 array list, 2nd time clone getting 1 array list but clicking on 3rd time getting 3 or 4 array list, when getting 3 or 4 array list after that dropdown's doesn't get
componentDidMount() {
const defaultOption = this.props.options.find(
(x) => x.productId === this.props.data.productId
);
this.setState({ selectedOption: defaultOption });
const { productInfoUrl, takeovers, complements } = this.props.data;
this.setState({
link: productInfoUrl,
takeovers: takeovers,
complements: complements,
});
}
export default connect(
(state, props) => {
const topics = props.data.topics;
const items = topics.reduce((x, y) => {
return { ...x, [y.title]: y };
}, {});
const dataGrid = state.dataGrid.dataGrid;
const options = dataGrid.map((arrayItem) => {
console.log()
return {
...arrayItem,
label: arrayItem.productName,
value: arrayItem.productName,
};
});
<StyledSelect
components={{ MenuList }}
options={options}
onChange={this.handleSelect}
defaultValue={defaultOption}
placeholder="Search for product"
filterOption={({ label }, query) =>
label.toLowerCase().indexOf(query.toLowerCase()) >= 0 &&
i++ < resultLimit
}
onInputChange={() => {
i = 0;
}}
/>
i tried to console the value and i got
clicking on 1st clone getting 2 array list, 2nd time clone getting 1 array list but clicking on 3rd time getting 3 or 4 array list, when getting 3 or 4 array list after that dropdown's doesn't get
Whenever we clone, there should be an option in dropdown
I'm currently working on React js project with "mui-datatables": "^4.2.2".
I have a list of data divided on pages with the possibility of selecting an item through a checkbox :
My problem :
when I select an item in the second page, the component rerender and automatically back to the first page.
I think the problem is a set state inside onRowSelectionChange :
const options = {
filter: false,
onRowClick: (rowData, rowMeta) => {
console.log("--rowData --");
console.log(rowData);
console.log("--rowMeta --");
console.log(rowMeta);
},
onRowSelectionChange: (
currentRowsSelected,
allRowsSelected,
rowsSelected
) => {
let items = [];
allRowsSelected.forEach((row) => {
items.push(data[row.dataIndex]);
});
setSelectedRows(items);
},
How can i fix this problem ?
you should store page number in the state as well say for example
curPage:1
when page change you should update the curPage as well,now you can use this inside the options props you pass to mui-datatable.like this
const options = {
page:this.state.curPage,//incase its class component
onChangePage:(page)=>{
this.setState({curPage:page});//assuming its a class component
},
filter: false,
onRowClick: (rowData, rowMeta) => {
console.log("--rowData --");
console.log(rowData);
console.log("--rowMeta --");
console.log(rowMeta);
},
onRowSelectionChange: (
currentRowsSelected,
allRowsSelected,
rowsSelected
) => {
let items = [];
allRowsSelected.forEach((row) => {
items.push(data[row.dataIndex]);
});
setSelectedRows(items);
},
hope this will help
I have simple shopping cart app made with REACT and REDUX.
I have some items that are displayed on screen with input filed where number of items is changed and based on that new price i calculated. But one thing is problematic.
Price is calculated correctly when I update number of items or when I add new but but when I update number that item is rendered last. So if I update number of first item it will just end as last item on the list
1
2
3
4
Let's say that I update item 1 then list will look like this
2
3
4
1
and this is really frustrating me. This is how I display items
{this.props.cart.map(item => {
return (
<div key={item.product.id}>
<Item item={item}/>
</div>
)
})}
I also tried with ...reverse().map but it does the same except it puts it on the top of page instead of bottom. I want them to stay where they are when they are updated
This is parent component of single item component. Both of them are class components, I also have this in parent component
const mapStateToProps = (state) => {
return {
cart: state.cart.cart
}
};
export default connect(mapStateToProps)(Cart);
Thank you for your time. If you need any other info just reach out and I will provide it.
UPDATE 1
This is how I update cart
handleChange = e => {
if(e.target.value <= 0) {
alert("Quantity must be grater than 0");
return;
}
if(e.target.value > this.props.item.product.amount) {
alert("Max number of products reached");
return;
}
if(this.state.quantity !== e.target.value) {
this.setState({
quantity: e.target.value,
// btnVisible: true
}, () => this.props.updateCartQuantity(this.props.item.product.id, this.state.quantity))
}
};
<input type="number" className="form-control" onChange={(e) => {this.handleChange(e)}} value={this.state.quantity}/>
This update is happening in child component
UPDATE 2
updateCartQuantity is a function
export const updateCartQuantity = (productId, quantity) => {
return {
type: 'UPDATE_CART_QUANTITY',
payload: {
productId,
quantity: quantity
}
}
};
it's data is handled here
case 'UPDATE_CART_QUANTITY':
let item = cart.find(item => item.product.id === action.payload.productId);
let newCart = cart.filter(item => item.product.id !== action.payload.productId);
item.quantity = action.payload.quantity;
newCart.push(item);
return {
...state,
cart: newCart
};
problem is probably in this case but I just can't see it
Okay. It is fixed thanks to #HMR comment on post. I just realised what I was doing.
Instead of cart.filter I should use this
let newCart = cart.map(c => c.id === action.payload.productId ? {...c, quantity: action.payload.quantity} : c);
and remove
newCart.push(item);
.map will take care of looping through elements so there is no need of pushing element to newCart.push(item)
Thank you for your comment. Hope this helps someone :)
I have a table of schedules that is rendered by a dropdown. Each schedule can then be marked for export via a slider, this will store the schedule id in scheduleIdsToExport and show that schedule in the export table.
But if I change the Select Query dropdown, which renders more schedules specific to that query, the schedules marked for export from the previous query disappear from the table. I want the schedules marked for export to persist in the table no matter what query is selected from the dropdown.
So I'm thinking I need to have something in my slider function to update state with the all the schedule objects marked for export and have them persist in the exported table. I'm not exactly sure how to go about storing all the schedules to keep them in the exported table and have the scheduleIdsToExport array also keep the id's of each schedule
slider = ({ id, isExported }) => {
if (isExported === true) {
this.setState(
{
scheduleIdsToExport: [id, ...this.state.scheduleIdsToExport]
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
} else {
const newArray = this.state.scheduleIdsToExport.filter(
storedId => storedId !== id
);
this.setState(
{
scheduleIdsToExport: newArray
},
() => {
console.log(this.state.scheduleIdsToExport);
}
);
}
};
The sandbox here will provide further explanation on what is happening.
This is just chaotic!
The problem : Keep track from multiples list of items(schedules) that will eventually be added to another list schedulesToExport
The Solution :
Create a parent component that reflects the previously described state
class Container extends Component{
state ={
querys :[
['foo','lore ipsum','it\'s never lupus'],
['bar','ipsumlorem', 'take the canolli']
],
selectedQuery : 0,
schedulesToExport : []
}
}
Now we have a list of lists, that can be interpreted as a list of querys containing a list of schedules
Render a select element to reflect each query:
render(){
const { querys, selectedQuery } = this.state
const options = querys.map((_, index) => (<option value={index}> Query: {index + 1}</option>))
return(
<div>
<select onChange={this.selectQuery}>
{options}
</select>
{
querys[selectedQuery].map(schedule => (
<button onClick={() => this.selectSchedule(index)}> Schedule: {schedule} </button>
))
}
</div>
)
}
What's happening? We are just rendering the selected query by it's index and showing all it's respective schedules.
Implement the selectQuery and selectSchedule methods which will add the selected schedule in the list to export:
selectQuery = e => this.setState({selectedQuery : e.target.value})
selectSchedule = index => {
const { selectedQuery } = this.state
const selected = this.state.querys[selectedQuery][index]
this.setState({
schedulesToExport: this.state.schedulesToExport.concat(selected)
})
}
That's it, now you a have a list of querys being displayed conditionally rendered selectedQuery props is just a index, but could be a property's name. You only see schedules from the current selected query, so when you click on schedule we just return it's index, and the state.querys[selectedQuery][index] will be your selected schedule, that is securely store in the state on a separated list.
I have updated your sandbox here.
In essence, it does not work in your example because of the following:
schedules
.filter(schedule =>
scheduleIdsToExport.includes(Number(schedule.id))
)
.map(schedule => {
return (
<Table.Row>
...
</Table.Row>
);
})
The value of schedules is always set to the current query, hence you end up showing schedules to export for the current query only.
A solution that changes very little of your code is to ditch scheduleIdsToExport altogether, and use schedulesToExport instead. Initially, we'll set schedulesToExport to an empty
object; we'll add schedules to it (keyed by schedule id) every time a schedule is selected - we'll delete schedules in the same way every time a schedule is unselected.
class App extends React.Component {
// ...
slider = ({ id, isExported }) => {
const obj = this.state.schedules.find(s => Number(s.id) === Number(id));
if (isExported === true) {
this.setState({
schedulesToExport: {
...this.state.schedulesToExport,
[id]: {
...obj
}
}
});
} else {
const newSchedulesToExport = this.state.schedulesToExport;
delete newSchedulesToExport[id];
this.setState({
schedulesToExport: newSchedulesToExport
});
}
};
// ...
}
You would then render the schedules to export as follows:
Object.keys(schedulesToExport).map(key => {
const schedule = schedulesToExport[key];
return (
<Table.Row>
...
</Table.Row>
);
})
Again, see more details in sandbox here.
For this example of React-Table that uses multi-select, any ideas on how to not use a select box and just be able to click the row... and then do the REVERSE of what this example does...
That the clicked row is filtered out and saved in state, but then the remainder of the table is re-rendered? Perhaps actually the state is all the data and we remove? But this would be very very bad if we have millions of records.
https://codesandbox.io/s/3x51yzollq
To be able to click the row and select it you can use getTrProps
data={this.state.data}
getTrProps={(state, rowInfo, column) => {
if (rowInfo && rowInfo.original) {
return {
onClick: () => {
// handle select
this.handleSingleCheckboxChange(rowInfo.index);
}
};
} else {
return {};
}
}}
And in the function that handle select, you can filter out and rerender
handleSingleCheckboxChange = index => {
//filter and setState to change data
let newData = this.state.data;
newData = newData .filter(value => value !== newData[index]);
this.setState({ data: newData });
};