Antd Modal how to update the value in it - javascript

I'm using Antd Modal to display order information by passing props to TableDetail Component. The thing is when I try to remove the items in the order I use setState and the modal won't update its data.
https://codesandbox.io/s/antd-reproduction-template-forked-vu3ez
A simple version of reproduction is provided above.
enter image description here
After I click on the remove button the items remain unchanged but when I close and open the modal it will update.
enter image description here
What should I do to make it change? I tried forceUpdate() but it won't work.
Here is the TableDetail component.
export default class TableDetail extends Component {
constructor(props) {
super(props);
this.state = {
tableOrder: props.tableOrder,
column: []
};
}
tableInitializer = () => {
const column = [
{
title: "name",
render: (item) => {
return <span>{item.name}</span>;
}
},
{
title: "price",
render: (item) => {
return item.price;
}
},
{
title: "amount",
render: (item) => {
return <span>{item.amount}</span>;
}
},
{
title: "removeButton",
render: (item) => {
return (
<Button
type="link"
style={{ color: "red" }}
onClick={() => this.props.removeItem(item)}
>
Remove
</Button>
);
}
}
];
this.setState({
column
});
};
UNSAFE_componentWillMount() {
this.tableInitializer();
}
render() {
return (
<Modal
visible={true}
title={
<span style={{ fontSize: "1.5rem", fontWeight: "bold" }}>
Table: {this.state.tableName}
</span>
}
cancelText="Cancel"
okText="OK"
onCancel={this.props.hideModal}
>
<Table
columns={this.state.column}
dataSource={this.state.tableOrder}
rowKey="name"
/>
</Modal>
);
}
}
TableDetail.propTypes = {
tableOrder: PropTypes.array.isRequired,
tableName: PropTypes.string.isRequired,
hideModal: PropTypes.func.isRequired,
removeItem: PropTypes.func.isRequired
};

You can use the filter method in your removeItem function
removeItem = (item) => {
const filtered = this.state.tableOrder.filter(
(order) => item.name !== order.name
);
console.log(item, filtered);
this.setState({
tableOrder: filtered
});
};
and one more thing you should avoid assigning your props to state like
this.state = {
tableOrder: props.tableOrder,
};
instead of assigning to state directly use that as props like
<Table
dataSource={this.props.tableOrder}
/>
In most cases, this is an antipattern. Don’t β€œcopy props into state.”
It creates a second source of truth for your data, which usually leads
to bugs. One source of truth is best.
Improved code Live demo

The issue in your code is in removeItem. You are trying to mutate tableOrder. According to react docs:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
So that is why it is kind of asynchronous and you can't see your updated changes. You need to do this:
removeItem = (item) => {
const { tableOrder } = this.state;
const newTable = [...tableOrder]; <- Create new variable and update variable
for (let i = 0; i < newTable.length; i++) {
if (item.name === newTable[i].name) {
console.log("Got it! " + newTable[i].name);
newTable.splice(i, 1);
console.log("new table: ", tableOrder);
break;
}
}
this.setState({
tableOrder: newTable <- update here
});
};
You dont need to actually modify array you can simply do this also:
this.setState(
{
tableOrder: this.state.tableOrder.filter(data => !(data.name===item.name))
}
)
//filter creates new array
Here is demo: https://codesandbox.io/s/antd-reproduction-template-forked-j4bfl?file=/tables.js

Related

react-widgets DropDownList dynamic load on demand

