How to store a list of items when items can exist in different modules?
One state to items or Multi states to items?
I assume there will be a lot of different modules that will store items and the more of these modules, the more I want to move away from one state
One state
class Item {
id: number;
name: string;
location: "equipment" | "magazine" | "shop" // | and other in future...
}
class AppState {
items: Item[];
}
const getItems = (store: Store): Observable<Item[]> => {
return store.select((appStore: AppStore) => appStore.items);
}
const getItemsInEquipment = (store: Store): Observable<Item[]> => {
const items$: Observable<Item[]> = getItems(store);
return items$.pipe(
map(items => items.filter(item => item.location === "equipment"))
);
}
const getItemsInMagazine = (store: Store): Observable<Item[]> => {
const items$: Observable<Item[]> = getItems(store);
return items$.pipe(
map(items => items.filter(item => item.location === "magazine"))
);
}
const getItemsInShop = (store: Store): Observable<Item[]> => {
const items$: Observable<Item[]> = getItems(store);
return items$.pipe(
map(items => items.filter(item => item.location === "shop"))
);
}
or...
Multi states
class Item {
id: number;
name: string;
}
class AppState {
itemsInEquipment: Item[];
itemsInMagazine: Item[];
itemsInShop: Item[];
// | and other in future...
}
const getItemsInEquipment = (store: Store): Observable<Item[]> => {
return store.select((appStore: AppStore) => appStore.itemsInEquipment);
}
const getItemsInMagazine = (store: Store): Observable<Item[]> => {
return store.select((appStore: AppStore) => appStore.itemsInMagazine);
}
const getItemsInShop = (store: Store): Observable<Item[]> => {
return store.select((appStore: AppStore) => appStore.itemsInShop);
}
const getItems = (store: Store): Observable<Item[]> => {
const itemsInEquipment$: Observable<Item[]> = getItemsInEquipment(store);
const itemsInMagazine$: Observable<Item[]> = getItemsInMagazine(store);
const itemsInShop$: Observable<Item[]> = getItemsInShop(store);
return combineLatest([itemsInEquipment$, itemsInMagazine$, itemsInShop$]);
}
What are the advantages and disadvantages of both approaches?
I would suggest a different approach. In NgRx, for example, we would set up a single Entity state for all Items, and then simply store arrays of the IDs of the Items in each list. NGXS Labs has an Entity state adapter that might be worth trying.
Basically, you have all of your Items stored in an Object, where the keys are the id field for each Item:
itemEntities: {
itemId1: Item,
itemId2: Item,
itemId3: Item
}
This allows for direct lookup, and prevents performance losses caused by Array actions.
Then, each of your itemsInEquipment, itemsInStore etc are all simple Arrays of ids, allowing you to grab them from the Entity state.
Related
I try to fetch the current offer price for my NFT project but i currently get undefined in this function
useEffect(() => {
returnsCurrentOfferPrice(NFT.tokenId)
.then((offer) => {
console.log(offer);
setReturnCurrentOfferPrice(offer);
})
.catch((error) => {
console.log('Current offer price error', error);
});
}, [NFT.tokenId]);
This is my use State Snippet
const [returnCurrentOfferPrice, setReturnCurrentOfferPrice] = useState(null);
This is how i retrieve the function into my UI
const returnsCurrentOfferPrice = async (tokenId) => {
await getCurrentOfferPrice(tokenId)
}
And finally this is how i retrieve the data from the blockchain
const getCurrentOfferPrice = async (tokenId) => {
const web3Modal = new Web3Modal();
const connection = await web3Modal.connect();
const provider = new ethers.providers.Web3Provider(connection);
const contract = signerOrProvider(provider);
const currentOfferPrice = await contract.getCurrentOfferAmount(tokenId);
const bigNumber = ethers.BigNumber.from(currentOfferPrice);
const currentOfferPriceInEther = ethers.utils.formatEther(bigNumber)
console.log('Current offer price', currentOfferPriceInEther );
return currentOfferPriceInEther;
}
I often use this code to fetch and update data for my like button. It works but I wonder if there is a more effective or cleaner way to do this function.
const isPressed = useRef(false); // check the need to change the like count
const [like, setLike] = useState();
const [count, setCount] = useState(count_like); // already fetch data
const [haveFetch, setHaveFetch] = useState(false); // button block
useEffect(() => {
fetchIsLike(...).then((rs)=>{
setLike(rs);
setHaveFetch(true);
})
return () => {}
}, [])
useEffect(()=>{
if(like) {
// animation
if(isPressed.current) {
setCount(prev => (prev+1));
// add row to database
}
}
else {
// animation
if(isPressed.current) {
setCount(prev => (prev-1));
// delete row from database
}
}
}, [like])
const updateHeart = () => {
isPressed.current = true;
setLike(prev => !prev);
}
My first observable returns an array of Persons. I want to update each person of that array with a list of clients from second observable. How do I do that? So far I have this:
const json: Person[] = [new Person('Alice'), new Person('Bob')];
const resultsObservable = new Observable<string[]>(subscriber => {
setTimeout(() => {
subscriber.next(['Client1', 'Client2', 'Client3']);
subscriber.complete();
}, 1000);
});
of(json).pipe(
switchMap( dataArray => {
return from(dataArray);
}),
map((x: Person) => {
resultsObservable.subscribe(r => {
x.clients = r;
});
return x;
}),
).subscribe(value => {
console.log(value);
});
}
Person:
export class Person{
name: string;
clients?: string[];
constructor(name: string) {
this.name = name;
}
}
But the problem is that return happens before the values are set, so at the end value of person.clients is undefined. How do I fix this? Or what is a better way to achieve what I'm trying to do?
Ok I think I found what I was looking for:
const result = persons.pipe(
mergeMap(p => resultsObservable.pipe(map(clients => {
p.clients = clients;
return p;
}))),
);
result.subscribe(p => console.log(p));
I have an operator that does some recursive operations on a child property of the source. What do I do to merge the child property back into the source after I'm done the recursive operation?
const state = {
posts: [
{id: 3, title: 't1', userId: 1},
],
index: 0,
config: {
previousBufferSize: 1,
nextBufferSize: 1,
}
};
const source = new BehaviorSubject(state);
const generatePreviousPosts$ = (posts) => {
return Observable.create(observer => {
getPost(posts[0].id - 1)
.then(previousPost => {
observer.next([previousPost, ...posts]);
});
});
};
const previousBuffer$ = source.pipe(
pluck('posts'),
expand(generatePreviousPosts$),
tap(console.log),
// What do I do to merge post back in the state so I could use takeWhile?
takeWhile(state => {
const {posts, config, index} = state;
return posts.length <= config.previousBufferSize - index + posts.length &&
posts[0].id != null;
})
);
One way to do it is to use mergeMap, but I feel like there could be a more elegant solution to this problem.
const previousBuffer$ = source.pipe(
mergeMap(
state => (of(state.posts)
.pipe(expand(generatePreviousPosts$))),
(state, posts) => ({...state, posts})),
takeWhile(state => {
const {posts, config, index} = state;
return posts.length <= config.previousBufferSize - index + posts.length &&
posts[0].id != null;
})
);
A much more elegant solution given by https://github.com/Dorus on the rxjs gitter.
const shouldGetPost = ({posts, config, index}) => posts.length <= config.previousBufferSize - index + posts.length
&& posts[0].id != null
const generatePreviousPosts = ({posts, config, index}) => !shouldGetPost({posts, config, index}) ? EMPTY :
from(getPost(posts[0].id - 1)).pipe(
map(previousPost => ({[previousPost, ...posts], config, index}))
)
const previousBuffer$ = source.pipe(
expand(generatePreviousPosts)
);
I try to retrieve datas in a subcollection based on the key received on the first call.
Basically, I want a list of all my user with the total of one subcollection for each of them.
I'm able to retrieve the data from the first Payload, but not from pointRef below
What is the correct way to achieve that?
getCurrentLeaderboard() {
return this.afs.collection('users').snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data()
const id = a.payload.doc.id;
const pointRef: Observable<any> = this.afs.collection('users').doc(`${id}`).collection('game').valueChanges()
const points = pointRef.map(arr => {
const sumPoint = arr.map(v => v.value)
return sumPoint.length ? sumPoint.reduce((total, val) => total + val) : ''
})
return { id, first_name: data.first_name, point:points };
})
})
}
I tried to put my code in a comment, but I think it's better formated as a answer.
First you need subscribe your pointRef and you can change your code like this.
getCurrentLeaderboard() {
return this.afs.collection('users').snapshotChanges().map(actions => {
return actions.map(a => {
const data = a.payload.doc.data()
const id = a.payload.doc.id;
const pointRef: Observable<any> = this.afs.object(`users/${id}/game`).valueChanges() // <--- Here
const pointsObserver = pointRef.subscribe(points => { //<--- And Here
return { id, first_name: data.first_name, point:points };
})
})
}
....
//Usage:
getCurrentLeaderboard.subscribe(points => this.points = points);
And if you going to use this function alot, you should start to denormalize your data.