React not re-rendering when state changed from hook - reactjs

Edit: Thanks for the help everyone. I needed to change the reference of the array and fixed it by doing:
setData([...sorted])
I am currently rendering out a list of tasks. This is a snippet of my return function within a functional component:
const [ data, setData ] = useState( mockData )
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>
When I click on a certain button on the page, the dataset gets sorted and I call setData(sortedData)
For some reason, the table isnt being re-rendered with the sorted data. Is there something I did wrong here?
This is the sort function:
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(sorted)
}

Its because you are using the same ref of the array, you need set the new data with
setData(old => "sorted data");
to change the reference of the state and it updates
function filterByContactAmount():void {
let sorted = data.sort((a:any, b:any) => {
let aTimesContacted:number = a.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
let bTimesContacted:number = b.data.person.contact.list.reduce((acc:number, val:any):number => acc + val.history.length, 0)
if ( aTimesContacted > bTimesContacted ) {
return 1
}
if ( bTimesContacted > aTimesContacted ) {
return -1
}
return 0;
})
console.log(sorted)
setData(old => [...sorted]) // Sorted is the new state sorted
}

You are mutating sate, the other answer is probably not the best because you are still mutating state and then setting state with a copy of the already mutated value.
The sort function can also be optimized. Maybe try the following:
function filterByContactAmount() {
let sorted = data
.map(d => ({//map shallow copies the array
...d,//shallow copies the item
sortedNum: d.data.person.contact.list.reduce(//do this once for every item, not for every time sort callback is called
(acc, val) => acc + val.history.length,
0
),
}))
.sort((a, b) => a.sortedNum - b.sortedNum);
console.log(sorted);
setData(sorted);
}

I think issue is located under d.claimable, I suppose it is boolean variable type. You must know that every key prop must be unique. Check if you have for example.id property, if not add it.
Uniqueness of key prop is very important during reconciliation process.
Unique identifier with a group of children to help React figure out which items have changed, have been added or removed from the list. It’s related to the “lists and keys” functionality of React described here.
Very nice article about reconciliation.
<tbody>
{ data.map(d => <TaskItem key={d.claimable} task={d}/>) }
</tbody>

Related

React Hooks - Infinite loops when setting a state within a map

For reference: I am working on a sliding puzzle game.
So I have a const Board function and I have defined 2 states named:
puzzlePieces
hiddenIndexNumber
The point of 'hiddenIndexNumber' is to keep track of the hidden block index within the game. So before the game starts, I loop through a new array that I create for puzzlePieces and use map to return HTML elements. When looping, I want to make sure that I get the hidden block index for my hiddenIndexNumber to keep track of the hidden block.
This is how my code (partially) looks:
const Board = () => {
const totalPieces = 9
const hiddenNumber = totalPieces
const[hiddenIndexNumber, setHiddenIndex] = useState(-1)
// here I create an array of 9 elements and shuffle them with underline
const [puzzlePieces, changePuzzlePieceContent] = useState(
_.shuffle( [ ...Array( totalPieces ).keys() ].map( num => num + 1 ) )
)
let puzzleElements = [ ...Array( totalPieces ).keys() ].map( index => {
// the problem here is that setHiddenIndex makes the framework rerender
// the DOM after setting the index number and I don't know how to solve the issue here
if( puzzlePieces[index] === hiddenNumber ) {
setHiddenIndex(index)
}
return <Puzzle
key = { index }
index = { index }
number = { puzzlePieces[index] }
hidden = { puzzlePieces[index] === hiddenNumber && true }
onChange = { handleChange }
/>
} )
}
The problem is with this code:
if( puzzlePieces[index] === hiddenNumber ) {
setHiddenIndex(index)
}
How do I make sure that I set hiddenIndexNumber without requesting for rerendering the DOM?
I would suggest you to look into shouldComponentUpdate() and useEffect() Hooks.
It's well described here: shouldComponentUpdate()? with an example.

React : Pushing result of map() to an array