I would like to use the awesome react-widgets DropDownList to load records on demand from the server.
My data load all seems to be working. But when the data prop changes, the DropDownList component is not displaying items, I get a message
The filter returned no results
Even though I see the data is populated in my component in the useEffect hook logging the data.length below.
I think this may be due to the "filter" prop doing some kind of client side filtering, but enabling this is how I get an input control to enter the search term and it does fire "onSearch"
Also, if I use my own component for display with props valueComponent or listComponent it bombs I believe when the list is initially empty.
What am I doing wrong? Can I use react-widgets DropDownList to load data on demand in this manner?
//const ItemComponent = ({item}) => <span>{item.id}: {item.name}</span>;
const DropDownUi = ({data, searching, fetchData}) => {
const onSearch = (search) => {
fetchData(search);
}
// I can see the data coming back here!
useEffect(() => {
console.log(data.length);
}, [data]);
<DropDownList
data={data}
filter
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
Got it! This issue is with the filter prop that you are passing to the component. The filter cannot take a true as value otherwise that would lead to abrupt behavior like the one you are experiencing.
This usage shall fix your problem:
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix πŸ˜…
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
Working demo
The entire code that I had tried at codesandbox:
Warning: You might have to handle the clearing of the values when the input is empty.
I thought that the logic for this was irrelevant to the problem statement. If you want, I can update that as well.
Also, I added a fakeAPI when searchTerm changes that resolves a mocked data in 2 seconds(fake timeout to see loading state).
import * as React from "react";
import "./styles.css";
import { DropdownList } from "react-widgets";
import "react-widgets/dist/css/react-widgets.css";
// Coutesy: https://usehooks.com/useDebounce
import useDebounce from "./useDebounce";
interface IData {
id: string;
name: string;
}
const fakeAPI = () =>
new Promise<IData[]>((resolve) => {
window.setTimeout(() => {
resolve([
{
name: "NA",
id: "user210757"
},
{
name: "Yash",
id: "id-1"
}
]);
}, 2000);
});
export default function App() {
const [state, ss] = React.useState<{
searching: boolean;
data: IData[];
searchTerm: string;
}>({
data: [],
searching: false,
searchTerm: ""
});
const debounceSearchTerm = useDebounce(state.searchTerm, 1200);
const setState = (obj: Record<string, any>) =>
ss((prevState) => ({ ...prevState, ...obj }));
const getData = () => {
console.log("getting data...");
setState({ searching: true });
fakeAPI().then((response) => {
console.log("response: ", response);
setState({ searching: false, data: response });
});
};
React.useEffect(() => {
if (debounceSearchTerm) {
getData();
}
}, [debounceSearchTerm]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<DropdownList
data={state.data}
filter={() => true} // This was the miss/fix πŸ˜…
valueField={"id"}
textField={"name"}
busy={state.searching}
searchTerm={state.searchTerm}
onSearch={(searchTerm) => setState({ searchTerm })}
busySpinner={<span className="fas fa-sync fa-spin" />}
delay={2000}
/>
</div>
);
}
Let me know if you have more queries on this πŸ˜‡
So it i think that list should be loaded a then you can filtering your loaded data.In your example on the beginning you don't have value so list is empty, you tape in some text and then value of list re render but it look like is not filtered.....
However I look through code base, and it's look like is not ready until you don't set manually open prop drop down list component. In getDerivedStateFromprops, next data list is read only if in next props is open set. to true
From DropDwonList
static getDerivedStateFromProps(nextProps, prevState) {
let {
open,
value,
data,
messages,
searchTerm,
filter,
minLength,
caseSensitive,
} = nextProps
const { focusedItem } = prevState
const accessors = getAccessors(nextProps)
const valueChanged = value !== prevState.lastValue
let initialIdx = valueChanged && accessors.indexOf(data, value)
//-->> --- -- --- -- -- -- -- - - - - - - - - - --- - - --------
//-->>
if (open)
data = Filter.filter(data, {
filter,
searchTerm,
minLength,
caseSensitive,
textField: accessors.text,
})
const list = reduceToListState(data, prevState.list, { nextProps })
const selectedItem = data[initialIdx]
const nextFocusedItem = ~data.indexOf(focusedItem) ? focusedItem : data[0]
return {
data,
list,
accessors,
lastValue: value,
messages: getMessages(messages),
selectedItem: valueChanged
? list.nextEnabled(selectedItem)
: prevState.selectedItem,
focusedItem:
(valueChanged || focusedItem === undefined)
? list.nextEnabled(selectedItem !== undefined ? selectedItem : nextFocusedItem)
: nextFocusedItem,
}
}
I would try:
<DropDownList
data={data}
filter
open
valueField={id}
textField={name}
onSearch={onSearch}
busy={searching} />
};
if it will be works, then you just have to
manage your open state by yourself.

