React toggle "Show All" and "Show Winners" doesn't work - reactjs

I have been struggling on this piece of codes which I suppose a button's clicked, the table will toggle between show all items and show winner items only.
Problem: The button has to be clicked two times to show winner items. Can't revert back to show all.
Do appreciate if someone can help. Thank you so much.
const MovieList = () => {
// Get Movies
const [movies, setMovies] = useState([])
const [winner, filterWinner] = useState(false)
const fetchMovies = async () => {
const res = await fetch('http://localhost:5000/data')
const data = await res.json()
return data
}
useEffect(() => {
const getMovies = async () => {
const moviesFromServer = await fetchMovies()
setMovies(moviesFromServer)
}
getMovies()
}, [])
//toggle between setting movies to all movies and winner movies.
//movie is an object that has a key and value pair "winner" : "True" or "winner" : "False"
const toggleWinner = () => {
filterWinner(!winner)
if (winner === true) {
const winners = movies.filter((movie) => movie.winner === 'True');
setMovies(winners);
} else {
setMovies(movies);
}
}
return (
<div className="container">
<h1>Movies</h1>
<hr />
<div>
<Button onClick={toggleWinner} color="info">{winner ? "Show All" : "Show Winners"}</Button>
</div>
<div>
<table className="table table-bordered table-striped">
<thead className="thead-dark">
<tr>
<th>Year</th>
<th>Film Name</th>
<th>Oscar Winner</th>
<th>Country</th>
</tr>
</thead>
<tbody>
{movies.map(movie => (
<tr key={movie.id}>
<td>{movie.year}</td>
<td>{movie.filmName}</td>
<td>{movie.winner}</td>
<td>{movie.country}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
export default MovieList;

The problem here is when you set state, that state will not be updated immediately, so you can't compare winner to true.
You can try this approach
const toggleWinner = () => {
//winner = false
filterWinner(prev => {
if(!prev) { // winner = true
const winners = movies.filter((movie) => movie.winner === "True");
setMovies(winners);
}
else {
setMovies(movies);
}
return !prev
});
};
Another problem is that you mutated the movies, so when you toggle again, old movies value is gone.
Check this codesandbox to see how I fixed that: https://codesandbox.io/s/frosty-browser-o8jeb?file=/src/App.js

If your state change depends on previous state value you need to call a function inside your update function.
const [state,setState]=useState(false);
If you want to toggle the state value you need to call update function like this.
setState(state=>!state)
In your case
filterWinner(winner=>!winner)
Note: you can use any name you want as argument inside update function.

Related

Apply sorting on all table users instead of the users of a single page

I have a table with 2 columns containing users info. I have divided the the table users in multiple pages, so that table of each page only displays 15 users. I have also implemented sorting on this table, so that when I click on each column header, the table is sorted according to this column. Here is the code:
import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
const Table = () => {
const [users, setUsers] = useState([]);
const [currentUsers, setCurrentUsers] = useState([]);
const [search, setSearch] = useState('');
const [isSorted, setIsSorted] = useState(false);
const [valueHeader, setValueHeader] = useState({title: "",body: ""}); //Value header state
const [sortedUsers, setSortedUsers] = useState([]);
const pageItemCount = 15
const [pageCount, setPageCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
useEffect(async () => {
try {
const response = await getUsers(search);
setUsers(response.data.users);
setPageCount(Math.ceil(response.data.users.length / pageItemCount))
setCurrentUsers(response.data.users.slice(0, pageItemCount))
} catch (error) { }
}, [search]);
const sortFn = (userA, userB) => {
// sort logic here, it can be whatever is needed
// sorting alphabetically by `first_name` in this case
return userA[valueHeader.body].localeCompare(userB[valueHeader.body]) //<== Use value of column header
}
useEffect(() => {
if (isSorted) {
setSortedUsers(currentUsers.slice().sort(sortFn))
} else {
setSortedUsers(currentUsers)
}
}, [isSorted, currentUsers, valueHeader]) //<== add valueHeader to dependency
const toggleSort = (target) => {
setIsSorted(!isSorted)
setValueHeader({
title: target,
body: target == "name" ? "first_name" : "mobile_number"
}) //<=== set state of value header
}
const changePage = (i) => {
setCurrentPage(i)
const startItem = ((i - 1) * pageItemCount) + 1
setCurrentUsers(users.slice(startItem - 1, (pageItemCount * i)))
}
const handleChange = (event, value) => {
changePage(value);
}
return (
<div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
<table className='w-full border-separate rounded-md'>
<thead>
<tr className='bg-text-secondary text-white shadow-sm text-center'>
<th className='p-2' onClick={()=>toggleSort("name")}>name</th>
<th className='p-2' onClick={()=>toggleSort("mobile")}>mobile</th>
</tr>
</thead>
<tbody>
{sortedUsers.map((item, index) =>
<tr key={item.id} className={index % 2 === 0 ? 'bg-white shadow-sm text-center' : 'bg-text bg-opacity-5 shadow-sm text-center'}>
<td className='text-text text-sm p-2'>{item.first_name}</td>
<td className='text-text text-sm p-2'>{item.mobile_number}</td>
</tr>
)}
</tbody>
</table>
<Pagination className="mt-2 pb-20" dir='ltr' page={currentPage} count={pageCount} onChange={handleChange} variant="outlined" shape="rounded" />
</div>
)
}
export default Table
The only problem is that, since I display only 15 users of the table in each page, when I click on the column header, only the users of that page is sorted, but I want to apply sorting on all users of the table (the users of all pages). Is it possible?
Edited code according to suggested answer:
import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
const Table = () => {
const [users, setUsers] = useState([]);
const [sortDirection, setSortDirection] = useState("asc");
const [search, setSearch] = useState('');
const pageItemCount = 15
const [pageCount, setPageCount] = useState(2);
const [currentPage, setCurrentPage] = useState(1);
useEffect(async () => {
try {
const response = await getUsers(search);
setUsers(response.data.users);
setPageCount(Math.ceil(response.data.users.length / pageItemCount));
} catch (error) { }
}, [search]);
useEffect(() => {
toggleSort("name");
}, [])
const startItem = (currentPage - 1) * pageItemCount + 1;
const pagedUsers = users.slice(startItem - 1, pageItemCount * currentPage);
const sortFn = (fieldToSort, direction) => (userA, userB) => {
if (direction === "asc")
return userA[fieldToSort].localeCompare(userB[fieldToSort]);
else return userB[fieldToSort].localeCompare(userA[fieldToSort]);
};
const toggleSort = (target) => {
const direction = sortDirection === "asc" ? "desc" : "asc";
const fieldToSort = target === "name" ? "first_name" : "mobile_number";
setSortDirection(direction);
setUsers(users.slice().sort(sortFn(fieldToSort, direction)));
};
const handleChange = (event, value) => {
setCurrentPage(value);
};
return (
<div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
<table className='w-full border-separate rounded-md'>
<thead>
<tr className='bg-text-secondary text-white shadow-sm text-center'>
<th className='p-2' onClick={()=>toggleSort("name")}>name</th>
<th className='p-2' onClick={()=>toggleSort("mobile")}>mobile</th>
</tr>
</thead>
<tbody>
{pagedUsers?.map((item, index) =>
<tr key={item.id} className={index % 2 === 0 ? 'bg-white shadow-sm text-center' : 'bg-text bg-opacity-5 shadow-sm text-center'}>
<td className='text-text text-sm p-2'>{item.first_name}</td>
<td className='text-text text-sm p-2'>{item.mobile_number}</td>
</tr>
)}
</tbody>
</table>
{users.length > 0 && (
<Pagination
className="mt-2 pb-20"
dir="ltr"
page={currentPage}
count={pageCount}
onChange={handleChange}
variant="outlined"
shape="rounded"
/>
)}
</div>
)
}
export default Table
You have 2 methods
the first is sorting the users array but this will change the users in this page
The second is wich i prefere is to call the sorting function in the changepage function
The error it's in this useEffect logic:
useEffect(() => {
if (isSorted) {
// When its sorted, you are setting to sort currentUsers,
// and it holds only the first 15 users, not the entire users array
setSortedUsers(currentUsers.slice().sort(sortFn))
} else {
setSortedUsers(currentUsers)
}
}, [isSorted, currentUsers, valueHeader])
The below code should work for you:
useEffect(() => {
if (isSorted) {
// get all users and sort it,
// returning an immutable array because the use of .slice()
const usersUpdated = users.slice().sort(sortFn).slice(0, pageItemCount);
// Updated the currentUsers and sortedUsers states
setSortedUsers(usersUpdated);
setCurrentUsers(usersUpdated);
} else {
// Updated the currentUsers and sortedUsers states with the first 15 users
setSortedUsers(users.slice(0, pageItemCount));
setCurrentUsers(users.slice(0, pageItemCount));
}
// instead call the useEffect base on currentUsers, you change it to users
}, [isSorted, users, valueHeader]);
Having said that, just a point - You are using to many states for user:
. one for all users
. one for current users
. one for sorted users
You can handle this with only one state, i did a code sample to you check it.
The main cause of your issue is the line:
setSortedUsers(currentUsers.slice().sort(sortFn))
when I click on the column header, only the users of that page is sorted
It's not that strange that only the users on the current page are sorted, since that's exactly what the line of code above does.
Instead you want to sort first, then take the currentUsers from the sortedUsers.
I've written an answer, but did overhaul your code. The reason being is that you violate the single source of truth principal. Often resulting in bugs or strange behaviour due to a mismatch between the different sources of truth.
Examples of source of truth duplication is the fact that you store 3 lists of users users, currentUsers, and sortedUsers. The pageCount is essentially stored 2 times. One can be calculated by Math.ceil(users.length / pageItemCount), the other is stored in the pageCount state.
What happens if you change the users array length, but forget to adjust the pageCount?
Instead of storing the pageCount in a state, you can derive it from two available values. So there is no need for a state, instead use useMemo.
const pageCount = useMemo(() => (
Math.ceil(users.length, pageItemCount)
), [users.length, pageItemCount]);
Similarly you could derive sortedUsers and currentUsers from users. If you have access to the order in which the users should be sorted, the current page, and the maximum page size.
I've extracted some of the logic into separate functions to keep the component itself somewhat clean. Since you haven't given us a snippet or environment to work with I'm not sure if the code below works. But it should hopefully give you some inspiration/insight on how to handle things.
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { getUsers } from '../../services/userService';
// Returns a new object without the given keys.
function without(object, ...excludeKeys) {
excludeKeys = new Set(excludeKeys);
return Object.fromEntries(
Object.entries(object).filter(([key]) => !excludeKeys.has(key))
);
}
// Compares the given property value in both users.
// Returns -1, 0, or 1, based on ascending comparison.
function compareUserProp(userA, userB, prop) {
const valueA = userA[prop];
const valueB = userB[prop];
if (typeof valueA === "string" && typeof valueB === "string") {
return valueA.localeCompare(valueB);
}
if (valueA < valueB) return -1;
if (valueA > valueB) return 1;
return 0;
}
function isEven(integer) {
return integer % 2 === 0;
}
function getUserTRClass(index) {
if (isEven(index)) {
return 'bg-white shadow-sm text-center';
} else {
return 'bg-text bg-opacity-5 shadow-sm text-center';
}
}
function Table({ maxPageSize = 15 }) {
const [users , setUsers ] = useEffect([]); // [{ first_name: "John", last_name: "Doe", age: 42 }]
const [search , setSearch ] = useEffect("");
const [order , setOrder ] = useEffect({}); // { last_name: "asc", age: "desc" }
const [currentPage, setCurrentPage] = useEffect(1);
const pageCount = useMemo(() => (
Math.ceil(users.length / maxPageSize)
), [users.length, maxPageSize]);
const sortedUsers = useMemo(() => {
const modifier = { asc: 1, desc: -1 };
return Array.from(users).sort((userA, userB) => {
for (const [prop, direction] of Object.entries(order)) {
const diff = compareUserProp(userA, userB, prop);
if (diff) return diff * modifier[direction];
}
return 0;
});
}, [users, order]);
const usersOnPage = useMemo(() => {
const zeroBasedPage = currentPage - 1;
const beginIndex = zeroBasedPage * maxPageSize;
const endIndex = beginIndex + maxPageSize;
return sortedUsers.slice(beginIndex, endIndex);
}, [sortedUsers, currentPage, maxPageSize]);
// Do not pass an async function directly to `useEffect`. `useEffect` expects
// a cleanup function or `undefined` as the return value. Not a promise.
useEffect(() => {
(async function () {
const response = getUsers(search);
setUsers(response.data.users);
// setCurrentPage(1); // optional, reset page to 1 after a search
})();
}, [search]);
const toggleSort = useCallback((prop) => {
const inverse = { "desc": "asc", "asc": "desc" };
setOrder((order) => {
const direction = order[prop] || "desc";
return { [prop]: inverse[direction], ...without(order, prop) };
});
}, []);
const changePage = useCallback((_event, newPage) => {
setCurrentPage(newPage);
}, []);
return (
<div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
<table className='w-full border-separate rounded-md'>
<thead>
<tr className='bg-text-secondary text-white shadow-sm text-center'>
<th className='p-2' onClick={() => toggleSort("first_name")}>name</th>
<th className='p-2' onClick={() => toggleSort("mobile_number")}>mobile</th>
</tr>
</thead>
<tbody>
{usersOnPage.map((user, index) => (
<tr key={user.id} className={getUserTRClass(index)}>
<td className='text-text text-sm p-2'>{user.first_name}</td>
<td className='text-text text-sm p-2'>{user.mobile_number}</td>
</tr>
))}
</tbody>
</table>
<Pagination className="mt-2 pb-20" dir='ltr' page={currentPage} count={pageCount} onChange={changePage} variant="outlined" shape="rounded" />
</div>
);
}
export default Table;
If you want to start the users of sorted simply set an initial value for order. For example:
const [order, setOrder] = useState({ last_name: "asc" });
The algorithm to solve this would be as follows
Maintain one state for your entire dataset.
state: allUsers
Capture the event of button click.
Applying sorting to the entire data based on event handler inputs you can decide the sort criterion.
allUsers.sort(criterionFunction);
// you may call an API for this step and bind result to allUsers if needed or do it on the client side.
Derive the slice of data set based on the limit and offset maintained in the local state.
usersInPage = allUsers.slice(offset,limit)
The derived data slice shall re-render itself on the pagination UI.
renderUsers(usersInPage)

Log one field from each element of an array

I have objects, in the database. I want to grab the userId using axios, but when I tried to console.log() it. It shows undefined. hen I hardcoded it and targeted it by array, it shows.
How can I console log all of userId? I would like to grab it so I can use it as an endpoint for my database
const res = await userRequest.get('user/find/'+userId)
I want to grab the userId only.
import React, { useEffect, useState } from 'react'
import { format } from 'timeago.js'
import { userRequest } from '../../requestMethod'
import './Widgetlg.css'
const WidgetLg = () => {
const Button = ({ type }) => {
return <button className={'widgetLgButton ' + type}>{type}</button>
}
const [orders, setOrders] = useState([])
const [users, setUsers] = useState([])
useEffect(() => {
const getOrders = async () => {
//this is just a shorcut api
try {
const res = await userRequest.get('orders')
setOrders(res.data)
console.log(res.data?.userId)
console.log(res.data)
console.log(res.data[0].userId)
} catch (error) {
console.log(error)
}
}
getOrders()
}, [])
useEffect(() => {
const getUsername = async () => {
try {
const res = await userRequest.get('user/find/')
setUsers(res.data)
} catch (error) {}
}
getUsername()
}, [])
return (
<div className="widgetLg">
<h3 className="widgetLgTitle">Latest Transactions</h3>
<table className="widgetTable">
<tr className="widgetLgTr">
<th className="widgetLgTh">Customer</th>
<th className="widgetLgTh">Date</th>
<th className="widgetLgTh">Amount</th>
<th className="widgetLgTh">Status</th>
</tr>
{orders.map((order) => (
<tr className="widgetLgTr">
<td className="widgetLgUser">
<span className="WidgetLgName"> **I want here to show the username** </span>
</td>
<td className="widgetLgDate"> {format(order.createdAt)} </td>
<td className="widgetLgAmmount">P {order.amount} </td>
<td className="widgetLgStatus">
<Button type={order.status} />
</td>
</tr>
))}
</table>
</div>
)
}
export default WidgetLg
You could try something like this if I understand you correctly
const userIdsArray = res.data.map(d => d.userId);
console.log(userIdsArray);
res.data is an array. To log all elements, you could just iterate over them:
res.data.forEach(el => console.log(el.userId));
The reason that console.log(res.data) gives undefined is that the array itself doesn't have a userId field, only the elements of the array do.

How to use .splice() property at React?

I'm new at Reactjs and in this case, I'm trying to show a list of operations. I need to show only the LAST 10 operations of the list and I'm trying to do this using .splice() on the array. I tried a lot but couldnĀ“t make it work.
I'm getting the following error:
TypeError: list is not iterable.
Any idea how to do this?
This is my component code so far:
export default function ListOperations() {
const dispatch = useDispatch();
// const list = useSelector((state) => state.operations);
const [list, setList] = React.useState({});
React.useEffect(async () => {
try {
const response = await axios.get("http://localhost:3000/operation");
dispatch({
type: "LIST_OPERATIONS",
list: response.data,
});
} catch (e) {
swal("Error", e.message, "error");
}
}, []);
const currentListCopy = [...list];
if (currentListCopy >= 10) {
currentListCopy.splice(10);
setList(currentListCopy);
}
return (
<div>
<div>
<h2>OPERATIONS HISTORY:</h2>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Reason</th>
<th>Amount</th>
<th>Date</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{list.map((oneOperation) =>
oneOperation ? (
<tr key={oneOperation.id}>
<td>{oneOperation.id}</td>
<td>{oneOperation.reason}</td>
<td>{oneOperation.amount}</td>
<td>{oneOperation.date}</td>
<td>{oneOperation.type}</td>
</tr>
) : null
)}
</tbody>
</table>
</div>
);
}
UPDATED VERSION:
export default function ListOperations(){
const dispatch = useDispatch();
const storeList = useSelector((state) => state.operations);
const [list, setList] = React.useState([]);
React.useEffect(async () => {
try{
const response = await axios.get('http://localhost:3000/operation');
dispatch({
type: 'LIST_OPERATIONS',
list: response.data
})
if(Array.isArray(storeList) && storeList.length){
const currentListCopy = [...storeList];
if(currentListCopy.length >= 10){
currentListCopy.splice(10);
setList(currentListCopy);
}
}
}
catch(e){
swal("Error", e.message, "error");
}
}, [storeList]);
There are a couple of issues, which are causing the error and also, if the error is fixed, the fetched results will not be shown in the application.
Issue 1
const [list, setList] = React.useState({});
In the above code, you're initializing state as an object, which is causing the error list is not iterable, in the below code, when you're trying to use the spread operator to create an array of state object.
const currentListCopy = [...list];
Fix
You can fix this issue by initialing the list state as an empty array.
const [list, setList] = React.useState({});
Issue 2
The second issue is you're dispatching an action in the useEffect hook, but not getting the updated state from the store, since this line // const list = useSelector((state) => state.operations); is commented out. Since you're not fetching any state from store also nor updating the local state list, you'll not see any changes in the map function, as its empty, even though some data is being returned from the network in the API call.
Fix
If you wish to use the state from the store to update the local store, than you've to uncomment this line // const list = useSelector((state) => state.operations) and rename list to something else.
Also you need to move your splice code to the useEffect hook, so, whenever the list updated in the global state, your local state also updated accordingly.
React.useEffect(() => {
if (Array.isArray(list) && list.length) { // assuming list is the global state and we need to ensure the list is valid array with some indexes in it.
const currentListCopy = [...list];
if(currentListCopy.length >= 10) { // as above answer point out
currentListCopy.splice(10);
setList(currentListCopy)
}
}
}, [list]); // added list as a dependency to run the hook on any change in the list
Also, as above answer point out, you should avoid async functions in the useEffect.
Update
the complete code
export default function ListOperations() {
const dispatch = useDispatch();
const storeList = useSelector((state) => state.operations);
const [list, setList] = React.useState([]);
React.useEffect(async () => {
try {
const response = await axios.get("http://localhost:3000/operation");
dispatch({
type: "LIST_OPERATIONS",
list: response.data,
});
} catch (e) {
swal("Error", e.message, "error");
}
}, []);
React.useEffect(() => {
if (Array.isArray(storeList) && storeList.length) {
const currentListCopy = [...storeList];
if(currentListCopy.length >= 10) {
currentListCopy.splice(10);
setList(currentListCopy)
}
}
}, [storeList]);
return (
<div>
<div>
<h2>OPERATIONS HISTORY:</h2>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Reason</th>
<th>Amount</th>
<th>Date</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{list.map((oneOperation) =>
oneOperation ? (
<tr key={oneOperation.id}>
<td>{oneOperation.id}</td>
<td>{oneOperation.reason}</td>
<td>{oneOperation.amount}</td>
<td>{oneOperation.date}</td>
<td>{oneOperation.type}</td>
</tr>
) : null
)}
</tbody>
</table>
</div>
);
}
if(currentListCopy >= 10){
currentListCopy.splice(10);
setList(currentListCopy)
}
you're missing "length" :
if(currentListCopy.length >= 10){
currentListCopy.splice(10);
setList(currentListCopy)
}
also, you shouldn't use promise inside useEffect
https://dev.to/danialdezfouli/what-s-wrong-with-the-async-function-in-useeffect-4jne

Set the local state using useEffect on Redux prop change

I am new to React Redux and I am trying to setState on a prop change in Redux using a useEffect hook.
I have the following code:
const DeploymentOverview = ({diagram, doSetDiagram}) => {
const { diagram_id } = useParams()
const [instances, setinstances] = useState(null)
const [error, seterror] = useState([false, ''])
useEffect(() => {
GetDiagram(diagram_id).then(d => doSetDiagram(d)).catch(err => seterror([true, err]))
}, [doSetDiagram])
useEffect(() => {
if (diagram) {
if (diagram.instances) {
let statusList = []
diagram.instances.forEach(instance => {
InstanceStatus(instance.key)
.then(status => statusList.push(status))
.catch(err => seterror([true, err]))
});
setinstances(statusList)
}
}
}, [diagram])
return (
<Container>
{error[0] ? <Row><Col><Alert variant='danger'>{error[1]}</Alert></Col></Row> : null}
{instances ?
<>
<Row>
<Col>
<h1>Deployment of diagram X</h1>
<p>There are currently {instances.length} instances associated to this deployment.</p>
</Col>
</Row>
<Button onClick={setinstances(null)}><FcSynchronize/> refresh status</Button>
<Table striped bordered hover>
<thead>
<tr>
<th>Status</th>
<th>Instance ID</th>
<th>Workflow</th>
<th>Workflow version</th>
<th>Jobs amount</th>
<th>Started</th>
<th>Ended</th>
<th></th>
</tr>
</thead>
<tbody>
{instances.map(instance =>
<tr>
<td>{ <StatusIcon status={instance.status}/> }</td>
<td>{instance.id}</td>
{/* <td>{instance.workflow.name}</td>
<td>{instance.workflow.version}</td> */}
{/* <td>{instance.jobs.length}</td> */}
<td>{instance.start}</td>
<td>{instance.end}</td>
<td><a href='/'>Details</a></td>
</tr>
)}
</tbody>
</Table>
</>
: <Loader />}
</Container>
)
}
const mapStateToProps = state => ({
diagram: state.drawer.diagram
})
const mapDispatchToProps = {
doSetDiagram: setDiagram
}
export default connect(mapStateToProps, mapDispatchToProps)(DeploymentOverview)
What I want in the first useEffect is to set de Redux state of diagram (this works), then I have a other useEffect hook that will get a list from one of the diagrams attributes named instances next I loop over those instances and do a fetch to get the status of that instance and add this status to the statusList. Lastly I set the instances state using setinstances(statusList)
So now I expect the list of statusresults being set into instances and this is the case (also working?). But then the value is changed back to the initial value null...
In my console it's first shows null (ok, initial value), then the list (yes!) but then null again (huh?). I read on the internet and useEffect docs that the useEffect runs after every render, but I still don't understand why instances is set and then put back to it's initial state.
I am very curious what I am doing wrong and how I can fix this.
If you have multiple async operations you can use Promise.all:
useEffect(() => {
if (diagram) {
if (diagram.instances) {
Promise.all(
diagram.instances.map((instance) =>
InstanceStatus(instance.key)
)
)
.then((instances) => setInstances(instances))
.catch((err) => setError([true, err]));
}
}
}, [diagram]);
Here is a working example:
const InstanceStatus = (num) => Promise.resolve(num + 5);
const useEffect = React.useEffect;
const App = ({ diagram }) => {
const [instances, setInstances] = React.useState(null);
const [error, setError] = React.useState([false, '']);
//the exact same code from my answer:
useEffect(() => {
if (diagram) {
if (diagram.instances) {
Promise.all(
diagram.instances.map((instance) =>
InstanceStatus(instance.key)
)
)
.then((instances) => setInstances(instances))
.catch((err) => setError([true, err]));
}
}
}, [diagram]);
return (
<pre>{JSON.stringify(instances, 2, undefined)}</pre>
);
};
const diagram = {
instances: [{ key: 1 }, { key: 2 }, { key: 3 }],
};
ReactDOM.render(
<App diagram={diagram} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
What you did wrong is the following:
diagram.instances.forEach(instance => {
InstanceStatus(instance.key)//this is async
//this executes later when the promise resolves
//mutating status after it has been set does not
//re render your component
.then(status => statusList.push(status))
.catch(err => seterror([true, err]))
});
//this executes immediately so statusList is empty
setinstances(statusList)

Removing Item from DOM display (React)

sample image of page so far
Please see the above image. I'm wanting to be able to remove the string in the "skill" column when it is deleted after clicking the "Delete" button on the right. I can delete an Employee using filter() with the Delete button on the left. This removes the entire row from the DOM, which is great, but I would like the Delete button on the right to simply remove the content from the Skill(s) column for that particular employee.
I have tried doing something similar in the deleteSkill() function, but I am not sure how to remove the content of the Skill(s) column without deleting the entire row. The issue lies in my setList() function within my deleteSkill() function. Any ideas would be greatly appreciated.
Here is the code:
import React, { Fragment, useEffect, useState } from 'react';
const List = () => {
const [list, setList] = useState([]);
//DELETE Employee by ID
const deleteEmployee = async (id) => {
try {
const deleteEmployee = await fetch(`http://localhost:5000/employees/${id}`, {
method: "DELETE"
});
setList(list.filter(item => item.employee_uuid !== id));
} catch (err) {
console.error(err.message)
}
};
//DELETE Skill by ID
const deleteSkill = async (id) => {
try {
const deleteSkill = await fetch(`http://localhost:5000/employees/${id}/skills`, {
method: "DELETE"
});
setList(list.filter(item => item.summary !== id));
} catch (err) {
console.error(err.message)
}
};
const getList = async () => {
try {
const response = await fetch("http://localhost:5000/employees")
const jsonData = await response.json();
setList(jsonData);
} catch (err) {
console.error(err.message);
}
};
useEffect(() => {
getList();
}, []);
console.log(list);
return (
<Fragment>
{" "}
<h1 class="text-center">Employee Skills Tracker</h1>
<table class="table mt-5 text-center">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th></th>
<th></th>
<th>Skill(s)</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{list.map(item => (
<tr key={item.employee_uuid}>
<td>{item.firstname}</td>
<td>{item.lastname}</td>
<td>Edit</td>
<td><button className="btn btn-danger" onClick={() => deleteEmployee(item.employee_uuid)}>Delete</button></td>
<td>{item.summary}</td>
<td>Edit</td>
<td><button className="btn btn-danger" onClick={() => deleteSkill(item.employee_uuid)} >Delete</button></td>
</tr>
))}
</tbody>
</table>
</Fragment>
);
};
export default List;
Instead of using list.filter(), use list.map(). The key is that you want to return a 1:1 list of modified elements, not a list without certain elements.
If you want to remove the summary for just a specific employee, I'd recommend
setList(list.map(item => {
if (item.id !== id) {
return item; // not the right employee, pass it through
}
let newItem = {...item};
delete newItem.summary;
return newItem;
}));
It's more complex, because you have to explicitly return what you want, but it's also what you're asking for.

Resources