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});
Related
I'm trying to import Wordpress categories from .cvs file. I'm writing a simple app in react and I've got a function:
componentDidUpdate( prevProps, prevState ) {
let that = this
if (prevState.syncStatus !== this.state.syncStatus && this.state.syncStatus == 'posts') {
row_terms.forEach( (element, inx) => {
let parent = that.state.parent_id;
let _terms = element.split('>')
_terms = _terms.map(function(e){return e.trim();});
const total = _terms.length
_terms.forEach( (_term, index) => {
addCategory(_term, that.state.parent_id).then(result => {
let term_id
if( result.code && result.code == 'term_exists' ) {
term_id = result.data.resource_id
} else {
term_id = result.id
}
if ( ( 1 + index ) === total ) {
categories.push(term_id)
} else {
that.setState({parent_id: term_id})
}
})
})
})
}
}
and addCategory:
import WooCommerce from './woocommerce'
async function addCategory(name, parent) {
console.log('parent', parent)
try {
return await WooCommerce.postAsync('products/categories', {name: name, parent: parent}).then(result => {
return JSON.parse(result.toJSON().body);
});
} catch (e) {
return e
}
}
export default addCategory
and initial value is set
constructor(props) {
super()
this.state = {
parent_id: 0,
}
}
I want to add cateogories one after another because I need to set parent_id for categories that are children. So when it adds a category and the condition
if ( ( 1 + index ) === total ) {
is not met I want to set parent_id and then use it in next iteration. But it doesn't work.
When I use
console.log(that.state.parent_id)
after
that.setState({parent_id: term_id})
It prints correct value.
When i run my app it prints 0 ( console.log in addCategory() ) for all categories before any request is made.
.forEach() isn't aware of the asynchronicity of addCategory; you'll either have to chain all of those thens, or preferably just use async/await and plain old for loops, which can be used with await without extra hoops to jump through.
Assuming you want to loop through all of the row_terms and process them, you'll also need to await on those promises...
const categories = [];
const promises = row_terms.map(async (element) => {
const terms = element.split(">").map((e) => e.trim());
const total = terms.length;
let parent = null;
for (let i = 0; i < total; i++) {
const term = terms[i];
const result = await addCategory(term, parent);
const term_id = result.code && result.code === "term_exists" ? result.data.resource_id : result.id;
if (i === total - 1) {
categories.push(term_id);
} else {
parent = term_id;
}
}
});
await Promise.all(promises);
I have used following code in my component to load new data when scrolling.But when new page of data is loaded the scroll bar keeps returning to the top.I'm using this for Magento PWA with react.
const Category = props => {
const {id} = props;
const classes = mergeClasses(defaultClasses, props.classes);
const [paginationValues, paginationApi] = usePagination();
const {currentPage, totalPages} = paginationValues;
const {setCurrentPage, setTotalPages} = paginationApi;
const [selectedPageSize, setPageSize] = useState(40);
const [pageNumber, setPageNumber] = useState(1);
function handlePageSize() {
setPageNumber(pageNumber+1)
}
const sortProps = useSort();
const [currentSort] = sortProps;
const previousSort = useRef(currentSort);
const pageControl = {
currentPage,
setPage: setCurrentPage,
totalPages
};
const [runQuery, queryResponse] = useLazyQuery(GET_CATEGORY,{fetchPolicy:'cache-first'});
const {loading, error, data} = queryResponse;
const {search} = useLocation();
const bqueryResponsee = useQuery(
GET_ATTRIBUTES
);
let battributedata = '';
// Keep track of the search terms so we can tell when they change.
const previousSearch = useRef(search);
// Get "allowed" filters by intersection of schema and aggregations
const {data: introspectionData} = useQuery(FILTER_INTROSPECTION);
// Create a type map we can reference later to ensure we pass valid args
// to the graphql query.
// For example: { category_id: 'FilterEqualTypeInput', price: 'FilterRangeTypeInput' }
const filterTypeMap = useMemo(() => {
const typeMap = new Map();
if (introspectionData) {
introspectionData.__type.inputFields.forEach(({name, type}) => {
typeMap.set(name, type.name);
});
}
return typeMap;
}, [introspectionData]);
// Run the category query immediately and whenever its variable values change.
useEffect(() => {
window.addEventListener('scroll', infiniteScroll);
// Wait until we have the type map to fetch product data.
if (!filterTypeMap.size) {
return;
}
const filters = getFiltersFromSearch(search);
const newFilters = {};
newFilters['category_id'] = {eq: String(id)};
filters.forEach((values, key) => {
newFilters[key] = getFilterInput(values, filterTypeMap.get(key));
});
runQuery({
variables: {
currentPage: Number(pageNumber),
id: Number(id),
filters: newFilters,
pageSize: Number(selectedPageSize),
sort: {[currentSort.sortAttribute]: currentSort.sortDirection}
}
});
window.scrollTo({
left: 0,
top: 0,
behavior: 'smooth'
});
}, [
currentPage,
currentSort,
filterTypeMap,
id,
selectedPageSize,
runQuery,
search,
pageNumber
]);
const totalPagesFromData = data
? Math.ceil(data.category.product_count/selectedPageSize)
: null;
useEffect(() => {
setTotalPages(totalPagesFromData);
return () => {
setTotalPages(null);
};
}, [setTotalPages, totalPagesFromData]);
// If we get an error after loading we should try to reset to page 1.
// If we continue to have errors after that, render an error message.
useEffect(() => {
if (error && !loading && currentPage !== 1) {
setCurrentPage(1);
}
}, [currentPage, error, loading, setCurrentPage]);
// Reset the current page back to one (1) when the search string, filters
// or sort criteria change.
useEffect(() => {
// We don't want to compare page value.
const prevSearch = new URLSearchParams(previousSearch.current);
const nextSearch = new URLSearchParams(search);
prevSearch.delete('page');
nextSearch.delete('page');
if (
prevSearch.toString() !== nextSearch.toString() ||
previousSort.current.sortAttribute.toString() !==
currentSort.sortAttribute.toString() ||
previousSort.current.sortDirection.toString() !==
currentSort.sortDirection.toString()
) {
// The search term changed.
setCurrentPage(1);
// And update the ref.
previousSearch.current = search;
previousSort.current = currentSort;
}
}, [currentSort, previousSearch, search, setCurrentPage]);
if (error && currentPage === 1 && !loading) {
if (process.env.NODE_ENV !== 'production') {
console.error(error);
}
return <div>Data Fetch Error</div>;
}
// Show the loading indicator until data has been fetched.
if (totalPagesFromData === null) {
return fullPageLoadingIndicator;
}
if (typeof bqueryResponsee.data !== "undefined" && !bqueryResponsee.loading) {
battributedata = bqueryResponsee.data.customAttributeMetadata.items[0].attribute_options;
} else {
battributedata = [];
}
const count = totalPagesFromData ? totalPagesFromData : null;
const galleries = [];
if (count && count >= 1) {
for (let i =1;i<=pageNumber;i++) {
galleries.push(
<section className={classes.gallery}>
<Gallery searchItems={null} ID={1}
newSort={{[currentSort.sortAttribute]: currentSort.sortDirection}}
newFilters={newFiltersLazy}
currentPage={i} categoryId={id} pageSize={40} introspectionData={introspectionData}
battributedata={battributedata}/>
</section>
)
}
}
function infiniteScroll(){
console.log(Math.ceil((window.innerHeight + document.documentElement.scrollTop)/100)*100,Math.ceil((document.documentElement.offsetHeight*8/10)/100)*100)
// End of the document reached?
if ( Math.ceil((window.innerHeight + document.documentElement.scrollTop)/100)*100
>= Math.ceil((document.documentElement.offsetHeight*8/10)/100)*100){
handlePageSize()
}
}
return (
<Fragment>
<Meta name="description" content={metaDescription}/>
<CategoryContent
totalPagesFromData={totalPagesFromData}
categoryId={id}
classes={classes}
data={loading ? null : data}
pageControl={pageControl}
sortProps={sortProps}
onSelectSize={handlePageSize}
battributedata={battributedata}
selectedPageSize={selectedPageSize}
galleries={galleries}
newSort={{[currentSort.sortAttribute]: currentSort.sortDirection}}
newFilters={newFiltersLazy}
introspectionData={introspectionData}
pageNumber={pageNumber}
/>
</Fragment>
);
};
What I did here is created a new array galleries and the Gallery component in each page increment while scrolling and inside Gallery the items will be created.Problem is the scroll bar keep returning to the top in each increment.Please help
I'm using Firestore to store messages. In order to optimize the mobile application performances, I would like to set a limit(50) in the firestore query.
It works well and implemented the onLoadEarlier React-native-gifted-chat available in the props.
All is working fine.
But, when I send a new message in the chat, after scrolled up to see the earliers messages, only the 50 last messages with the new one, off course, are available.
So, each time I'm adding a message in the Firestore database, the onSnapshot (in the useeffect) is executed and apply the limit query.
Is there a way to avoid this ?
Thanks.
Here my useEffect :
useEffect(() => {
const messagesListener = firestore()
.collection('groups')
.doc(group._id)
.collection('messages')
.orderBy('createdAt', 'desc')
.limit(50)
.onSnapshot(querySnapshot => {
const newMessages = querySnapshot.docs.map(doc => {
const firebaseData = doc.data();
const data = {
_id: doc.id,
text: '',
createdAt: new Date().getTime(),
...firebaseData
};
return data;
});
setMessages(previousMessages => {
return GiftedChat.append(previousMessages, newMessages);
});
});
return () => messagesListener();
}, []);
I am using FlatList in react-native to render chats and I had to paginate the chats list. Since Firestore query cursor is not supported in live listener, I created two list, recentChats & oldChats.
I populate recentChats using live listener query.onSnapshot & oldChats using cursor startAfter. FlatList data is combination of both list and I take care of merging logic.
const MESSAGE_LIMIT = 15;
const ChatWindow = props => {
const { sessionId, postMessage, onSendTemplateButtonPress } = props;
// Firestore cursor is not supported in query.onSnapshot so maintaining two chat list
// oldChats -> chat list via cursor, recentChats -> chat list via live listener
const [oldChats, setOldChats] = useState([]);
const [recentChats, setRecentChats] = useState([]);
// if true, show a loader at the top of chat list
const [moreChatsAvailable, setMoreChatsAvailable] = useState(true);
const [inputMessage, setInputMessage] = useState('');
useEffect(() => {
const query = getGuestChatMessagesQuery(sessionId)
.limit(MESSAGE_LIMIT);
const listener = query.onSnapshot(querySnapshot => {
let chats = [];
querySnapshot.forEach(snapshot => {
chats.push(snapshot.data());
});
// merge recentChats & chats
if (recentChats.length > 0) {
const newRecentChats = [];
for (let i = 0; i < chats.length; i++) {
if (chats[i].sessionId === recentChats[0].sessionId) {
break;
}
newRecentChats.push(chats[i]);
}
setRecentChats([...newRecentChats, ...recentChats]);
} else {
setRecentChats(chats);
if (chats.length < MESSAGE_LIMIT) {
setMoreChatsAvailable(false);
}
}
});
return () => {
// unsubscribe listener
listener();
};
}, []);
const onMessageInputChange = text => {
setInputMessage(text);
};
const onMessageSubmit = () => {
postMessage(inputMessage);
setInputMessage('');
};
const renderFlatListItem = ({ item }) => {
return (<ChatBubble chat={item} />);
};
const onChatListEndReached = () => {
if (!moreChatsAvailable) {
return;
}
let startAfterTime;
if (oldChats.length > 0) {
startAfterTime = oldChats[oldChats.length - 1].time;
} else if (recentChats.length > 0) {
startAfterTime = recentChats[recentChats.length - 1].time;
} else {
setMoreChatsAvailable(false);
return;
}
// query data using cursor
getGuestChatMessagesQuery(sessionId)
.startAfter(startAfterTime)
.limit(MESSAGE_LIMIT)
.get()
.then(querySnapshot => {
let chats = [];
querySnapshot.forEach(snapshot => {
chats.push(snapshot.data());
});
if (chats.length === 0) {
setMoreChatsAvailable(false);
} else {
setOldChats([...oldChats, ...chats]);
}
});
};
return (
<View style={[GenericStyles.fill, GenericStyles.p16]}>
<FlatList
inverted
data={[...recentChats, ...oldChats]}
renderItem={renderFlatListItem}
keyExtractor={item => item.messageId}
onEndReached={onChatListEndReached}
onEndReachedThreshold={0.2}
ListFooterComponent={moreChatsAvailable ? <ActivityIndicator /> : null}
/>
{
Singleton.isStaff ?
null:
<ChatInput
onMessageInputChange={onMessageInputChange}
onMessageSubmit={onMessageSubmit}
inputMessage={inputMessage}
style={GenericStyles.selfEnd}
onSendTemplateButtonPress={onSendTemplateButtonPress}
/>
}
</View>
);
};
Your query is OK for the first time, for consequent queries you must use the ::startAt or ::startAfter methods.
You can find more information in the official documentation.
https://firebase.google.com/docs/firestore/query-data/query-cursors
I have a list a discussion object containing an array of comments and each comment can hold an array of replies. I display the discussion this way:
<div v-for="comment in comments" v-bind:key="comment._id">
<Comment :itemId="itemId" :comment="comment" />
<Replies v-if="comment.replies.length > 0" :itemId="itemId" :comment="comment" />
</div>
<Button value="Load more" #clicked="loadMoreComments(itemId)" />
and Replies:
<div v-for="reply in replies" v-bind:key="reply._id">
<Comment :itemId="itemId" :comment="reply" />
</div>
<Button :value="Load more" #clicked="loadChild()"/>
As you can see both use the same pattern. They differ in a computed property:
computed: {
comments() {
return this.$store.getters.DISCUSSION.comments.map(id => this.$store.getters.GET_COMMENT(id));
},
replies() {
return this.$store.getters.GET_REPLIES(this.comment).map(id => this.$store.getters.GET_COMMENT(id));
},
},
When I hit the Load more button for comments, new comments appear. But when I hit the Load more button in replies, then no new reply is displayed though I can see in debugger that the array was enlarged.
Vuex store submodule:
state: () => ({
discussion: {
incomplete: true,
comments: [],
},
comments: {},
}),
getters: {
DISCUSSION: state => state.discussion,
GET_COMMENT: state => id => state.comments[id],
GET_REPLIES: state => (comment) => {
if (comment.allShown) {
return comment.replies;
}
return comment.replies.slice(0, REPLY_LIMIT);
},
},
mutations: {
APPEND_COMMENTS: (state, payload) => {
const { comments, incomplete, userId } = payload;
state.discussion.incomplete = incomplete;
const commentIds = [];
comments.forEach(comment => processComment(state, comment, commentIds, userId));
state.discussion.comments = state.discussion.comments.concat(commentIds);
},
PREPEND_COMMENTS: (state, payload) => {
const { comments, userId } = payload;
const commentIds = [];
comments.forEach(comment => processComment(state, comment, commentIds, userId));
state.discussion.comments = commentIds.concat(state.discussion.comments);
},
SET_REPLIES: (state, payload) => {
console.log('SET_REPLIES');
const { commentId, replies, userId, replace } = payload;
const comment = state.comments[commentId];
if (!comment) {
return;
}
state.comments[commentId].showAll = true;
const commentIds = [];
replies.forEach(reply => processComment(state, reply, commentIds, userId));
if (!comment.replies || comment.replies.length === 0 || replace) {
state.comments[commentId].replies = commentIds;
} else {
state.comments[commentId].replies = comment.replies.concat(commentIds);
}
},
}
function processComment(state, comment, commentIds, userId) {
if (comment.replies) {
const repliesIds = [];
comment.replies.forEach((reply) => {
reply.voted = hasVoted(reply.votes, userId);
state.comments[reply._id] = reply;
repliesIds.push(reply._id);
});
comment.replies = repliesIds;
comment.allShown = comment.replies.length < REPLY_LIMIT;
} else if (!comment.parentId) {
comment.replies = [];
comment.allShown = false;
}
state.comments[comment._id] = comment;
commentIds.push(comment._id);
}
The complete source code is there: https://github.com/literakl/mezinamiridici/tree/comment_refactoring/spa
Here is minimum reproducible codesandbox: https://codesandbox.io/s/frosty-taussig-v8u4b?file=/src/module.js
I have verified that this happens because of the getter with a parameter. When I put the reply in static array so I could use parameter-less getter, it started to work.
I follow this recommendation: https://forum.vuejs.org/t/vuex-best-practices-for-complex-objects/10143
Where is the issue?
Update:
One thing that smells is the mutation GET_REPLIES because it works on the passed object. So Vue has no chance to detect that the object is from the state. So I have rewritten it to pass only commentId and load the comment from the state, but it did not help.
I guest you should replace showAll with allShown prop and also use Vue.set where you add new keys to comments object because due to Vue caveats Vuex doesn't see new props, see caveats for objects
SET_REPLIES: (state, payload) => {
console.log("SET_REPLIES");
const { commentId, replies, userId, replace } = payload;
const comment = state.comments[commentId];
if (!comment) {
console.log(`Comment ${commentId} not found`);
return;
}
state.comments[commentId].allShown = true;
// state.comments[commentId].showAll = true;
...
function processComment(state, comment, commentIds, userId) {
if (comment.replies) {
const repliesIds = [];
comment.replies.forEach(reply => {
Vue.set(state.comments, reply._id, reply);
// state.comments[reply._id] = reply;
repliesIds.push(reply._id);
});
comment.replies = repliesIds;
comment.allShown = comment.replies.length < 3;
} else if (!comment.parentId) {
comment.replies = [];
comment.allShown = false;
}
Vue.set(state.comments, comment._id, comment);
// state.comments[comment._id] = comment;
commentIds.push(comment._id);
}
Also correct GET_REPLIES call like this:
computed: {
replies() {
return this.$store.getters
.GET_REPLIES(this.comment) // passing comment itself instead of its id
.map(id => this.$store.getters.GET_COMMENT(id));
}
},
corrected example
In my E-Commerce project, the products are stored in localStorage. componentDidMount() gets all these products from localStorage and displays it. How to state the state or condition when no products are available.
componentDidMount = () => {
this.setProducts();
// Gets all products in cart from localStorage
this.setState(
() =>{
return {cart: JSON.parse(localStorage.getItem('myCart'))}
},
() => {
this.addTotal()
})
// How to set the condition when no products in Cart?
}
// Set all the products on Main Page
setProducts = () => {
let tempProducts = [];
storeProducts.forEach(item => {
const singleItem = {...item};
tempProducts = [...tempProducts, singleItem];
});
this.setState(() => {
return {products: tempProducts};
});
};
// Here products are added to cart and stored in localStorage
addToCart = (id) => {
let tempProducts = [...this.state.products];
const index = tempProducts.indexOf(this.getItem(id));
const product = tempProducts[index];
product.inCart = true;
product.count = 1;
const price = product.price;
product.total = price;
this.setState(() => {
return { products: tempProducts, cart: [...this.state.cart,
product] };
},
() => {
this.addTotal();
localStorage.setItem('myCart', JSON.stringify(this.state.cart))
});
}
I have also tried to make following changes, but, no effect. In componentDidMount()
componentDidMount() {
if(this.state.cart.length > 0) {
this.setState(
() =>{
return {cart: JSON.parse(localStorage.getItem('myCart'))}
},
() => {
this.addTotal()
})
} else {
this.setState(() => {
return {cart:[]}
})
}
}
// Clear Cart
clearCart = () => {
this.setState(() => {
return {cart:[]}
}, () => {
this.setProducts();
this.addTotal();
})
localStorage.removeItem('myCart')
}
When I remove code of setState (shown in the beginning) from componentDidMount() displays empty cart message, which is expected else, if the cart is cleared and refreshed browser throws 'cart.length' error. Any possible solution?
JSON.parse will return an object. It depends on your data structure but there is no cart.lendth for the object. So that is your first problem. So for the below example, I store the parsed value as an array.
Also, if state.cart is not initiated, there is no .length property for it.
For your second problem have a look at the below version of your componentDidMount:
componentDidMount() {
if(Array.isArray(this.state.cart) && this.state.cart.length) {
const cart = localStorage.getItem('myCart') || [];
this.setState({cart: [JSON.parse(cart)]}), this.addTotal);
} else {
this.setState({ cart:[] });
}
}
Again, it depends on your implementation, but you might need to initiate the component's state with cart: localStorage.getItem('myCart') || [] or doing what I have done above. I'm basically checking if cart is an array && it has length then parse it otherwise initiate the array.
Finally I got the solution as below
const cart = localStorage.getItem('myCart')
this.setState({cart: JSON.parse(cart) ? JSON.parse(cart) : []}, this.addTotal)
Just modified the code and works perfectly without any issues