How to solve Cannot update during an existing state issue - react?

I have a function I calling it in render() method
and it's setState a Flag from the state.
So I got this error
Cannot update during an existing state transition (such as within
render).
I read about this error, and what I understand it's because I setState in render method and this is the wrong way.
So I'm forced to do it if u have any idea to handle this tell me.
The main idea about this function
I have an array of an object "Name as Tools" so in every object I have "id, name, count, price"
so that will render a three input in UI like this
and I have a boolean flag in-state "isEmpty" that checks every input in array before sending this data to the database.
Code
State = {
toolsUsed: [
{
id: 0,
name: '',
price: '',
count: '',
},
],
// Checker
isEmpty: false,
}
renderToolsUsed = () => {
const {toolsUsed} = this.state;
const tools = toolsUsed.map((item, i) => {
const {count, price, name, id} = item;
this.setState({
isEmpty: ['name', 'price', 'count'].every(key => item[key].length > 0),
});
return (
<View key={i} style={styles.tools}>
.... Inputs here ...
</View>
);
});
return tools;
};
JSX
render() {
return (
<View>
{this.renderToolsUsed()}
</View>
);
}
You can't update the state like this. It is like infinite loop. setState will trigger render, then render will trigger another setState, then you keep repeat the circle.
I don't know why you need isEmpty when you already have toolsUsed which you can use it to check if all input are empty.
Lets say if you insist to have isEmpty, then you can set it inside input change event.
The code is not tesed. I wrote the code directly from browser. But you can get the idea before the code.
renderToolsUsed = () => {
const { toolsUsed } = this.state;
const tools = toolsUsed.map((item, i) => {
return (
<View key={i} style={styles.tools}>
<TextInput value={item.name} onChangeText={(text) => {
this.setState({
toolsUsed: [
...toolsUsed.slice(0, i - 1),
{...item, name: text },
...toolsUSed.slice(i)
]
}, this.updateEmptyState)
}>
// other input here
</View>
);
});
// ...
};
updateEmptyState = () => {
this.setState({
isEmpty: this.state.toolsUsed.every(x => x.name === '' && x.price === '' && x.count === '')
})
}
The state is not designed to store all the data you have in the app
For what you need isEmpty inside the state?
To do this, use a global variable
Or check it out when you want it out of the render

set multiple states, and push to state of array in one onClick function

I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}

Even after passing updated value from parent to child, child is not rendered

