Warning in React js array - javascript

I have this function
static getDerivedStateFromProps(nextProps, prevState) {
let { fetching } = nextProps
const { error } = nextProps
if (prevState.fetching !== fetching && !fetching) {
fetching = error
}
const userId =
Object.keys(nextProps.match.params).length > 0
? nextProps.match.params[Object.keys(nextProps.match.params)[0]]
: 'new'
if (userId !== 'new') {
const itemUser = nextProps.usersList.filter(item => {
if (String(item.userid) === userId)
return item
})
return { profileItem: { ...prevState.profileItem, ...itemUser[0] }, index: userId, fetching }
}
return { fetching }
}
It works and does what it is suppoused to do, but I want to get rid of this warning:
Expected to return a value at the end of arrow function array-callback-return
It says the problem is on the line
const itemUser = nextProps.usersList.filter(item => {

Since filter's callback expects you to return a boolean, you can just rewrite that line to:
const itemUser = nextProps.usersList.filter(item => String(item.userid) === userId)
The problem exists, because of this function:
item => {
if (String(item.userid) === userId)
return item
}
If item.userid != userId, you're currently not returning anything, so it implicitly returns undefined. It's good practice to always return something, even if it's null or false. In this case, your function is working as expected, because the filter callback expects a boolean. When you return item, item is truthy and thus the filter includes that item. Additionally, if you don't return anything, it implicitly returns undefined, which is falsy, and thus filters out the item.
In the end, since you're trying to return one item, you should ideally be using .find() instead. This will prevent excess iterations after the item is found, since you're only ever looking for exactly one item:
const itemUser = nextProps.usersList.find(item => String(item.userid) === userId);
return { profileItem: { ...prevState.profileItem, ...itemUser }, index: userId, fetching }

Related

Javascript - filter if statement doesnt work

Hey guys i got this code where i wanted filter array and return only if doesnt equal to type RJ_TIME and seatClassKey TRAIN_1ST_CLASS at same time.
This doesnt work and behave like OR, so it will return me object that isnt RJ_TIME or TRAIN_1ST_CLASS.
.filter((pClass) => {
if (isFirstClassSoldOut) {
if (pClass.type !== "RJ_TIME" && pClass.seatClassKey !== "TRAIN_1ST_CLASS") {
return pClass
}
} else {
return pClass
}
})
.map((pClass) => (
The condition in the callback for filter needs return a boolean value which is what filter uses to determine whether the iterated element is returned or not.
Here only objects 2 & 4 will be returned as they're the only objects whose properties match the condition.
const isFirstClassSoldOut = true;
const arr = [
{ type: 'RJ_TIME', seatClassKey: 'TRAIN_1ST_CLASS' },
{ type: 'RJ_TIME2', seatClassKey: 'TRAIN_1ST_CLASS2' },
{ type: 'RJ_TIME3', seatClassKey: 'TRAIN_1ST_CLASS' },
{ type: 'RJ_TIME4', seatClassKey: 'TRAIN_1ST_CLASS4' },
];
arr.filter(pClass => {
return isFirstClassSoldOut
&& (pClass.type !== 'RJ_TIME'
&& pClass.seatClassKey !== 'TRAIN_1ST_CLASS');
})
.map(pClass => console.log(pClass));

Nothing shows up after setState filling by components

Client: React, mobx
Server: NodeJS, MongoDB
Short question:
I have an array of elements which fills inside of useEffect function, expected result: each element of array should be rendered, actual result: nothing happens. Render appears only after code changing in VSCode.
Tried: changing .map to .forEach, different variations of spread operator in setState(...[arr]) or even without spread operator, nothing changes.
Info:
Friends.jsx part, contains array state and everything that connected with it, also the fill-up function.
const [requestsFrom, setRequestsFrom] = useState([]) //contains id's (strings) of users that will be found in MongoDB
const [displayRequestsFrom, setDisplayRequestsFrom] = useState([]) //should be filled by elements according to requestsFrom, see below
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom]
requestsFrom.map(async(f) => {
if (requestsFrom.length === 0) {
console.log(`empty`) //this part of code never executes
return
} else {
const _candidate = await userPage.fetchUserDataLite(f)
_arr.push( //template to render UserModels (below)
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
console.log(_arr)
}
})
setDisplayRequestsFrom(_arr)
// console.log(`displayRequestsFrom:`)
console.log(displayRequestsFrom) //at first 0, turns into 3 in the second moment (whole component renders twice, yes)
}
Render template function:
const render = {
requests: () => {
return (
displayRequestsFrom.map((friendCandidate) => {
return (
<FriendModel link={friendCandidate.link} username={friendCandidate.username} userId={friendCandidate.userId}/>
)
})
)
}
}
useEffect:
useEffect(() => {
console.log(`requestsFrom.length === ${requestsFrom.length}`)
if (!requestsFrom.length === 0) {
return
} else if (requestsFrom.length === 0) {
setRequestsFrom(toJS(friend.requests.from))
if (toJS(friend.requests.from).length === 0) {
const _arr = [...requestsFrom]
_arr.push('0')
setRequestsFrom(_arr)
}
}
if (displayRequestsFrom.length < 1 && requestsFrom.length > 0) {
getUsersToDisplayInFriendRequestsFrom()
//displayRequestsFrom and requestsFrom lengths should be same
}
},
[requestsFrom]
)
Part of jsx with rendering:
<div className={styles.Friends}>
<div className={styles['friends-container']}>
{render.requests()}
</div>
</div>
UPD: my console.log outputs in the right order from beginning:
requestsFrom.length === 0
requestsFrom.length === 3
displayRequestsFrom === 0
displayRequestsFrom === 3
As we can see, nor requestsFrom, neither displayRequestsFrom are empty at the end of the component mounting and rendering, the only problem left I can't find out - why even with 3 templates in displayRequestsFrom component doesn't render them, but render if I press forceUpdate button (created it for debug purposes, here it is:)
const [ignored, forceUpdate] = React.useReducer(x => x + 1, 0);
<button onClick={forceUpdate}>force update</button>
PRICIPAL ANSWER
The problem here is that you are executing fetch inside .map method.
This way, you are not waiting for the fetch to finish (see comments)
Wrong Example (with clarification comments)
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom];
// we are not awating requestsFrom.map() (and we can't as in this example, cause .map is not async and don't return a Promise)
requestsFrom.map(async (f) => {
const _candidate = await userPage.fetchUserDataLite(f)
// This is called after setting the state in the final line :(
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
} )
setDisplayRequestsFrom(_arr) // This line is called before the first fetch resolves.
// The _arr var is still empty at the time of execution of the setter
}
To solve, you need to await for each fetch before updating the state with the new array.
To do this, your entire function has to be async and you need to await inside a for loop.
For example this code became
const getUsersToDisplayInFriendRequestsFrom = async () => { // Note the async keyword here
const _arr = [...displayRequestsFrom]
for (let f of requestsFrom) {
const _candidate = await fetchUserData(f)
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
}
setDisplayRequestsFrom(_arr)
}
You can also execute every fetch in parallel like this
const getUsersToDisplayInFriendRequestsFrom = async () => { // Note the async keyword here
const _arr = [...displayRequestsFrom]
await Promise.all(requestsFrom.map((f) => {
return fetchUserData(f).then(_candidate => {
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
});
}));
setDisplayRequestsFrom(_arr);
}
Other problems
Never Calling the Service
Seems you are mapping on an empty array where you are trying to call your service.
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom]
/* HERE */ requestsFrom.map(async(f) => {
if (requestsFrom.length === 0) {
return
If the array (requestsFrom) is empty ( as you initialized in the useState([]) ) the function you pass in the map method is never called.
Not sure what you are exactly trying to do, but this should be one of the problems...
Don't use state for rendered components
Also, you shoudn't use state to store rendered components
_arr.push(
<FriendModel key={_candidate.id} isRequest={true} link='#' username={_candidate.login} userId={_candidate._id}/>
)
, instead you should map the data in the template and then render a component for each element in your data-array.
For example:
function MyComponent() {
const [myData, setMyData] = useState([{name: 'a'}, {name: 'b'}])
return (<>
{
myData.map(obj => <Friend friend={obj} />)
}
</>)
}
Not:
function MyComponent() {
const [myDataDisplay, setMyDataDisplay] = useState([
<Friend friend={{name: 'a'}} />,
<Friend friend={{name: 'b'}} />
])
return <>{myDataDisplay}</>
}
Don't use useEffect to initialize your state
I'm wondering why you are setting the requestsFrom value inside the useEffect.
Why aren't you initializing the state of your requestsFrom inside the useState()?
Something like
const [requestsFrom, setRequestsFrom] = useState(toJS(friend.requests.from))
instead of checking the length inside the useEffect and fill it
So that your useEffect can became something like this
useEffect(() => {
if (displayRequestsFrom.length < 1 && requestsFrom.length > 0) {
getUsersToDisplayInFriendRequestsFrom()
}
},
[requestsFrom]
)

Creating new array vs modifing the same array in react

Following is the piece of code which is working fine, but I have one doubt regarding - const _detail = detail; code inside a map method. Here you can see that I am iterating over an array and modifying the object and then setting it to setState().
Code Block -
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
const { invoiceData } = this.state;
invoiceData.map(invoiceItem => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(detail => {
const _detail = detail;
if (_detail.tagNumber === data.tagNumber) {
_detail.id = data.id;
}
return _detail;
});
}
return invoiceItem;
});
state.invoiceData = invoiceData;
}
this.setState(state);
};
Is this approach ok in React world or I should do something like -
const modifiedInvoiceData = invoiceData.map(invoiceItem => {
......
code
......
})
this.setState({invoiceData: modifiedInvoiceData});
What is the pros and cons of each and which scenario do I need to keep in mind while taking either of one approach ?
You cannot mutate state, instead you can do something like this:
checkInvoiceData = (isUploaded, data) => {
if (isUploaded) {
this.setState({
invoiceData: this.state.invoiceData.map(
(invoiceItem) => {
if (invoiceItem.number === data.savedNumber) {
invoiceItem.details.map(
(detail) =>
detail.tagNumber === data.tagNumber
? { ...detail, id: data.id } //copy detail and set id on copy
: detail //no change, return detail
);
}
return invoiceItem;
}
),
});
}
};
Perhaps try something like this:
checkInvoiceData = (isUploaded, data) => {
// Return early
if (!isUploaded) return
const { invoiceData } = this.state;
const updatedInvoices = invoiceData.map(invoiceItem => {
if (invoiceItem.number !== data.savedNumber) return invoiceItem
const details = invoiceItem.details.map(detail => {
if (detail.tagNumber !== data.tagNumber) return detail
return { ...detail, id: data.id };
});
return { ...invoiceItem, details };
});
this.setState({ invoiceData: updatedInvoices });
};
First, I would suggest returning early rather than nesting conditionals.
Second, make sure you're not mutating state directly (eg no this.state = state).
Third, pass the part of state you want to mutate, not the whole state object, to setState.
Fourth, return a new instance of the object so the object reference updates so React can detect the change of values.
I'm not saying this is the best way to do what you want, but it should point you in a better direction.

