Vue 3 Composition API broken after re-rendering the component - vuejs3

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];
};

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 };
};

Unexpected mutation in pinia prop when change local component reactive()

When the value of an reactive object is changed, a prop in my Pinia store is mutated. I tried different ways of attributing new value to this store to avoid this problem, but so far I dont't really understand what is happenning. This is my code:
Component
const form = reactive({
name: "",
exercises: [{ exercise: "", method: "", series: "" }],
});
const handleChange = ({ value, name }: any, eventIndex: any) => {
const newEx = form.exercises.map((ex, index) => ({
...ex,
...(index === eventIndex ? { [name]: value } : null),
}));
const newForm = { name: form.name, exercises: newEx };
Object.assign(form, newForm)
};
const onSubmit = async () => {
const { valid } = await formRef.value.validate();
if (!valid) return;
const storeTraining = workoutStore.newWorkout.training || [];
const workout = {
...workoutStore.newWorkout,
training: [...storeTraining, form],
};
workoutStore.setNewWorkout(workout);
isOpen.value = false;
};
Store
import { ref, computed } from "vue";
import { defineStore } from "pinia";
import type { IUser } from "#/domain/users/type";
import type { ITraining, IWorkout } from "#/domain/workouts/types";
export const useWorkoutStore = defineStore("workouts", () => {
const creatingWorkoutStudent = ref<IUser | null>(null);
const newWorkout = ref<Partial<IWorkout>>({});
const setCreatingWorkoutStudent = (newUser: IUser) => {
creatingWorkoutStudent.value = newUser;
};
const setNewWorkout = (workout: Partial<IWorkout>) => {
newWorkout.value = { ...workout };
return newWorkout.value;
};
const addNewTraining = (training: ITraining) => {
newWorkout.value.training
? newWorkout.value.training.push(training)
: (newWorkout.value.training = [training]);
};
const reset = () => {
newWorkout.value = {};
creatingWorkoutStudent.value = null;
};
return {
creatingWorkoutStudent,
setCreatingWorkoutStudent,
newWorkout,
setNewWorkout,
reset,
addNewTraining,
};
});
And this is a example of an input that mutates reactive values
<text-field
label="ExercĂ­cio"
#input="
handleChange(
{ value: $event.target.value, name: 'exercise' },
index
)
"
:value="exercise.exercise"
:rules="[validationRules.required]"
/>
Every time that a input changes a value in const form = reactive(), the same property in Pinia store is changed too.
I've created a fiddle to exemplify my issue here

Redux State getting reset/overridden when new component loads

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;
}
}

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)})
})

TypeError: defaultPieces inside of componentDidMount is not a function