In the parent component, I receive data from the server and then map this data into a jsx format. Inside this mapping I have a child component and try to pass a value from state of parent to child as a property, however when I update state of this value, the render function for child is not executed.
Expected behavior: As a user I see a list of items. If I click on an item it should become as checked.
export class ReactSample extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
mappedItems: [],
selectedIds: [],
isSelected: false,
clickedTripId: null
};
this.toggleSelection = this.toggleSelection.bind(this);
}
componentWillMount(){
console.log("Component mounting")
}
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({selectedIds:
state.selectedIds.concat(id)}));
this.setState(() => ({clickedTripId: id}));
this.mapItems(this.state.items);
}
}
componentDidMount() {
const self = this;
MyService.getItems()
.then(res => {
self.setState(() => ({ items: res.allItems }));
self.setState(() => ({ mappedItems:
this.mapItems(res.allItems) }));
}
)
}
mapItems (items) {
return items.map(trip => {
return (
<li key={trip.id} onClick={(e) => (this.toggleSelection(trip.id,
e))}>
<span>{trip.title}</span>
<Tick ticked={this.state.clickedTripId}/>
<span className="close-item"></span>
</li>
);
});
}
getItems() {
}
render() {
return (
<div>
<a className="title">This is a react component!</a>
<Spinner showSpinner={this.state.items.length <= 0}/>
<div className="items-container">
<ul id="itemsList">
{this.state.mappedItems}
</ul>
</div>
</div>
);
}
}
export class Tick extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('RENDER');
return (<span className={this.props.ticked ? 'tick display' :
'tick hide' }></span>);
}
}
I see a couple issues.
In toggleSelection you aren't doing anything with the result of mapItems. This kind of bug would be much easier to avoid if you just remove mappedItems from state and instead just call mapItems within your render method.
The other issue is you are passing this.state.clickedTripId as the ticked property. I assume you meant to pass something more like this.state.clickedTripId === trip.id.
As Ryan already said, the problem was that mappedItems where not updated when toggleSelection was clicked. As it is obvious from the code mapItems returns data in jsx format. To update it I had to call this.setState({mappedItems: this.mapItems(this.state.items)}) which means that I call mapItems and then I assign the result to the state. In this case my list will be updated and Tick component will receive this.state.clickedItemId as a tick property. There is one more issue that needs to be done to make this code working:
this mapped list needs to be updated after this.state.clickedItemId is updated. The method setState is asynchronous which means that this.setState({mappedItems: this.mapItems(this.state.items)}) has to be called only after this.state.clickedItemId is updated. To achieve this, the setState method can receive a callback function as a second parameter. The code snippet is the following:
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({
clickedItemId: id,
selectedIds: state.selectedIds.concat(id)
}), () => this.setState({mappedItems: this.mapItems(this.state.items)}));
}
}
In this case, at the time the mapItems function is executed all data from the state that is needed here will be already updated:
mapItems (items) {
return items.map(item => {
return (
<li key={item.id} onClick={(e) => (this.toggleSelection(item.id, e))}>
<span>{item.title}</span>
<span>{this.state.clickedItemId}</span>
<Tick ticked={this.state.clickedItemId === item.id}/>
<span className="close-item"></span>
</li>
);
});
}

Grid view is not being shown correctly

