The application renders a table of an array of values in table rows, the mission is to detect the new table rows that are rendered after doing an action 'REFRESH' the table and pass them a new class name for the new rows.
As a new to react and redux , I can do it right , There are many methods I know.
We can create an array called oldResult = []
pass the old rows to it
loop over oldResult[] to detect the new values
here's the function :
let oldOrders = []
const getNewOrders = (orders) => {
oldOrders.push(OrderList.orders);
for (let i in orders){
if (orders.i != oldOrders[i]){
return this.order.state = 'newClickablerow'
}
}
}
here's rendering of the table rows:
<tr key={i.toString()}
onClick={_onClick}
className={'clickableRow'}
onMouseLeave={() => this.props.selectOrder(null)}
onMouseEnter={() => this.props.selectOrder(order)}>
<td data-label='Bewertung'>
{this._renderScore(score, order.creation_date)}
</td>
<td data-label='Börse'>
<img src={TranseuLogo} className="ordertable-logo"/>
</td>
<td data-label='Von'>
{renderLocation(order.pickup.location)}
</td>
<td data-label='Nach'>
{renderLocation(order.delivery.location)}
</td>
<td data-label='Vor-/Haupt-/Nachlauf'>
{renderRoute(deadhead, order.route, bobtail)}
</td>
<td data-label='Fracht'>
<CargoSummary
content={{bodytypes: this.props.content.bodytypes}}
cargo={order.cargo}/>
</td>
</tr>)
and this is the reducer which pass the values to the table :
function orders(state = [], action) {
switch(action.type) {
case RECEIVE_ORDERS:
return action.orders;
case RECEIVE_ORDERS_ERROR:
return []
case REFRESH:
default:
return state;
}
}
I dont know how to fire the function and use it when the REFRESH action is fired , what should i do in case of refresh?
and how to change the state (class name of the table row) ?
I searched and found that I can do that by changing the row state
constructor(props) {
super(props);
this.state = {
className : 'clickablerow'
}
}
Avoid redux state mutation to prevent bugs. Check out the Redux documentation for explanation and code examples: http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html#inserting-and-removing-items-in-arrays
Depending on your exact use case (like update / add / remove items) you could map over and compare every item using the array index, and add a boolean property like "isNew" on the order object. If you want to also check for updated items and if items can be removed then it would be better to use an object with unique IDs as the key to loop over every item.
You don't need to use local "state" on the order if you just want to render the className when updated from redux state, unless you also want to change it afterwards (like clicking it or removing the className again after a few seconds).
If you want to compare the whole object properties you could use utility methods like lodash.isEqual and lodash.omit to remove the "isNew" property before comparing to the new value, otherwise if you just want to add/remove items you can only compare the ID for example...
Didn't test this code but with using arrays of order objects and just checking if the object at the same position has the same ID to add "isNew" boolean value it would look something like this:
function orders(state = [], action) {
switch(action.type) {
case RECEIVE_ORDERS:
return action.orders;
case RECEIVE_ORDERS_ERROR:
return []
case REFRESH:
return refreshOrders(state.orders, action.orders)
default:
return state;
}
}
function refreshOrders (oldOrders, newOrders) {
return newOrders.map((newOrder, i) => {
// compare ID for same index and add a property isNew if it's different
const oldOrder = oldOrders[i] || {} // default to empty object if not found
const isNew = oldOrder.id === newOrder.id
return { ...newOrder, isNew }
})
}
Updated and simplified my answer, since I don't fully understand your requirements based on the explanation, so if this is enough to understand please mark this as answer or further explain what exactly you need to do (if you need to add / remove and also update items and if this class will only change after refreshing etc.)
Related
I currently dynamically render the same component when clicking a button and the latest component is rendered on the top of the list.
Now, I want to delete the component. Each component has a cancel button to delete the rendered component. So I should be able to delete the component no matter where it is in the list.
Here's what I have so far:
local state:
state = {
formCount: 0
}
add and cancel:
onAddClicked = () => {
this.setState({formCount: this.state.formCount + 1});
}
onCancelButtonClicked = (cancelledFormKey: number) => {
const index = [...Array(this.state.formCount).keys()].indexOf(cancelledFormKey);
if (index > -1) {
const array = [...Array(this.state.formCount).keys()].splice(index, 1);
}
}
Parent component snippet:
{ [...Array(this.state.formCount).keys()].reverse().map(( i) =>
<Form key={i}
onCancelButtonClicked={() => this.onCancelButtonClicked(i)}
/>)
}
The only thing is I'm not sure how to keep track of which form was cancelled/deleted. I think I would need to create a new object in my local state to keep track but how do I know which index of the array I deleted as part of state? I'm not sure how do that? As I am using the count to make an array above.
Usually, this isn't how you'd generate a list of items. You're not storing the form data in the parent, and you're using index based keys which is a no-no when you're modifying the array. For example, I have an array of size 5 [0, 1, 2, 3, 4], when I remove something at position 2, the index of all the items after it changes causing their key to change as well, which will make react re-render them. Since you're not storying the data in the parent component, you will lose them.
Just to humor you, if we want to go with indexed based keys, we may have to maintain a list of removed indexes and filter them out. Something like this should do the trick:
state = {
formCount: 0,
deletedIndex: []
}
onCancelButtonClick = (cancelledIndex: number) => setState((prevState) => ({
deletedIndex: [...prevState.deletedIndex, cancelledIndex]
});
And your render would look like:
{
[...Array(this.state.formCount)].keys()].reverse().map((i) => (
if (deletedIndex.includes(i) {
return null;
} else {
<Form key={i} ... />
}
))
}
As a rule of thumb though, avoid having index based keys even if you don't care about performance. It'll lead to a lot of inconsistent behavior, and may also cause the UI and the state to be inconsistent. And if you absolutely want to for fun, make sure the components that are being rendered using index based keys have their data stored at the parent component level
I'm updating an object within react's state which I use to display a list. The state updates correctly, however the display breaks.
This is the section of the code from inside my render function which produces the list.
this.state.shoppingItems ? this.state.shoppingItems.currentShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
Here is the code that produces the previous items list:
this.state.shoppingItems ? this.state.shoppingItems.previousShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
This is the method which removes the item from the current list and adds it to the previous list, where the issue occurs.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
let shoppingItems = this.state.shoppingItems;
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
Here is the list before I remove the item.
Here is the list after I remove the middle item:
Finally here is the console.log output which shows that the items have updated properly but the display hasn't updated:
I'm entirely new to react coming from angular so I have no idea if this is the correct way to do this or if there is a better way. But could somebody help me figure out why the display isn't updating?
The issue seemed to be the key on the item in the map. I replaced the index with the item's id from the database as below and now it renders properly.
return <ItemSummary key={task._id} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={task} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
Similar answer here:
Change to React State doesn't update display in functional component
The issue is the update for shoppingItems. You save a reference to the current state object, mutate it, and store it back in state. Spreading this.state.shoppingItems into a new object first will create a new object reference for react to pick up the change of.
React uses shallow object comparison of previous state and prop values to next state and prop values to compute what needs to be rerendered to the DOM and screen.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
const shoppingItems = {...this.state.shoppingItems};
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
I had a similar issue with my application in which I had to delete comments made.
<textarea disabled key={note._id} className="form-control">{note.note}</textarea>
But the issue got resolved when I added the Key attribute to the list item.
Please know that I am new to ReactJS with Redux.
I have list of passengers, and each passenger has list of flights. I would like to update the flight property, checkedIn with the action property isCheckedIn. How can I achieve that with in reducer?
reducer.js
export default function passengerReducer(
state = initialState.passengers,
action
) {
switch (action.type) {
case types.LOAD_PASSENGERS_SUCCESS:
return action.passengers;
case types.UPDATE_PASSENGER_SUCCESS:
console.log("action ", action.passengerData.passengerId);
console.log("state ", state);
return state
.filter(x => x.id == action.passengerData.passengerId)
.map(f => {
f.flights[0].checkedIn = action.passengerData.isCheckedIn
});
default:
return state;
}
}
The state contains array of objects. Each object also contains flights. At the moment, I am only focusing the first flight with in flights array.
The action contains isCheckedIn property. I would like to update checkedInproperty of the flights with isCheckedIn property from action.
So this is the piece of code in question I presume:
case types.UPDATE_PASSENGER_SUCCESS:
console.log("action ", action.passengerData.passengerId);
console.log("state ", state);
return state
.filter(x => x.id == action.passengerData.passengerId)
.map(f => {
f.flights[0].checkedIn = action.passengerData.isCheckedIn
});
You're (a) filtering the array only for the element you want to change, and (b) mapping that but not returning anything from your map function
Just (a) alone is bad -- you're going to change your entire state to only include the filtered items? I don't think that's what you intended. But then (b) means you're returning an array full of undefined
What you want to do, instead, is create a new array, var newArray = state.slice(0);
Then, find the index of the item you want to change the checked_in property of,
var index = newArray.findIndex(x => x.id == action.passengerData.passengerId);
var newPassenger = Object.assign({}, newArray[index]);
newPassenger.flights[0].checkedIn = action.passengerData.isCheckedIn;
newArray[index] = newPassenger;
return newArray;
So you've found the item you wanted to change, changed it, put it back in the array (this is the immutable way to do things, I think), and then returned the FULL array
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(item){
item.expand = true;
this.setState({})
}
this.state.myArray.map((item) =>{
return <div onClick={()=>this.clickItem(item)}>{item.name}</div>
})
In React, i have a simple array of objects,
when i click on one of theses object, i want to change their prop and update the state, what is the proper way of doing this.
i feel like there could be a better way
You need to copy your state, update the copied state and the set the state.
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(key){
let items = this.state.myArray;
items[key].expand = true;
this.setState({items})
}
this.state.myArray.map((key, item) =>{
return <div onClick={()=>this.clickItem(key)}>{item.name}</div>
})
Okay, a couple of things.
You're mutating the state directly which is going to fail silently and you're also missing the key prop on your <div.
This is easily resolved though by using the data you have available to you. I don't know whether each name is unique but you can use that as your key. This helps React decide which DOM elements to actually update when state changes.
To update your item in state, you need a way to find it within the state originally, so if name is unique, you can use Array.prototype.find to update it.
clickItem(item) {
const targetIndex = this.state.items.find(stateItem => stateItem.name === item.name)
if (targetIndex === -1)
// Handle not finding the element
const target = this.state.items[targetIndex]
target.expand = !target.expand // Toggle instead of setting so double clicking works as expected.
this.setState({
items: this.state.items.splice(targetIndex, 1, target) // This replaces 1 item in the target array with the new one.
})
}
This will update state and re-render your app. The code is untested but it should work.
Assume I have the next piece of code inside the React component
removeItem = (item) => {
this.items.remove(item) // this.items -> mobx array
}
renderItem = (item, index) => {
var _item = undefined
switch (item.type) {
case "header":
_item = <Header key={item.id} onRemove={() => this.removeItem(item)} />
// a few more cases
// note that item.id is unique and static
}
// return _item -> works fine
return [
_item,
this.state.suggested
? <Placeholder key={-item.id} />
: null
]
}
render() {
return (
<div>
{this.items.map((item, i) => renderItem(item))}
</div>
)
}
Also assume that inside each of item I have a button that triggers onRemove handler with click. And each component has textarea where user can enter his text.
Obviously, when user enters text inside item's textarea, it should be saved until item will be removed.
The problem is when I remove some item, each item that goes after the removed one is being remounted (edited for Vlad Zhukov). It happens only when I return an array from renderItem(...) (I mean, when I return only item, this problem doesn't happen).
My question: is this a bug, or it's a feature? And how can I avoid it (desirable without wrapping item and Placeholder with another React child)?
UPDATED
I tried rewrite renderItem(...) the next way:
renderItem = (item, index) => {
var Item = undefined
switch (item.type) {
case "header":
Item = Header
// a few more cases
// note that item.id is unique and static
}
// return _item -> works fine
return [
<Item key={item.id} onRemove={() => this.removeItem(item)} />,
this.state.suggested
? <Placeholder key={-item.id} />
: null
]
}
And it still causes the problem.
Rerendering is absolutely fine in React and can be considered the main feature. What happens in your case is components remount when you make changes to an array of elements when these elements have no key props.
Have a look at this simple example. As you can see rerendering components has no difference but removing the first element will clear values of inputs below.
You've got 2 options:
Use a component instead of an array and set key to it (see an example). There is really no reason not to.
Remove all keys. The reason why it works is because React internally already uses keys for elements. However I wouldn't suggest this as it doesn't look reliable enough to me, I'd prefer to control it explicitly.