How to use .splice() property at React? - arrays

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

Related

Old state is losing while doing search in React

So I'm trying to build search feature for a website and I'm facing an issue where when a specific search is done the old state is not there, and if you delete a character is deleted I don't have the old state and doesn't not filter anything.
Here is my function:
function filterSearch(state, query) {
const orders = state.orders.filter((order) =>
order.id.toLowerCase().includes(query.toLowerCase())); // this filters the state
state.orders = orders; // this is redux state but you can think it as a setState
}
The problem is that you're storing the original state and overwriting the same. These are my suggestions
First Approach: storing filtered orders
function filterSearch(state, query) {
const orders = state.orders.filter((order) => {
const id = order.id.toLowerCase();
return id.includes(query.toLowerCase()));
});
state.filteredOrders = orders;
}
Basically you'll create a new state to store the filtered orders. That way the state orders will maintain the original list
Second Approach: returning filtered
function filterSearch(state, query) {
const orders = state.orders.filter((order) => {
const id = order.id.toLowerCase();
return id.includes(query.toLowerCase()));
});
return orders;
}
This way instead of storing the filtered data or overwriting the original data, use the function to return the filtered data and use it where you need it
you have to maintain two state for this, one state would be of the initial order then you can put that state into the local state which you can use to display the orders and always search on the initial state so that user will always get the correct result, something like following
const Table = ({ initailSearch }) => {
const [data, setData] = React.useState(initailSearch);
const [query, setQuery] = React.useState("");
const handleChange = (e) => {
setQuery(e.target.value);
};
useEffect(() => {
let filterData = initailSearch;
if (query.length > 0) {
filterData = initailSearch.filter((item) => {
return item.id.toLowerCase().includes(query.toLowerCase());
});
}
setData(filterData);
}, [query]);
return (
<>
<input onChange={handleChange} />
<table>
<thead>
<tr>
<th>order</th>
</tr>
{data.map((item) => {
return (
<tr key={item.id}>
<td>{item.id}</td>
</tr>
);
})}
</thead>
</table>
</>
);
};
working code sandbox link

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.

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

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.

TypeError: can't convert undefined to object In React

I'm able to fetch the data from API, but not able to set the data into react state variable. Using useEffect. It's Weird because Initially Code was working fine, I was able to set the data into state variable, but after writing bunch of code. I'm getting this error.
App.js
const fetchData = async () => {
try {
const response = await axios.get(
"https://60d007f67de0b200171079e8.mockapi.io/bakery"
);
const { data } = response;
return data;
} catch (err) {
console.error(err)
}
};
const extractData = (bakerys) => {
const bakery = bakerys[0];
const header = [];
Object.keys(bakery).forEach((objKeys) => {
const value = bakery[objKeys];
// if(type of value !== 'object'){
header.push(objKeys);
})
return header;
};
export default function App() {
const [bakerys, setBakerys] = useState([]);
const [flatbakery, setFlatbakery] = useState([]);
useEffect(() => {
fetchData().then((randomData) => {
console.log('randomData ->', randomData) // able to console data as an Array of object
setBakerys(randomData); // Not able to set the randomData into state variable
console.log('bakerys', bakerys)
})
}, []);
useEffect(() => {
setFlatbakery(extractData(bakerys));
}, [bakerys]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Edit to see some magic happen!</h2>
<table>
<thead>
<tr>
{flatbakery.map((headers, idx) => (
<th key={idx}>
{headers}
</th>
))
}
</tr>
</thead>
</table>
</div>
);
}
Output
This case would come up when your bakerys array is empty, that is bakerys[0] is essentially undefined. You probably need to add some sort of check before you try to iterate the keys of it.
const extractData = (bakerys) => {
const bakery = bakerys[0];
const header = [];
if(bakery) { // only run the following block if bakery is not undefined(or falsey)
Object.keys(bakery).forEach((objKeys) => {
const value = bakery[objKeys];
// if(type of value !== 'object'){
header.push(objKeys);
})
}
return header;
};
EDIT: It appears I have forgotten to mention WHY bakerys may be empty initially. UseEffect runs when the component mounts as well, so the first time that it is called, bakerys is still an empty array. As subsequent updates are made to it, it will eventually be populated with data, so you should always attempt to check if the value has been populated before attempting to run any operations on it.

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)

Resources