Hello I am trying to map through an array of objects and push them to a new array.
My ISSUE : only the last item of the object is being pushed to the new array
I believe this has to do with React life cycle methods but I don't know where I should I loop and push the values to the array to get the full list
//My object in an array named states
var states = [{"_id":"Virginia","name":"Virginia","abbreviation":"VN","__v":0},{"_id":"North Carolina","name":"North Carolina","abbreviation":"NC","__v":0},{"_id":"California","name":"California","abbreviation":"CA","__v":0}];
export function StateSelect()
{
**EDIT 1**
const options = [];
function getStates()
{
//This is how I am looping through it and adding to an array
{ states.length > 0 &&
states.map(item =>
(
console.log(`ITEM: ${JSON.stringify(item)}`),
options.push([{ value: `${item.name}`, label: `${item.name}`}])
))
}
}
return( {getStates()}: );
}
Thank you
It looks like your getStates() might not even be returning anything... but assuming it is, I believe you should be able to accomplish this using a forEach() fn in order to push values into your options array... Try adding the following into your map:
states.map((item) => {
console.log(`ITEM: ${JSON.stringify(item)}`);
let processed = 0;
item.forEach((i) => {
options.push([{ value: `${i.name}`, label: `${i.name}`}]);
processed++;
if(processed === item.length) {
// callback fn, or return
}
}
.map usually used to return another result, you could just use .forEach
In fact, you don't really need to declare options at all, just use .map on state to return the result would be fine.
return states.length > 0 && states.map(({ name }) => {
return { value: name, label: name };
});

drag to reorder and save state with reactjs

I'm writing a small app that uses the react-sortable-hoc
everything is great but im having issues displaying the list ordered by order
I have
user 0
user 1
user 2
when I drag user 2 above user 0
instead of getting
user 2
user 0
user 1
I get
user 2
user 1
user 0
I think It has to do with the way I'm setting the order in the state. but I can't figure it out.
this is how I set the order on sort end
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
const newItems = [...prevState];
newItems[newIndex].order = oldIndex;
newItems[oldIndex].order = newIndex;
return newItems.sort((a, b) => a.order - b.order);
})
};
here's the app running so you can play with it.
https://codesandbox.io/s/winter-https-xelrd?fontsize=14&hidenavigation=1&theme=dark
I have fixed it,
here is the working url to play with https://codesandbox.io/s/quizzical-colden-rm62y
You were correct in guessing that the problem was with the onSortEnd function. Instead of swapping the newIndex and oldIndex position we just need to either bubble them up or down.
Here is a working code, it can be cleaned up a bit, but you got the idea :)
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
const newItems = [...prevState];
if (oldIndex > newIndex) {
for (let i = oldIndex - 1; i >= newIndex; i--) {
newItems[i].order++;
newItems[oldIndex].order = newIndex;
}
} else if (oldIndex < newIndex) {
for (let i = oldIndex + 1; i <= newIndex; i++) {
newItems[i].order--;
newItems[oldIndex].order = newIndex;
}
}
return newItems.sort((a, b) => a.order - b.order);
});
};
Hope it helps. Happy coding :)
What you do is swapping.
If you want to just "insert" the element in the new position you will have to update all the items between the two positions.
In your case, one approach would be to just move the element and re-create the order for all items
setUsers(prevState => {
const newItems = [...prevState];
newItems.splice(newIndex, 0, newItems.splice(oldIndex, 1)[0]).forEach((item,index)=>{
item.order = index;
});
return newItems
});
Demo at https://codesandbox.io/s/confident-river-mrh3p
So looks like your code is simply swapping the elements. This does not seem like what you really want to do. In fact you really want to remove the element and insert it at a given position. I think since you already have the oldIndex and newIndex, you can approach the sort function as follows:
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
var newItems = [...prevState];
let elem = newItems[oldIndex]
newItems.splice(oldIndex, 1)
newItems.splice(newIndex, 0, elem)
return newItems
});
};
There isn't really a need for order and is capturing more than the minimum state required (unless you use it elsewhere).

ReactJS : Using function as props to return value for <td> gives <Component/> error

I have the following function:
budgetList() {
return this.state.projects.map(currentproject=> {
return <Budget project={currentproject} filter= {this.filterIssues} key={currentproject._id} stop={this.stopPropagation} modal={this.state.modal} toggle={this.toggle}/>;
})
}
My issue comes from the filterIssues function which is used to filter through the state and retrieve some values. Posted below.
filterIssues (id, sprint) {
let filteredIssues = this.state.jiras
.filter( issue => issue.fields.project.key === id )
.filter( issue => issue.fields.sprint.name === sprint )
let sum = 0;
for (var i = 0; i < filteredIssues.length; i++) {
sum += filteredIssues[i].fields.timetracking.originalEstimateSeconds
}
sum /= 3600;
console.log(sum)
return sum;
}
Now I am using the function to retrieve a value for each in the Budget element like so.
const Budget = props => (
<tr onClick={() => props.toggle(props.project._id)}>
<td>£{((props.project.proposedBudget / 4) *1000) }</td>
<td>{() => props.filter(props.project.project, "APP" + 74)}</td>
<td>{() => props.filter(props.project.project, "APP" + 75)}</td>
</tr>
)
The problem is that for every
Functions are not valid as a React child. This may happen if you
return a Component instead of from render. Or maybe you
meant to call this function rather than return it.
Not exactly sure what I am doing wrong. Function works fine on its own outside the Budget component.

Best way to remove a specific item in AsyncStorage

I'm storing an array of objects in AsyncStorage and would like to know the best way to remove a specific object. Right now I´m passing an id to my function and then I loop through the array to match the id and remove the object and then the array in AsyncStorage is updated. This seems to work ok, but I wonder if this is optimal or if there is a better way to do this?
My function right now:
export function removeData(id) {
AsyncStorage.getItem('#books')
.then((books) => {
const updatedBooks = (JSON.parse(books))
for (let i = 0; i < updatedBooks.length; i++) {
if(updatedBooks[i].id == id) {
updatedBooks.splice(i, 1);
}
}
AsyncStorage.setItem('#books', JSON.stringify(updatedBooks));
})
}
My function for adding data to AsyncStorage:
export function addData(book) {
AsyncStorage.getItem('#books')
.then((books) => {
const b = books ? JSON.parse(books) : [];
b.push(book);
AsyncStorage.setItem('#books', JSON.stringify(b));
});
}
Button to add data with sample data to show structure:
<Button
title = "Add book"
onPress={() => addData({
id: 1,
title: 'Harry Potter',
author:'J.K. Rowling',
thumbnail:'https://covers.openlibrary.org/w/id/7984916-M.jpg',
})
To remove single item
AsyncStorage.removeItem('key', (err) => {
// key 'key' will be removed, if they existed
// callback to do some action after removal of item
});
To remove multiple items
let keys = ['k1', 'k2'];
AsyncStorage.multiRemove(keys, (err) => {
// keys k1 & k2 removed, if they existed
// callback to do some action after removal of item
});
Reference:
RemoveItem method
MultiRemove method

Resources