How to use Nested map() functions while fetching data from Firebase Firestore

I am learning React and I have some menu and sub_menu items, I made two different collections on the Firestore and I am fetching data from them. I wanted to show the menu and according to their parent id their submenu.
I used JavaScript map() inside the map() and my code works well but I wanted to know that is my way is right because I'm getting this warning in the console saying:
Line 35: Expected to return a value at the end of arrow function array-callback-return
What am I doing wrong?
project_action.jsx (the action file using Redux's code)
// FETCHING HEADER MENU ITEMS
export const fetchMenuItems = () => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
// MAKE ASYNC CALL
const firestore = getFirestore();
let mainData = [];
let subMenuData = [];
// GETTING MAIN MENUES
firestore.collection('header_menu_items').orderBy('item_pos', 'asc').get().then(querySnapshot => {
querySnapshot.forEach(doc => {
mainData.push({
item_id: doc.id,
item_name: doc.data().item_name,
item_link: doc.data().item_link,
is_active: doc.data().is_active,
has_sub_menu: doc.data().has_sub_menu,
item_pos: doc.data().item_pos,
sub_menu: [],
// otherData: doc.data()
});
});
// GETTING SUB MENUES
firestore.collection('header_menu_categories').orderBy('item_name', 'asc').get().then(querySnapshot => {
querySnapshot.forEach(doc => {
subMenuData.push({
item_id: doc.id,
parent_id: doc.data().parent_id,
item_name: doc.data().item_name,
is_active: doc.data().is_active
});
});
// BUILDING MENU
mainData.map(item => {
if (item.has_sub_menu === true) {
return subMenuData.map(sub_item => {
if (item.item_id === sub_item.parent_id) {
item.sub_menu.push(sub_item);
}
return item;
})
}
});
dispatch({ type: 'FETCH_MENU_ITEM', data: mainData });
});
});
}
};
Printing data using it
<ul>
{
props.mainMenu && props.mainMenu.map(item => {
return (
(item.is_active) &&
<li key={item.item_id} className={item.has_sub_menu ? 'has-menu-items' : ''}>
<a href={item.item_link}>{item.item_name}</a>
{
(item.has_sub_menu) &&
<ul className="sub-menu">
{
item.sub_menu.map(sub_item => {
return (
<li key={sub_item.item_id}>
<a href={sub_item.item_link}>{sub_item.item_name}</a>
</li>
)
})
}
</ul>
}
</li>
)
})
}
</ul>
It's an eslint rule: https://eslint.org/docs/rules/array-callback-return
It enforces you to return something from any array filtering, mapping, and folding callback function
mainData.map(item => {
if (item.has_sub_menu === true) {
return subMenuData.map(sub_item => {
if (item.item_id === sub_item.parent_id) {
item.sub_menu.push(sub_item);
}
return item;
})
}
// nothing returned here
});
An off topic note: Are you sure this function will really work?
I suspect that you wanted to do it something like this:
mainData.forEach(item => {
if (item.has_sub_menu === true) {
item.sub_menu = subMenuData.reduce((acc, sub_item) => {
if (sub_item.parent_id === item.item_id) {
acc.push(sub_item);
}
return acc;
}, []);
}
});
Is this the browser console or your system terminal? Looks more like the system terminal and the message says it all: "Line 35: Expected to return a value at the end of arrow function array-callback-return" You are most probably not returning a value. If you map over the array and in the callback you have an if -> what happens if the condition of the if is not satisfied? You won't enter it - so what are you returning when you don't go in the if statement - you guessed it - nothing. Depending on what you do, you can return false. Then filter all the elements and return just the one with a positive value.