Inside of my PossibleMatches component, I have imported a few asynchronous functions from my actions/index file and as I call these functions inside of componentDidMount, the Error I'm getting back is: defaultPieces is not a function.
Below are the contents of my PossibleMatches component and actions/index.js file:
For the sake of brevity, I did my best to add everything that is relevant to the main problem.
PossibleMatches.js
import { connect } from 'react-redux';
import {arrangePieces, defaultPieces, setEvaluatedPiece, getCorrespondingPieces} from '../actions/index';
constructor(props){
super(props);
const initialState = {
currentComponent: {whichPiece: {whichType: null, currentUpperComponent: null, currentLowerComponent: null}},
UpperComponents: this.props.UpperComponents,
LowerComponents: this.props.LowerComponents,
UpperComponentEnabled: false,
LowerComponentEnabled: false,
isFetched: false,
isFetching: true
}
this.state = {
...initialState
}
this.residingUpperComponent = createRef();
this.residingLowerComponent = createRef();
//Also need to this.prop.setEvaluatedPiece based off of this.props.setCurrentComponent if callback from Lower or Upper Components elapses time
this.setNewPiece = this.setNewPiece.bind(this);
this.renderDecision = this.renderDecision.bind(this);
}
async componentDidMount(){
const {store} = this.context;
let stateRef = store.getState();
const { defaultPieces, arrangePieces } = this.props;
try {
const summon = () => { defaultPieces();
arrangePieces();}
await summon();
} catch(error){
throw Error(error);
}
console.log("AFEWOVMAOVHIE")
this.setState({isFetched: true, isFetching: false});
}
renderDecision(){
const { currentComponent, LowerComponentEnabled, UpperComponentEnabled, isFetching, isFetched} = this.state;
const { suggestedBottoms, suggestedTops, UpperComponents, LowerComponents } = this.props;
if (isFetching){
return (<div className='activityLoader'>
<ActivityIndicator number={3} duration={200} activeColor="#fff" borderWidth={2} borderColor="50%" diameter={20}/>
</div>);
} else if (isFetched){
return (
<div className = "PossibleMatches_Container">
<i className = 'captureOutfit' onClick = {this.snapshotMatch}></i>
<TransitionGroup component = {PossibleMatches}>
** ** ** {UpperComponents.map((component) => {
return (<UpperComponent key={component.created_at} id={component.id}
switchComponent={this.switchFocus}
setCurrentPiece={this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {() => {if (LowerComponentEnabled === false){this.setState({LowerComponentEnabled: true})} else return; this.setState({currentLowerComponent: suggestedBottoms[0]})}}
isLowerComponentEnabled = {LowerComponentEnabled}
ref = {this.residingUpperComponent}
className = {currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>
)
})
}
</TransitionGroup>
<TransitionGroup component = {PossibleMatches}>
{LowerComponents.map((component) => {
return (<LowerComponent key={component.created_at} id={component.id}
setCurrentPiece = {this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {() => {if (UpperComponentEnabled === false){this.setState({UpperComponentEnabled: true})} else return; this.setState({currentUpperComponent: suggestedTops[0]})}}
switchComponent = {this.switchFocus}
isUpperComponentEnabled = {UpperComponentEnabled}
ref = {this.residingLowerComponent}
className = {this.state.currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : this.state.currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>)
})
}
</TransitionGroup>
</div>
)
}
}
render(){
return(
<div className = 'GorClothingContainer'>
<Wardrobe upperComponent={this.state.currentComponent.whichPiece.currentUpperComponent} lowerComponent={this.state.currentComponent.whichPiece.currentLowerComponent} enableCapture={(snapshot) => this.snapshotMatch = snapshot} />
{this.renderDecision()}
</div>
);
}
function mapStateToProps(state){
const { UpperComponents, LowerComponents, contemplated_piece, extraTops, extraBottoms, standaloneBottoms, standaloneTops, suggestedBottoms, suggestedTops } = state.possibleMatches;
return {UpperComponents, LowerComponents, contemplated_piece, extraTops, extraBottoms, standaloneBottoms, standaloneTops, suggestedBottoms, suggestedTops };
}
export default connect(mapStateToProps, {defaultPieces, arrangePieces, getCorrespondingPieces, setEvaluatedPiece})(PossibleMatches)
Inside of my actions/index.js
export function defaultPieces(){
return function(dispatch){
fetch(`${API_URL}/possible_matches/setup_possible_matches`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then((res) => res.json())
.then((json) => {
console.log('defaultPieces: ', json);
dispatch(getInitialPieces(json))
})
}
}
export function getInitialPieces(request){
return {
type: INITIAL_PIECES,
payload: request
}
}
Inside of PossibleMatches reducer:
import {INITIAL_PIECES, GET_ANCILLARY_PIECES, ORGANIZE_PIECES, SET_CONTEMPLATED_PIECE} from '../actions/types';
const initialState = {
UpperComponents: [],
LowerComponents: [],
contemplated_piece: null,
extraTops: [],
extraBottoms: [],
standaloneTops: [],
standaloneBottoms: [],
suggestedTops: [],
suggestedBottoms: []
}
export default function(state = initialState, action){
switch(action.type){
case INITIAL_PIECES:
return {...state, {contemplated_piece: action.payload.contemplated_piece,
extraTops: action.payload.extra_tops,
extraBottoms: action.payload.extra_bottoms,
standaloneTops: action.payload.standalone_tops,
standaloneBottoms: action.payload.standalone_bottoms,
suggestedTops: action.payload.suggested_tops,
suggestedBottoms: action.payload.suggested_bottoms}
case GET_ANCILLARY_PIECES:
return {...state, extraTops: action.payload.extra_tops,
extraBottoms: action.payload.extra_bottoms,
standaloneTops: action.payload.standalone_tops,
standaloneBottoms: action.payload.standalone_bottoms,
suggestedTops: action.payload.suggested_tops,
suggestedBottoms: action.payload.suggested_bottoms}
case ORGANIZE_PIECES:
return {...state, UpperComponents: action.payload.UpperComponents,
LowerComponents: action.payload.LowerComponents}
case SET_CONTEMPLATED_PIECE:
return {...state, contemplated_piece: action.payload.contemplated_piece}
default:
return state;
}
}
Because defaultPieces is not a valid function to the PossibleMatches components, it interferes with the interpretation of the UpperComponents prop that comes from mapStateToProps function (denoted with an * above).
What is peculiar is the json logged out to the console from both the arrangePieces and defaultPieces methods:
It was a bizarre fix, but I basically needed to set conditions in ComponentDidMount to halt the program based on the state of UpperComponents relative to what got returned from this.props.arrangePieces().
if (UpperComponents.length === 0){
return;
} else {
this.setState({isFetched: true, isFetching: false});
}
Since the component rerenders, I thought it was idealistic to add a componentWillRecieveProps lifecycle method to deal with incoming props:
componentWillReceiveProps(nextProps){
if (nextProps.contemplated_piece !== this.props.contemplated_piece){
if (nextProps.contemplated_piece.merch_type === 'top'){
this.setState({currentLowerComponent: nextProps.suggestedBottoms[0],
currentUpperComponent: nextProps.contemplated_piece});
}
else if (nextProps.contemplated_piece.merch_type === 'bottom'){
this.setState({currentLowerComponent: nextProps.contemplated_piece,
currentUpperComponent: nextProps.suggestedTops[0]});
}
}
else {
return null;
}
}

Resources