So I am fairly new to react and most of my learning have been by watching tutorial, So at this point, I diverted from my instructor and started implementing it using my own understanding and then I was thrown with following error
React: .map is not a function
Here is the code
render() {
let person = null;
if (this.state.showPerson) {
person= (
<div>
{
this.state.person.map((el, index) => {
return <Person
key={el.id}
click={this.deletePersonHandler.bind(index)}
name={el.name}
age={el.age}
changed={(event) => this.eventSwitchHandler(event, el.id)} />
})
}
</div>
);
}
return (
The error occured after I implement eventSwitchHandler, Here is my switch handler code
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.find(checkID);
function checkID (passedID) {
return passedID.id === id
}
console.log("newP")
const newP = {...personInput}
console.log(newP)
newP.name = event.target.value
console.log(personInput)
this.setState ({person: newP})
}
[Updated] Here is State
state = {
person: [
{id: "name1n", name: "Rohit", age: 24},
{id: "name2l", name: "Hariom", age: 23},
{id: "name3g", name: "Vaibhav", age: 58}
],
someOtherState: "Untouched state",
showPerson: false
}
[Update] Here is my instructor code, His name change handler is equal to my eventSwitchHandler
Again, My first question would be why does .map is not a function error occurs and while console.logging stuff, I observed something which is kinda rare to me for which I have attached a screenshot (why does the name appear to be different in both the places?)
Your person appears to be a javascript object and not an array which is what provides the map function.
You can check out the rest of the details in the docs here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
To iterate the Object by .map method, utilizing the Object.keys() which returns an array of a given object's keys:
Object.keys(this.state.person).map((key, index) => {
console.log(this.state.person[key]);
})
Update
You have done different things to your instructor code:
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.find(checkID);
function checkID (passedID) {
return passedID.id === id
}
const newP = {...personInput} // **** newP is an object. ****
newP.name = event.target.value
// What you missed:
// let person = this.state.person;
// person[personInput] = newP;
// this.setState ({person: person});
this.setState ({person: newP}) // **** now person becomes object, not an array any more. ****
}
You are not updating the state correctly in eventSwitchHandler
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.find(checkID);
function checkID (passedID) {
return passedID.id === id
}
console.log("newP")
const newP = {...personInput} // newP is an object here
console.log(newP)
newP.name = event.target.value
console.log(personInput)
this.setState ({person: newP}) // overwriting person array with object
}
You would change that to
eventSwitchHandler = (event, id) => {
const personInputIndex = this.state.person.findIndex(checkID);
function checkID (passedID) {
return passedID.id === id
}
const newName = event.target.value
this.setState (prevState => ({
person: [
...prevState.person.slice(0, personInputIndex),
{...prevState.person[personInputIndex], name: newName},
...prevState.person.slice(personInputIndex)
]
})
)
}
or
eventSwitchHandler = (event, id) => {
const personInputIndex = this.state.person.findIndex(checkID);
function checkID (passedID) {
return passedID.id === id
}
const newName = event.target.value
this.setState (prevState => ({
person: Object.assign([], prevState.person, {
[personInputIndex]: {...prevState.person[personInputIndex], newName
}})
})
)
}
eventSwitchHandler = (event, id) => {
const personInput = this.state.person.findIndex(checkID);
function checkID (passedID) {
return passedID.id === id;
}
const person = {...this.state.person[personInput]};
person.name = e.target.value;
const newPerson =[...this.state.person];
newPerson[personInput] = person;
this.setState ({person: newPerson})
}
Related
My FlatList does not update when the props I pass from redux change. Every time I send a message I increase everyones unread message count in both firebase and in my redux store. I made sure to include key extractor and extra data, but neither helps. The only thing that changes the unread message count is a reload of the device. How do I make sure the flatList updates with MapStateToProps. I made sure to create a new object by using Object.Assign:
action:
export const sendMessage = (
message,
currentChannel,
channelType,
messageType
) => {
return dispatch => {
dispatch(chatMessageLoading());
const currentUserID = firebaseService.auth().currentUser.uid;
let createdAt = firebase.database.ServerValue.TIMESTAMP;
let chatMessage = {
text: message,
createdAt: createdAt,
userId: currentUserID,
messageType: messageType
};
FIREBASE_REF_MESSAGES.child(channelType)
.child(currentChannel)
.push(chatMessage, error => {
if (error) {
dispatch(chatMessageError(error.message));
} else {
dispatch(chatMessageSuccess());
}
});
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child(channelType)
.child(currentChannel).child('users')
UNREAD_MESSAGES.once("value")
.then(snapshot => {
snapshot.forEach(user => {
let userKey = user.key;
// update unread messages count
if (userKey !== currentUserID) {
UNREAD_MESSAGES.child(userKey).transaction(function (unreadMessages) {
if (unreadMessages === null) {
dispatch(unreadMessageCount(currentChannel, 1))
return 1;
} else {
alert(unreadMessages)
dispatch(unreadMessageCount(currentChannel, unreadMessages + 1))
return unreadMessages + 1;
}
});
} else {
UNREAD_MESSAGES.child(userKey).transaction(function () {
dispatch(unreadMessageCount(currentChannel, 0))
return 0;
});
}
}
)
})
};
};
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
let currentUserID = firebaseService.auth().currentUser.uid;
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// flag whether you can check in or not
if (currentMountain) {
dispatch(checkInAvailable());
} else {
dispatch(checkInNotAvailable());
}
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.on("value", snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
const UNREAD_MESSAGES = FIREBASE_REF_UNREAD.child("Public")
.child(channelId).child('users').child(currentUserID)
UNREAD_MESSAGES.on("value",
snapshot => {
if (snapshot.val() === null) {
// get number of messages in thread if haven't opened
dispatch(unreadMessageCount(channelId, 0));
} else {
dispatch(unreadMessageCount(channelId, snapshot.val()));
}
}
)
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function () {
dispatch(loadPublicChannelsSuccess(data));
}, 150);
});
};
};
Reducer:
case types.UNREAD_MESSAGE_SUCCESS:
const um = Object.assign(state.unreadMessages, {[action.info]: action.unreadMessages});
return {
...state,
unreadMessages: um
};
Container- inside I hook up map state to props with the unread messages and pass to my component as props:
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
unreadMessages: state.chat.unreadMessages,
};
}
Component:
render() {
// rendering all public channels
const renderPublicChannels = ({ item, unreadMessages }) => {
return (
<ListItem
title={item.info.Name}
titleStyle={styles.title}
rightTitle={(this.props.unreadMessages || {} )[item.id] > 0 && `${(this.props.unreadMessages || {} )[item.id]}`}
rightTitleStyle={styles.rightTitle}
rightSubtitleStyle={styles.rightSubtitle}
rightSubtitle={(this.props.unreadMessages || {} )[item.id] > 0 && "unread"}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
return (
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
keyExtractor={(item, index) => index.toString()}
extraData={[this.props.publicChannels, this.props.unreadMessages]}
removeClippedSubviews={false}
/>
</View>
);
}
}
Object.assign will merge everything into the first object provided as an argument, and return the same object. In redux, you need to create a new object reference, otherwise change is not guaranteed to be be picked up. Use this
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
// or
const um = {...state.unreadMessages, [action.info]: action.unreadMessages }
Object.assign() does not return a new object. Due to which in the reducer unreadMessages is pointing to the same object and the component is not getting rerendered.
Use this in your reducer
const um = Object.assign({}, state.unreadMessages, {[action.info]: action.unreadMessages});
what is the best practice to modify and return a new object from a function?
I wrote the following function :
export const addItemToCart = (currentCart, item) => {
const { name, ...otherProps } = item;
//if item exist in the cart
if (currentCart[name]) {
currentCart[name]["quantity"]++;
return currentCart;
}
//if the item does not exist
else
{
currentCart[name] = { ...otherProps };
currentCart[name]["quantity"] = 1;
return currentCart;
}
// the function must return a new modified object on each call
};
Obviously, the hard-coded property "quantity", and the return statements can definitely be improved.
how can I improve this function to be more readable?
More "readable" is very opinion-based, either way, you can try something like this:
const currentCart = {
hello: {
quantity: 1
}
};
const addItemToCart = (currentCart, item) => {
const { name } = item;
// Short circuit + return the last value
const quantityPrev = currentCart[name] && currentCart[name].quantity;
// Or operator on boolean expression
const quantity = 1 + (quantityPrev || 0);
// Destructing for shallow copy, dynamic key assign
return { ...currentCart, [name]: { quantity } };
};
console.log(addItemToCart(currentCart, { name: 'hello' }));
console.log(addItemToCart(currentCart, { name: 'blazer' }));
editItem = (id) => {
this.setState(
{
lanche: [
{
id: id,
editItem: true
}]
}
)
}
In this way, setState runs on all items in the array, I need to edit only the item that has the same ID
You need to find the item in the array with the given id, and modify that one only. setState method changes the whole object with the given key.
try this:
editItem = (id) => {
this.setState(
{
lanche: this.state.lanche.map(item => {
if(item.id === id) {
item.editItem = true
}
return item;
})
}
)
}
You can use map like this:
editItem = (id) => {
// update the relevant item
const updatedItems = this.state.lanche.map(item => {
if (item.id === id) {
item.editItem = true;
}
return item;
} );
this.setState(
{
lanche: updatedItems
}
)
}
If I understand your question;
You could search for the index like so;
const index = this.state.lanche.findIndex((e) => e.id === id);
and then alter the object
const newLanche = this.state.lanche;
newLanche[index].editItem = true;
And then update the store;
this.setState({ lanche: newLanche });
I've got add
I've got delete
now I need modify
add just adds to the pile haphazardly
delete is able to do it's work with surgical precision because it uses key to find it's culprit :
addInput = (name) => {
const newInputs = this.props.parameters;
newInputs.push({
name,
key: uuid(),
value: { input: '' },
icon: { inputIcon: 0 },
});
this.setState({
newInput: newInputs,
});
this.props.exportParameter(newInputs);
};
removeInput = (key) => {
const newInputs = this.props.parameters.filter(x => x.key !== key);
this.setState({
newInput: newInputs,
});
this.props.exportParameter(newInputs);
};
how do I modify (for example set the value back to '' without deleting and recreating the item) ?
modifyInput = (key) => {
?????
};
You can use Array.prototype.find()
modifyInput = (key) => {
const match = this.props.parameters.find(x => x.key === key);
// do stuff with matched object
};
You can map through the parameters and then modify when you find a match:
modifyInput = (key) => {
const newInputs = this.props.parameters.map(x => {
if (x.key === key) {
x.modification = true;
}
return x;
});
this.setState({
newInput: newInputs,
});
this.props.exportParameter(newInputs);
};
var empMap= {};
//initialize empMap with key as Employee ID and value as Employee attributes:
//Now when salary updated on UI from 100 - 300 you can update other fields
var e=empMap[id];
if(e){
e.Salary=newSalary;
e.YearlySalary = newSalary*12;
e.Deductions = e.YearlySalary*0.2;
empMap[id]=e;
}
I've got add
I've got delete
now I need modify
add just adds to the pile haphazardly
delete is able to do it's work with surgical precision because it uses key to find it's culprit :
addInput = (name) => {
const newInputs = this.props.parameters;
newInputs.push({
name,
key: uuid(),
value: { input: '' },
icon: { inputIcon: 0 },
});
this.setState({
newInput: newInputs,
});
this.props.exportParameter(newInputs);
};
removeInput = (key) => {
const newInputs = this.props.parameters.filter(x => x.key !== key);
this.setState({
newInput: newInputs,
});
this.props.exportParameter(newInputs);
};
how do I modify (for example set the value back to '' without deleting and recreating the item) ?
modifyInput = (key) => {
?????
};
You can use Array.prototype.find()
modifyInput = (key) => {
const match = this.props.parameters.find(x => x.key === key);
// do stuff with matched object
};
You can map through the parameters and then modify when you find a match:
modifyInput = (key) => {
const newInputs = this.props.parameters.map(x => {
if (x.key === key) {
x.modification = true;
}
return x;
});
this.setState({
newInput: newInputs,
});
this.props.exportParameter(newInputs);
};
var empMap= {};
//initialize empMap with key as Employee ID and value as Employee attributes:
//Now when salary updated on UI from 100 - 300 you can update other fields
var e=empMap[id];
if(e){
e.Salary=newSalary;
e.YearlySalary = newSalary*12;
e.Deductions = e.YearlySalary*0.2;
empMap[id]=e;
}