test case failing due to .map is not a function error

Hi i have a react component expenses-total.js and a corresponding test case expenses-total.test.js as shown below.
expenses-total.js
export default (expenses=[]) => {
if (expenses.length === 0) {
return 0;
} else {
return expenses
.map(expense => expense.amount)
.reduce((sum, val) => sum + val, 0);
}
};
expenses-total.test.js
import selectExpensesTotal from '../../selectors/expenses-total';
const expenses = [
{
id: "1",
description: "gum",
amount: 321,
createdAt: 1000,
note: ""
},
{
id: "2",
description: "rent",
amount: 3212,
createdAt: 4000,
note: ""
},
{
id: "3",
description: "Coffee",
amount: 3214,
createdAt: 5000,
note: ""
}
];
test('Should return 0 if no expenses', ()=>{
const res = selectExpensesTotal([]);
expect(res).toBe(0);
});
test('Should correctly add up a single expense', ()=>{
const res = selectExpensesTotal(expenses[0]);
expect(res).toBe(321);
});
test('Should correctly add up multiple expenses',()=>{
const res = selectExpensesTotal(expenses);
expect(res).toBe(6747);
});
when i run the test case, its getting failed by giving an error
TypeError: expenses.map is not a function
I know the test case is correct but dont know what is wrong with thecomponent.
Could anyone please help me in fixing this error?
The problem is with if (expenses.length === 0) and the test case that uses selectExpensesTotal(expenses[0]):
expenses[0] passes an object, which has no length property, so in the function being tested, expenses.length returns undefined. However, undefined === 0 evaluates to false so your code goes into the else block tries to use .map on the object, which doesn't have that function, thus it throws an error.
In a brief: you can't map over an object.
expenses is an array of objects, so expenses[0] is an object.
Condition expenses.length === 0 evaluates to false, since obviously .length property does not exist on Object.prototype, so the else condition takes place - your function tries to map over an object.
The problem is that expenses[0] is an object (you probably expected it to be an array) and an object does not have a map function. A quick hack would be to add another ifs into the loop to check if expenses is actually an object. So that:
export default (expenses=[]) => {
if (expenses.length === 0) {
return 0;
} else {
if (typeof expenses === 'object') {
return expenses.amount
} else {
return expenses
.map(expense => expense.amount)
.reduce((sum, val) => sum + val, 0);
}
}
};
I hope this help.
To fix this error, you can pass in an array of object into
selectExpensesTotal([expenses[0]])
rather than just an object
selectExpensesTotal(expenses[0])
So your code show look like this:
test('Should correctly add up a single expense', ()=>{
const res = selectExpensesTotal([expenses[0]]);
expect(res).toBe(321);
});
.map function will now work on expenses. Because, this is now an array of object ( works with map function ) and not an object(This does not work with map function)

Categories

Resources