Redux State getting reset/overridden when new component loads - firebase

All my cart items are held in the redux state and whenever I load a different component, the cart items are getting reset. So for example, in my cart I have 6 bananas selected for a total price of $3. I go to the Fruits page and add 2 more bananas. Instead of having 8 bananas total for a price of $4, the cart state gets reset when the Fruits component loads and I only end up with 2 bananas for $1. I think this is because in my Fruits page, I'm setting the state as I load products from Firebase and I think because from Firebase I'm setting the Bananas item, it doesn't have the quantity carried over from my redux cart.
let itemsRef = db.ref('/productInfo/0/Fruits');
class Fruits extends Component {
static navigationOptions = {
title: 'Fruits',
};
constructor(props) {
super(props);
this.state = {
items: [],
filtered: []
}
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
itemsRef.on('value', (snapshot) => {
let data = snapshot.val();
let items = Object.values(data);
this.setState({items});
this.setState({
filtered: items
});
});
}
componentWillReceiveProps(nextProps) {
this.setState({
filtered: nextProps.items
});
}
handleChange(e) {
let currentList = []
let newList = []
if (e.toString() !== "") {
currentList = this.state.items
newList = currentList.filter(item => {
const lc = item.name.toString().toLowerCase()
const filter = e.toString().toLowerCase()
return lc.includes(filter)
})
} else {
newList = this.state.items
}
this.setState({
filtered: newList
});
}
render() {
return (
<SafeAreaView style ={{flex:1, marginTop:30}}>
<Searchbar
placeholder="Search"
onChangeText= {(term) => { this.handleChange(term) }}
style = {{marginHorizontal: 20}}
/>
{
this.state.items.length > 0
? <CategoryProductsPage products={this.state.filtered} onPress={this.props.addItemToCart} width = {width}/>
: <Text></Text>
}
</SafeAreaView>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
addItemToCart: (product) => dispatch({ type: 'ADD_TO_CART', payload: product })
}
}
export default connect(null, mapDispatchToProps)(Fruits);
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
Ideally, I want my redux cart state to be universal throughout my application and it shouldn't get changed unless the user increases or decreases the quantity. I'm only having this bug through the Category Components page and I believe its because of the way I'm showing the Firebase products and the code in my constructor and componentDidMount. The challenge I'm facing is being able to incorporate and import my Firebase products without changing the state.
The code for the cart:
import {ADD_TO_CART, REMOVE_FROM_CART, SUB_QUANTITY, ADD_QUANTITY} from './actions';
const initialState = {
cart: [
],
totalQuantity: 0,
total: 0.0
};
const updateDeleteProductList = (updateProduct, productList) => {
return productList.map(product => {
if (product.name === updateProduct.name) {
updateProduct.frequency -=1;
updateProduct.totalPrice = (parseFloat(updateProduct.totalPrice) - product.price).toFixed(2)
return updateProduct;
}
return product;
});
};
const updateDeleteAllProductList = (updateProduct, productList) => {
return productList.map(product => {
if (product.name === updateProduct.name) {
updateProduct.frequency = 0;
updateProduct.totalPrice = 0;
return updateProduct;
}
return product;
});
};
const updateAddProductList = (updateProduct, productList) => {
return productList.map(product => {
if (product.name === updateProduct.name) {
updateProduct.frequency +=1;
//state.totalPrice = (parseFloat(state.totalPrice) + count).toFixed(2)
updateProduct.totalPrice = (parseFloat(updateProduct.totalPrice) + product.price).toFixed(2)
return updateProduct;
}
return product;
});
};
const cartItems = (state = initialState, action) => {
switch(action.type) {
case ADD_TO_CART:
console.log("Cart product screen payload" + JSON.stringify(action.payload))
let addedItem = action.payload
//check if the action id exists in the addedItems
let existed_item= state.cart.find(item=> action.payload.name === item.name)
console.log("WHAT IS EXISTED ITEM " + JSON.stringify(existed_item))
if(existed_item)
{
// addedItem.frequency += 1
// addedItem.totalPrice = (parseFloat(addedItem.totalPrice) + action.payload.price).toFixed(2)
let updatedProductList = updateAddProductList(action.payload,state.cart);
return{
...state,
cart: updatedProductList,
total: (parseFloat(state.total) + addedItem.price).toFixed(2)
}
}
else{
addedItem.frequency = 1;
addedItem.totalPrice = (parseFloat(addedItem.totalPrice) + action.payload.price).toFixed(2)
//calculating the total
let newTotal = (parseFloat(state.total) + addedItem.price).toFixed(2)
return{
...state,
cart: [...state.cart, addedItem],
total : newTotal
}
}
case REMOVE_FROM_CART:
let itemToRemove = action.payload
console.log("REMOVE ALL" + JSON.stringify(action.payload))
let new_items = state.cart.filter(item=> action.payload.id !== item.id)
// let deletedProducts = updateDeleteAllProductList(action.payload, state.cart)
itemToRemove.totalPrice = 0
//calculating the total
let newTotal = state.total - (itemToRemove.price * itemToRemove.frequency )
//let newTotal = (parseFloat(state.total) - action.payload.totalPrice).toFixed(2)
return{
...state,
cart: new_items,
total: newTotal
}
case ADD_QUANTITY:
// let addItem = action.payload
// console.log('additem is' + JSON.stringify(addItem))
// addItem.frequency += 1
let updatedProductList = updateAddProductList(action.payload,state.cart);
let newTotalAfterAdd = (parseFloat(state.total) + action.payload.price).toFixed(2)
return{
...state,
cart: updatedProductList,
total: newTotalAfterAdd
}
case SUB_QUANTITY:
let lastItemGone = action.payload
//if the qt == 0 then it should be removed
if(action.payload.frequency === 1){
let new_items = state.cart.filter(item=> action.payload.id !== item.id)
lastItemGone.totalPrice = 0
let newTotal = (parseFloat(state.total) - action.payload.price).toFixed(2)
return{
...state,
cart: new_items,
total: newTotal
}
}
else {
// addedItem.frequency -= 1
let updatedProductList = updateDeleteProductList(action.payload,state.cart);
console.log("updatedProductlist" + JSON.stringify(updatedProductList))
// const newState = state.set( 'orderProducts', updatedProductList).set('lastOperation', action.type);
// return newState;
// let newCart = state.cart
// for (let i =0; i<newCart.length; i++){
// if(addedItem.name === newCart[i].name){
// newCart[i].frequency -=1
// }
// }
// console.log("yo new cart is" + JSON.stringify(newCart))
let newTotal = (parseFloat(state.total) - action.payload.price).toFixed(2)
return{
...state,
cart: updatedProductList,
total: newTotal
}
}
default:
return state;
}
}

Related

How to call another generator in redux-saga?

I would like to fetch detail information with getTemplateCoins generator from each item's id of fetched list in the same generator. My idea is to iterate through active and unactive list and return new list with additional information
MyCollectionSaga
export function* getMyCollectionDetails(api) {
try {
const responseDetails = yield makeRequest(api, api.getMySocialProfile, null);
const responseBadges = yield makeRequest(api, api.getBadges, null);
const responseTemplates = yield makeRequest(api, api.getTemplates, null);
if (responseDetails.ok && responseBadges.ok && responseTemplates.ok) {
const templates = responseTemplates.data.results.map((e) => parseTemplate(e));
const active = templates.filter((e) => e.active);
const unactive = templates.filter((e) => !e.active);
//Would like to call getTemplateCoins here with templateId as item's id of active and unactive list above.
//then save them in the new array and pass them to getMyCollectionDetailSuccess below
yield put(
MyCollectionsActions.getMyCollectionDetailsSuccess(
responseDetails.data.result,
responseBadges.data.result,
active,
unactive
)
);
}
} catch (error) {`
log(error);
}
}
export function* getTemplateCoins(api, { templateId }) {
try {
const params = { templateId };
const response = yield makeRequest(api, api.getTemplateCoins, params);
if (response.ok) {
const coinsCollections = response.data.results.coins.filter((e) => e.inCollection);
const coinsMissing = response.data.results.coins.filter((e) => !e.inCollection);
const coins = [
{ title: i18n.t('common:collectionTemplateSectionCollected'), data: coinsCollections },
{ title: i18n.t('common:collectionTemplateSectionMissing'), data: coinsMissing },
];
yield put(CollectionTemplateActions.getTemplateCoinsSuccess(coins));
} else {
yield put(
CollectionTemplateActions.getTemplateCoinsFailure(i18n.t('common:genericErrorMessage'))
);
}
} catch (error) {
log(error);
yield put(
CollectionTemplateActions.getTemplateCoinsFailure(i18n.t('common:genericErrorMessage'))
);
}
}`
Here is the redux function
MyCollectionRedux
const getMyCollectionDetails = (state) => {
return { ...state, isLoading: true, error: false, message: '' };
};
const getMyCollectionDetailsSuccess = (
state,
{ socialProfile, allBadges, templatesActivated, templatesUnactivated }
) => {
return {
...state,
socialProfile,
allBadges,
templatesActivated,
templatesUnactivated,
isLoading: false,
error: false,
};
};
const getMyCollectionDetailsFailure = (state, { message }) => {
return { ...state, message, isLoading: false, error: true };
};

Vue 3 Composition API broken after re-rendering the component

thanks for reading my post.
It's hard to describe my issue.
I have a reactive object cached globally(like a store and let's call it 'store' here) to preserve the states together with some actions. It's working all good initially. But when I added more pages & switching between the pages, the components are broken after re-rendering. The components can still read correctly from the store and call the actions to update the store data and can console.log the 'fingerprint' to show it's the same store. But the updated data in store is no longer updated in the UI. I can console.log the data in store which looks all good, but the UI is frozen with the data since last unmounted.
I'm providing my component code below.
<template>
<div class="min-w-full relative" ref="container">
<hi-transition slow>
<center-box
v-if="loading"
class="absolute w-full h-full left-0 top-0 text-primary-light opacity-50"
>
<hi-spinner class="w-1/6 h-1/6" :stroke="2" />LOADING...
</center-box>
<div v-else-if="noResult">
No Result
</div>
<hi-markable
v-else
class="w-full h-full divide-y divide-primary-lighter overflow-y-auto block"
:search="searchValue"
>
<div
v-for="item in displayItems"
:key="item.id"
class="hover:bg-primary-lightest transition-colors duration-500 flex items-center block"
#click="selectItem(item)"
:style="{
'min-height': minItemHeight + 'px',
height: itemHeight + 'px'
}"
:class="{ active: currentItem === item }"
>
<slot :item="item" />
</div>
</hi-markable>
</hi-transition>
</div>
</template>
<script>
import { inject, ref } from "vue";
import HiSpinner from "#/ui/HiSpinner";
import CenterBox from "#/ui/elements/CenterBox";
import HiTransition from "#/ui/HiTransition";
import HiMarkable from "#/ui/HiMarkable";
import { computed } from "vue";
export default {
name: "HiDataList",
components: { HiMarkable, HiTransition, CenterBox, HiSpinner },
props: {
storeToken: String,
minItemHeight: [Number, String],
autoItemsPerPage: Boolean
},
emits: ["select"],
setup(props, { emit }) {
//this store is a reactive object cached somewhere
const store = inject(props.storeToken);
//to make sure it's the same store
console.log("fingerprint", store.fingerprint);
const { displayItems, currentItem, loading, searchValue, noResult } = store;
const container = ref(null);
const itemHeight = computed(() => {
return props.autoItemsPerPage
? store.autoItemHeight.value
: props.minItemHeight;
});
if (props.autoItemsPerPage) {
store.autoItemsPerPage(container, props.minItemHeight);
}
function selectItem(item) {
console.log(item);
emit("select", item);
store.setCurrentItem(item);
}
return {
displayItems,
selectItem,
container,
itemHeight,
currentItem,
loading,
searchValue,
noResult
};
}
};
</script>
<style scoped>
.active,
.active:hover {
#apply bg-primary-lighter border-primary-light;
}
</style>
The listStore
import { computed, reactive, watch, toRefs } from "vue";
import { useElementSize } from "#vueuse/core";
import { watchProps } from "#/utils/reactiveHelpers";
function filter(item, filters) {
const f = (key, filter) => {
const itemVal = item[key];
if (Array.isArray(itemVal)) return itemVal.indexOf(filter) >= 0;
else return itemVal === filter;
};
for (let key in filters) {
const filterVal = filters[key];
if (Array.isArray(filterVal)) {
for (let i = 0; i < filterVal.length; i++) {
if (f(key, filterVal[i])) return true;
}
} else {
return f(key, filterVal);
}
}
return false;
}
const getTime = date => {
if (date.milliseconds) return date.milliseconds;
if (date.seconds) return date.seconds;
if (date.getTime) return date.getTime();
};
const createListStore = (source, settings = {}) => {
const state = reactive({
source: source,
currentPage: 0,
itemsPerPage: 0, //zero means display all
loading: true,
ready: false,
noData: false,
noResult: false,
filters: {},
search: null,
searchables: settings.searchables || [],
sortBy: null,
sortType: "alpha",
desc: false,
currentItem: null,
autoItemHeight: 0,
fingerprint: Math.random() * 10000000000000000
});
// const { itemsPerPage,source,filters,search,searchables,sortBy,sortType,desc } = toRefs(state);
const {
itemsPerPage,
ready,
loading,
noResult,
search,
autoItemHeight
} = toRefs(state);
watchProps(state, "source", v => {
if (typeof v !== "undefined") {
state.ready = true;
state.loading = false;
if (!v.length) state.noData = true;
}
});
const currentPage = computed(() => state.currentPage + 1);
// const itemsPerPage = computed(() => state.itemsPerPage);
const totalItems = computed(() => results.asc.length);
const from = computed(() => {
if (totalItems.value === 0) return 0;
return state.currentPage * state.itemsPerPage + 1;
});
const to = computed(() => {
const t = from.value + displayItems.value.length - 1;
return t > totalItems.value ? totalItems.value : t;
});
const totalPages = computed(() => {
if (totalItems.value === 0 || state.itemsPerPage === 0) return 1;
return Math.ceil(totalItems.value / state.itemsPerPage);
});
const gotoPage = page => {
console.log("gotoPage", page);
state.currentPage = page - 1;
console.log(state.currentPage);
};
const prevPage = () => {
if (state.currentPage > 0) state.currentPage--;
};
const nextPage = () => {
if (state.currentPage < totalPages.value) state.currentPage++;
};
const updateFilters = (filter, val) => {
state.filters[filter] = val;
};
/**
*
* #param column
* #param desc
* #param type "alpha"|"number"|"date"|"time"
*/
const sortBy = (column, desc = false, type = "alpha") => {
state.sortBy = column;
state.desc = desc;
state.sortType = type;
};
function doSearch(item) {
const searchables = state.searchables;
for (let i = 0; i < searchables.length; i++) {
const key = searchables[i];
let value = item[key];
if (value && typeof value === "string") {
value = value.toLowerCase();
}
if (value && value.indexOf(state.search) >= 0) {
return true;
}
}
return false;
}
const results = reactive({
desc: [],
asc: []
});
function calcResults() {
if (!state.ready || state.noData) return null;
// console.log("re-calc results....");
const hasFilters = Object.keys(state.filters).length > 0;
// console.log(Object.keys(state.filters));
let items = [];
if (hasFilters || (state.search && state.search.length)) {
//do filter & search
const source = state.source;
for (let i = 0; i < source.length; i++) {
const item = source[i];
// console.log(filter(item, state.filters));
if (hasFilters && !filter(item, state.filters)) {
continue;
}
if (state.search && state.search.length && !doSearch(item)) {
continue;
}
items.push(item);
}
if (!items.length) {
results.desc = results.asc = [];
return null;
}
} else {
items = state.source;
}
if (state.sortBy) {
//do sort
const sort = state.sortBy;
// const desc = state.desc ? -1 : 1;
const type = state.sortType.toLowerCase();
items.sort((a, b) => {
a = a[sort];
b = b[sort];
if (type === "date" || type === "time") {
return getTime(a) - getTime(b);
} else {
if (typeof a === "string") a = a.trim();
if (typeof b === "string") b = b.trim();
if (state.sortType.toLowerCase() === "number") {
return a - b;
} else {
return a.localeCompare(b, "en", { sensitivity: "base" });
}
}
});
}
results.asc = items;
results.desc = [...items].reverse();
// return items;
}
//changed to watch for the wired vue error.
watchProps(
state,
["source", "filters", "search", "searchables", "sortBy", "sortType"],
() => {
calcResults();
state.noResult = results.asc.length === 0;
}
);
const displayItems = computed(() => {
if (!results.asc.length) return [];
const re = state.desc ? results.desc : results.asc;
if (state.itemsPerPage === 0) return re;
const from = state.currentPage * state.itemsPerPage;
const to = from + state.itemsPerPage;
return re.slice(from, to);
});
/**
*
* #param elementRef ref
* #param minHeight Number
* #param itemHeightRef ref
*/
const autoItemsPerPage = (elementRef, minHeight) => {
const { height } = useElementSize(elementRef);
// console.log(elementRef);
watch(height, v => {
const items = Math.floor(v / minHeight);
// itemHeightRef.value = v / items;
// console.log(v / items);
state.itemsPerPage = items;
state.autoItemHeight = v / items;
});
};
const setCurrentItem = item => {
console.log("set current", state.fingerprint);
state.currentItem = item;
};
const currentItem = computed(() => state.currentItem);
return {
currentPage,
itemsPerPage,
totalItems,
displayItems,
from,
to,
totalPages,
gotoPage,
prevPage,
nextPage,
ready,
loading,
updateFilters,
sortBy,
search: v => (state.search = v),
searchValue: search,
autoItemsPerPage,
setCurrentItem,
currentItem,
noResult,
autoItemHeight,
fingerprint: state.fingerprint
};
};
export { createListStore };
The store provider
import { provide } from "vue";
import { createListStore } from "#/ui/storeProvider/listStore";
const storeDepot = {};
export const provideListStore = (token, source, settings = {}) => {
if (!storeDepot[token]) {
console.log("create new store", token);
storeDepot[token] = createListStore(source, settings);
}
provide(token, storeDepot[token]);
return storeDepot[token];
};

Why In redux Output is coming like this --> State Changed{}

I am learning redux with simple age Increment and Decrement example here is the code
const { createStore } = require(`redux`);
const initialState = {age: 21};
const reducerOne = (state = initialState, action) => {
const newState = {...state};
if(action.type === `ADD`) {
newState.age = action.value;
}
if(action.type === `SUBTRACT`) {
newState.age = action.value;
}
return newState;
}
const store = createStore(reducerOne);
store.subscribe(() => {
console.log(`State Changed` + JSON.stringify(store.getState()));
})
store.dispatch({type: `ADD`, val: 10});
store.dispatch({type: `SUBTRACT`, val: 5});
But in Output it is showing like this --> State Changed{}
Help how to fix this and to get Output
Your action is posting the val property and your reducer is reading the value property.
Change your actions this way and it will work:
store.dispatch({type: `ADD`, value: 10});
store.dispatch({type: `SUBTRACT`, value: 5});
EDIT:
You are replacing your old age value, but probably you want to add or subtract from it. You have to modify your reducer to achieve such behaviour:
const reducerOne = (state = initialState, action) => {
const newState = {...state};
if(action.type === `ADD`) {
// add to the previous `age` value and do not overwrite it
newState.age = state.age + action.value;
}
if(action.type === `SUBTRACT`) {
// subtract from the previous `age` value and do not overwrite it
newState.age = state.age - action.value;
}
return newState;
}

update value of an element of object array in redux store

There is a challenge update existing elements value in json array in redux store by an action creater.
You can run code here also shared it below;
console.clear()
const CreateTeam = (team, point) => {
return {
type:"CREATE_TEAM",
payload: {team, point}
}
}
const UpdateTeam = (team, point) => {
return {
type:"UPDATE_TEAM_POINT",
payload: {team, point}
}
}
const TeamReducer = (state = [], action) => {
if(action.type == "CREATE_TEAM")
{
return [...state, action.payload]
}
if(action.type == "UPDATE_TEAM_POINT")
{
let point=action.payload.point;
return [...state, {
...state.teams,
point:point
}]
}
return state;
}
const { createStore, combineReducers } = Redux;
const league = combineReducers({
teams: TeamReducer
})
const store = createStore(league);
store.dispatch(CreateTeam("TeamA",10));
store.dispatch(CreateTeam("TeamB",20));
store.dispatch(UpdateTeam("TeamA",15));//not work
console.log(store.getState())
create actions works fine, I expected the point value of TeamA set to 15.. but its added new object has only "point" property value 15
There is an error in name of actionTypes:
action dispatches type:"UPDATE_TEAM"
reducer handles action.type == "UPDATE_TEAM_POINT"
You have to perform immutable change, try this:
const TeamReducer = (state = [], action) => {
if(action.type == "CREATE_TEAM")
{
return [...state, action.payload]
}
if(action.type == "UPDATE_TEAM")
{
const {team, point} = action.payload;
const changedIdx = state.findIndex((item) => item.team === team);
return [...state.slice(0, changedIdx), action.payload, ...state.slice(changedIdx + 1)]
}
return state;
}

Cant get the SQLite table to be shown through RecyclerListView in React-Native

I'm pretty new to React native, I'm trying to create a dictionary app and in order to that, wanna fetch some data from a local SQLite and put it to RecyclerListView, But I can not get any results, just empty white screen. What am I doing wrong??
Here is my code:
import React, {Component} from 'react';
import {StyleSheet , View, Dimensions , Text , Image} from 'react-native';
import { RecyclerListView, DataProvider , LayoutProvider } from 'recyclerlistview';
const SCREEN_WIDTH = Dimensions.get('window').width;
var SQLite = require('react-native-sqlite-storage')
var db = SQLite.openDatabase({name: 'tes.sqlite', createFromLocation: '~dictionary.sqlite'})
export default class App extends Component {
constructor(props){
super(props);
const record = [];
db.transaction((tx) => {
tx.executeSql('SELECT * FROM tblWord', [], (tx, results) => {
// var len=results.rows.length;
for (let i = 0; i < 10 ; i++) {
let row = results.rows.item(i);
record.push({
type: 'NORMAL',
item:{
id:row.id,
name: row.word_english,
description: row.word_persian,}
});
console.log('row.word_english = ' + row.word_english ) //this shows the right output in console
}
});
});
this.state = {
list: new DataProvider((r1, r2) => r1 !== r2).cloneWithRows(record),
};
this.layoutProvider = new LayoutProvider((i) => {
return this.state.list.getDataForIndex(i).type;
}, (type ,dim) => {
switch (type) {
case 'NORMAL' :
dim.width = SCREEN_WIDTH;
dim.height=100;
break;
default:
dim.width = 0;
dim.height = 0;
break;
}
})
}
rowRenderer = (type , data) => {
const {name , description } = data.item;
return (
<View>
<Text style={{fontSize:20 , color : 'blue'}}>name: {name}</Text>
<Text>desc: {description}</Text>
</View>
)
}
render() {
return(
<View style={styles.container}>
<RecyclerListView
style = {{flex:1}}
rowRenderer={this.rowRenderer}
dataProvider={this.state.list}
layoutProvider={this.layoutProvider}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container:{
flex:1,
},
})
PS: If I replace the transaction function with the code below, it works perfectly:
for (i=0; i<10 ; i+=1) {
record.push({
type: 'NORMAL',
item:{
id:1,
name: 'name1',
description: 'this is for testing.',
},
});
}
try this:
db.transaction(tx => {
tx.executeSql('SELECT * FROM tblWord',
[], (_, { rows: { _array } }) => {console.log(_array)})
})

Resources