Grid view is not being shown with space, rows and column wise and When I am clicking delete menu item, it is passing the last array value (last card's value) to the function, not the clicked card's value. Something is wrong in Grid view.
Following is the data used by the cards. Import statements are there.
Array:
0: {id: "5", title: "Java", price: "78$"}
1: {id: "2", title: "C++", price: "79$"}
2: {id: "4", title: "C", price: "127$"}
3: {id: "1", title: ".Net", price: "65$"}
4: {id: "3", title: "React Js", price: "67$"}
This is the code of my component:
const styles = theme => ({
root: {
flexGrow: 1,
},
paper: {
padding: theme.spacing.unit * 2,
textAlign: 'center',
color: theme.palette.text.secondary,
},
card: {
maxWidth: 400,
},
media: {
height: 0,
paddingTop: '56.25%', // 16:9
},
actions: {
display: 'flex',
},
});
const ITEM_HEIGHT = 40;
class Products extends Component {
constructor() {
super();
this.state = {
products: [],
searchString: ''
};
this.getProducts()
}
state = {
anchorEl: null,
};
handleClick = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
delete = id => {
alert(id)
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
let updatedProducts = [...this.state.products].filter(i => i.id !== id);
this.setState({ products: updatedProducts });
});
}
getProducts() {
axios.get('http://localhost:9022/products/getAll')
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
}
onSearchInputChange = (event) => {
if (event.target.value) {
this.setState({ searchString: event.target.value })
} else {
this.setState({ searchString: '' })
}
this.getProducts()
}
render() {
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
const { classes } = this.props;
return (
<div>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<Grid container spacing={12}>
<Grid item xs={4} xm={4}>
<div className="row">
{this.state.products.map(currentProduct => (
<div key={currentProduct.id}>
<Card>
<CardHeader
action={
<IconButton aria-label="More"
aria-owns={open ? 'long-menu' : null}
aria-haspopup="true"
onClick={this.handleClick}>
<MoreVertIcon />
<Menu
id="long-menu"
anchorEl={anchorEl}
open={open}
onClose={this.handleClose}
PaperProps={{
style: {
maxHeight: ITEM_HEIGHT * 4.5,
width: 100,
},
}}
>
<MenuItem component={Link} to={'/products/' + currentProduct.id}>Edit
</MenuItem>
<MenuItem onClick={() => this.delete(currentProduct.id)}>Delete
</MenuItem>
</Menu>
</IconButton>
}
title={currentProduct.title}
/>
<CardContent>
<Typography component="p">
{currentProduct.id}
</Typography>
</CardContent>
</Card>
</div>
))}
</div>
</Grid>
</Grid>
</div>
)
}
}
export default withStyles(styles)(Products);
I see what the problem is. It's a problem with your code logic.
What you're trying to do in the action section of the CardHeader is rendering a Menu that has two static items in it
<MenuItem component={Link} to={'/products/' + currentProduct.id}>Edit</MenuItem> <MenuItem onClick={() => this.delete(currentProduct.id)}>Delete</MenuItem>
The thing is the Menu has to have a unique id, but each time you render it you give the same => simple-menu instead you could do something like simple-menu-${currentProduct.id}. And the best to do is that you render a separate component from the CardHeader instead of actions.
This gives you more control over you component and each element you want to render.
See and edit it here:
I personally don't like to put a Menu inside of that card, instead I'd put icons to the top left/right of the card.
Uncomment the action property in the CardHeader and comment out the component one to see what I mean!
I hope that's clear, let me know if it isn't!
There are some things that you can fix which should solve all your problems, or at least guide you through the right direction.
I will guide you through, but it would be ideal if I you read the docs first.
You are calling this.getProducts() inside your constructor, and that function uses setState
You are initialising and setting anchorEl in the state outside of the constructor
You are calling super without passing on props, which might lead to bugs
You are not binding functions that use this (handleClick, handleClose, getProducts, etc), which could lead to undefined state of this.
You are calling functions that get values from the state right after calling setState, which might not get the correct values because of how setState works in React.
You should avoid all.
Constructor, bindings, first time fetch
Constructor
Constructor from the official docs:
You should not call setState() in the constructor(). Instead, if your
component needs to use local state, assign the initial state to
this.state directly in the constructor:
The constructor for a React component is called before it is mounted.
When implementing the constructor for a React.Component subclass, you
should call super(props) before any other statement. Otherwise,
this.props will be undefined in the constructor, which can lead to
bugs.
Typically, in React constructors are only used for two purposes:
Initializing local state by assigning an object to this.state.
Binding event handler methods to an instance.
Your code:
constructor() {
super();
this.state = {
products: [],
searchString: ''
};
this.getProducts()
}
state = {
anchorEl: null,
};
Change it to:
constructor(props) {
super(props);
this.state = {
products: [],
searchString: '',
anchorEl: null,
};
this.onSearchInputChange = this.onSearchInputChange .bind(this);
this.getProducts = this.getProducts.bind(this);
this.handleClick = this.handleClick.bind(this);
this.handleClose = this.handleClose.bind(this);
}
First time fetch
To call this.getProducts() when the app starts, don't use the constructor, use componentDidMount instead.
componentDidMount from the official docs:
componentDidMount() is invoked immediately after a component is
mounted (inserted into the tree). Initialization that requires DOM
nodes should go here. If you need to load data from a remote endpoint,
this is a good place to instantiate the network request.
Create this function inside the component:
componentDidMount(){
this.getProducts();
}
Bindings
Binding from the official docs:
There are several ways to make sure functions have access to component
attributes like this.props and this.state, depending on which syntax
and build steps you are using
- Bind in Constructor (ES2015)
- Class Properties (Stage 3 Proposal)
- Bind in Render
You can use any of these, but I suggest you use the first one.
Your functions:
handleClick = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
Change them to:
handleClick(event) {
this.setState({ anchorEl: event.currentTarget });
};
handleClose() {
this.setState({ anchorEl: null });
};
setState
Correct use of setState
setState from the official docs
setState(updater[, callback])
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
setState() does not always immediately update the component. It may
batch or defer the update until later. This makes reading this.state
right after calling setState() a potential pitfall. Instead, use
componentDidUpdate or a setState callback (setState(updater,
callback)), either of which are guaranteed to fire after the update
has been applied. If you need to set the state based on the previous
state, read about the updater argument below.
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
So you should not do this:
onSearchInputChange = (event) => {
if (event.target.value) {
this.setState({ searchString: event.target.value })
} else {
this.setState({ searchString: '' })
}
this.getProducts()
}
Because you cannot guarantee that when this.getProducts() is called, the previous setState functions have finished. This means that it might work most of the times, but there would be some cases when React hasn't finished updating the state and you are already calling this.getProducts().
Instead, you should call this.getProducts() once setState has finished, and to guarantee that just use the callback like this (and I am also changing the function's declaration because we already bound it in the constructor with the previous change):
onSearchInputChange(event) {
let newSearchString = '';
if (event.target.value) {
newSearchString = event.target.value;
}
// call getProducts once React has finished updating the state using the callback (second argument)
this.setState({ searchString: newSearchString }, () => {
this.getProducts();
});
}
Your getProducts is OK (now that we bound it in the constructor), but you are calling console.log when it should not be called:
getProducts() {
axios.get('http://localhost:9022/products/getAll')
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
}
Based on the previous explanation of setState, call it like this:
getProducts() {
axios.get('http://localhost:9022/products/getAll')
.then(res => {
this.setState({ products: res.data }, () => {
console.log(this.state.products);
});
});
}
Your delete function
Assuming that your data is in fact an array, like this:
products: [
{id: "5", title: "Java", price: "78$"}
{id: "2", title: "C++", price: "79$"}
{id: "4", title: "C", price: "127$"}
{id: "1", title: ".Net", price: "65$"}
{id: "3", title: "React Js", price: "67$"}
]
the code that you have should work with the previous changes in the component. But, there is something you can improve as well.
This is your code:
delete = id => {
alert(id)
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
let updatedProducts = [...this.state.products].filter(i => i.id !== id);
this.setState({ products: updatedProducts });
});
}
I will refer back to the docs to the setState documentation where the updater function is explained:
setState(updater[, callback])
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is
being applied. It should not be directly mutated. Instead, changes
should be represented by building a new object based on the input from
state and props. For instance, suppose we wanted to increment a value
in state by props.step:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
Both state and props received by the updater function
are guaranteed to be up-to-date. The output of the updater is
shallowly merged with state.
It's important to understand when to use this updater function, and the state param in that function.
The easiest case is the one they mention:
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
This could be done like this:
this.setState({counter: this.state.counter + this.props.step});
But since you cannot guarantee that setState has been successful and that it has finished updating the values, you should do it using the updater function.
Now, back to your delete function.
Change this:
delete = id => {
alert(id)
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
let updatedProducts = [...this.state.products].filter(i => i.id !== id);
this.setState({ products: updatedProducts });
});
}
to this (notice that I changed the parameter name state to prevState in the updater function so that it makes more sense and it's easier to understand):
delete = id => {
alert(id);
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
// To guarantee you get the correct values, get them from the state in the updater function in setState
this.setState((prevState, prevProps) => {
// This happens inside the setState function
let updatedProducts = [...prevState.products].filter(i => i.id !== id);
// The updater function must return the values that will be modified in the state
return ({
products: updatedProducts
});
});
});
}
It's important to notice that filtering before setState like this:
let updatedProducts = [...this.state.products].filter(i => i.id !== id);
this.setState({ products: updatedProducts });
Will work most of the times but it is not recommended, use the updater function instead when handling this situations to ensure everything works every time.

Categories

Resources