Ngrx/data getWithQuery does not filter data according to the query I gave it - angular-ngrx-data

When I try to query for Approved Tags like so, it returns all entries not the approved ones
getAllApprovedTags(): Observable<Array<Tag>> {
return this.tagsService.getWithQuery({tagStatus:'Approved'});
}
I have also tried the following
getAllApprovedTags(): Observable<Array<Tag>> {
return this.tagsService.getWithQuery("tagStatus='Approved'");
}
And the following
getAllApprovedTags(): Observable<Array<Tag>> {
return this.tagsService.getWithQuery("tagStatus=Approved");
}
I am using
Angular CLI: 11.0.1
Node: 10.19.0
OS: linux x64
NgRx/Data: 10.1.2
The back-end is
Loopback 4 and MongoDB
I have the following model
export class Tag {
constructor(
public id: string,
public tagName: string,
public tagDescription: string,
public tagStatus: string,
public createdDate: Date,
){ }
}
My entity config looks like the following
const entityMetadata: EntityMetadataMap = {
Tag: {},
};
const pluralNames = {
Tag: 'Tags',
};
export const entityConfig = {
entityMetadata,
pluralNames,
};
The service class looks like the following
#Injectable({
providedIn: 'root'
})
export class TagsService extends EntityCollectionServiceBase <Tag> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Tag', serviceElementsFactory);
}
}
The data is as follows
Example Data

getWithQuery simply adds query parameters to the endpoint URL:
/api/tags/?tagStatus=Approved
Even if the server is set up to handle this, it may not be what you're trying to do. More context is needed, but based off your question, it sounds like you're wanting to filter the collection of Tag entities down to those whose status is "Approved". If so, maybe you want filterFn (docs here):
const entityMetadata: EntityMetadataMap = {
Tag: {
filterFn: (tags: Tag[], status: TagStatus) => {
return tags.filter(tag => tag.status === status);
}
},
};
And in your component:
getAllApprovedTags(): Observable<Array<Tag>> {
this.tagService.setFilter(TestStatus.Approved); // or 'Approved' if you don't use an enum
return this.tagsService.filteredEntities$;
}
However, from my understanding, any other places using that filter would also be changed, so it may be better for your use case to use a selector.
#Injectable({
providedIn: 'root'
})
export class TagsService extends EntityCollectionServiceBase <Tag> {
approvedTags$ = this.entities$.pipe(select(selectApprovedTags));
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Tag', serviceElementsFactory);
}
}
// selectors
export const selectApprovedTags = createSelector(
(tags) => tags,
(tags: Tag[]) => tags.filter(tag => tag.status === TagStatus.Approved)
);
above selector implementation based on what I'm seeing in their demo

Related

Mapping over an array in React typescript

I have a static data structure that I'm serving from my Next project's API route which is an array of objects with the following interface:
export interface Case {
id: string;
title: string;
beteiligte: string[];
gerichtstermin?: string;
tasks?: [];
}
I'm using React Query to pull in the data in a component:
const Cases:React.FC = () => {
const { data, status } = useQuery("cases", fetchCases);
async function fetchCases() {
const res = await fetch("/api/dummy-data");
return res.json();
}
return (
<DefaultLayout>
<Loading loading={status === "loading"} />
{data.cases.map((case)=><p>{case.name}</p>)}
</DefaultLayout>
);
}
export default Cases;
But when trying to map over the data I keep getting these squiggly lines and an incredibly ambiguous error:
I've tried absolutely everything I can think of and have searched far and wide through React Query documentation, typescript guides and of course many, many stackoverflow posts. Please help! (typescript newbie)
UPDATE:
When I type {data.cases.map(()=>console.log("hello")} there is no error at all. But as soon as I add a parameter to the anonymous function, the errors pop up: {data.cases.map((case)=>console.log("hello"))}
In the case interface, you do not have "name" property.
you should be mapping only if success is true. you have to guard your code. see what you get console.log(data). also, use key prop when mapping
{status === "success" && (
// case is reserved word. use a different name
{data.cases.map((caseItem) => (
<p key={caseItem.id}>{caseItem.name}</p>
))}
)}
case is a reserved word. If you use any other word it works
I tried to reproduce your issue in sandbox and I managed to make it "transpile"
Sandbox link
You have to add prop name of type string to the Case interface in order to use it
export interface Case {
name: string;
id: string;
title: string;
beteiligte: string[];
gerichtstermin?: string;
tasks?: [];
}
// in my example interface is named CaseResonse
I assume your API response looks like this
{ "data": { "cases": [] } }
You have to define the API response to be usable:
interface GetCaseResponseWrapper {
cases: Array<Case>;
}
// in my example it is array of CaseResponse
interface GetCaseResponseWrapper {
cases: Array<CaseResponse>;
}
You need to decorate your fetchCases to return GetCaseResponseWrapper
async function fetchCases() {
const res = await fetch("/api/dummy-data");
const jsonResult = await res.json();
return jsonResult.data as GetCaseResponseWrapper;
}
the data from the useQuery may be undefined, so you should use ?.
{data?.cases.map((c) => (
<p>{c.name}</p>
))}
Sandbox link

Updating mobx store observable within self results in 'Cannot read property of <observable> of undefined'

I know that several similar issues like that exist already. However, having reviewed many of them I was not able to find solution to my problem. Here is the situation:
I want my ReportStore to fetch data from the server in form of an array and store them in an observable map. Fetching part is OK, the data is coming correctly. The problem occurs when I try to write values to observable map inside of requestNewReports method. Here is the store code:
ReportStore.ts
import api from "../api";
import { observable, action, ObservableMap } from "mobx";
import { IReportMetaData } from "../pages/ManagerView/TicketSales";
import { notificationStore } from "./NotificationStore";
export class ReportStore {
fetchedReports: ObservableMap<number, IReportMetaData>;
constructor() {
this.fetchedReports = observable.map({});
}
async requestNewReports(fromDate: string = '', toDate: string = '') {
try {
const res = await api.get("reports/filtered", {
params: {
fromDate: from,
toDate: to
}
});
res.data.forEach((row: IReportMetaData, index: number) => {
if (!this.fetchedReports.has(index)) {
this.fetchedReports.set(index, row);
}
});
} catch (err) {
return notificationStore.showError(err.message);
}
}
}
export const reportStore = new ReportStore();
The store is being provided in app.tsx via <Provider reportStore={reportStore}>....</Provider> and injected in the components like that:
export interface ITicketSalesProps {
reportStore?: ReportStore;
}
export const TicketSales = inject('reportStore')(observer((props: ITicketSalesProps) => {
// Component logic
}
And being called like that:
// inside TicketSales
useEffect(() => {
const { fetchedReports, requestNewReports } = props.reportStore!;
if (fetchedReports.size === 0) {
requestNewReports();
}
}, []);
My setup:
create-react-app via npx with TypeScript,
"mobx": "^5.15.3",
"mobx-react": "^6.1.4",
According to some issues the problem lies in legacy decorators. However,according to official mobx documentation
Decorators are only supported out of the box when using TypeScript in create-react-app#^2.1.1. In older versions or when using vanilla JavaScript use either the decorate utility, eject, or the customize-cra package.
Which seems to be exactly my case.
Hence I am out of ideas of why it can be broken...
I even tried to eject and try babel plugin as was suggested in some issues:
{
"plugins": [
["#babel/plugin-proposal-decorators", { "legacy": true }],
["#babel/plugin-proposal-class-properties", { "loose" : true }]
]
}
But no success.
Any help would be appreciated.
Thanks in advance!
I think this issue comes from your javascript code in general and not mobx.
From what I can read here, requestNewReports() is not a bound method. In your component you destructure your store and assign requestNewReports to a new variable so "this" is not your store anymore. Thus you have the "undefined" error.
You could change it to:
// inside TicketSales
useEffect(() => {
const {reportStore} = props
if (reportStore.fetchedReports.size === 0) {
reportStore.requestNewReports();
}
}, []);
Or, you could bind the method to the instance like:
...
constructor() {
this.fetchedReports = observable.map({});
this.requestNewReports = this.requestNewReports.bind(this)
}

React Apollo first object from subscription not being merge into previous data it actually gets removed

I have a query which gets me a list of notes and a subscription which listens and inserts new notes by altering the query. However the problem is the first note doesn't get added.
So let me add more detail, initially the query response with an object which contains an attribute called notes which is an array of 0 length, if we try and add a note the attribute gets removed. The note is created so if I refresh my application the query will return the note then If I try and add a note again the note gets added to the array in the query object.
Here is my notes container where I query for notes and create a new property to subscribe to more notes.
export const NotesDataContainer = component => graphql(NotesQuery,{
name: 'notes',
props: props => {
console.log(props); // props.notes.notes is undefined on first note added when none exists.
return {
...props,
subscribeToNewNotes: () => {
return props.notes.subscribeToMore({
document: NotesAddedSubscription,
updateQuery: (prevRes, { subscriptionData }) => {
if (!subscriptionData.data.noteAdded) return prevRes;
return update(prevRes, {
notes: { $unshift: [subscriptionData.data.noteAdded] }
});
},
})
}
}
}
})(component);
Any help would be great, thanks.
EDIT:
export const NotesQuery = gql`
query NotesQuery {
notes {
_id
title
desc
shared
favourited
}
}
`;
export const NotesAddedSubscription = gql`
subscription onNoteAdded {
noteAdded {
_id
title
desc
}
}
`;
Another EDIT
class NotesPageUI extends Component {
constructor(props) {
super(props);
this.newNotesSubscription = null;
}
componentWillMount() {
if (!this.newNotesSubscription) {
this.newNotesSubscription = this.props.subscribeToNewNotes();
}
}
render() {
return (
<div>
<NoteCreation onEnterRequest={this.props.createNote} />
<NotesList
notes={ this.props.notes.notes }
deleteNoteRequest={ id => this.props.deleteNote(id) }
favouriteNoteRequest={ this.props.favouriteNote }
/>
</div>
)
}
}
Another edit:
https://github.com/jakelacey2012/react-apollo-subscription-problem
YAY got it to work, simply the new data sent down the wire needs to be the same shape as the original query.
e.g.
NotesQuery had this shape...
query NotesQuery {
notes {
_id
title
desc
shared
favourited
}
}
yet the data coming down the wire on the subscription had this shape.
subscription onNoteAdded {
noteAdded {
_id
title
desc
}
}
notice shared & favourited are missing from the query on the subscription. If we added them it would now work.
This is the problem, react-apollo internally detects a difference and then doesn't add the data I guess It would be useful if there was a little more feed back.
I'm going to try and work with the react-apollo guys to see if we can put something like that in place.
https://github.com/apollographql/react-apollo/issues/649

Meteor React Tutorial Expected string, got object

Hi guys so I got to step 9 by following this tutorial https://www.meteor.com/tutorials/react/security-with-methods
So i've written all the methods with their checks however I get errors like Exception while invoking method 'tasks.remove' Error: Match error: Expected string, got object
Here are my written codes
This is the tasks.js
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
import { check } from 'meteor/check'
export const Tasks = new Mongo.Collection('tasks')
Meteor.methods({
'tasks.insert' (text) {
check(text, String)
// Make sure the user is logged in before insterting a task
if (!this.userId) {
throw new Meteor.Error('not-authorized')
}
Tasks.insert({
text,
createdAt: new Date(),
owner: this.userId,
username: Meteor.users.findOne(this.userId).username
})
}, // tasks.insert
'tasks.remove' (taskId) {
check(taskId, String)
Tasks.remove(taskId)
},
'tasks.setChecked' (taskId, setChecked) {
check(taskId, String)
check(setChecked, Boolean)
Tasks.update(taskId, { $set: { checked: setChecked } })
}
})
And this is the Task.jsx
import React, { Component, PropTypes } from 'react'
import { Meteor } from 'meteor/meteor'
// import { Tasks } from '../api/tasks.js'
// Task component - represents a single todo item
export default class Task extends Component {
toggleChecked () {
// Set the checked value to the opposite of its current value
Meteor.call('tasks.setChecked',this.props.task._id, !this.props.task.checked)
}
deleteThisTask () {
Meteor.call('tasks.remove', this.props.task._id)
}
render () {
// Give tasks a different className when they are checked off,
// so that we can style them nicely
const taskClassName = this.props.task.checked ? 'checked' : ''
return (
<li className={taskClassName}>
<button className="delete" onClick={this.deleteThisTask.bind(this)}>
×
</button>
<input
type="checkbox"
readOnly
checked={this.props.task.checked}
onClick={this.toggleChecked.bind(this)}
/>
<span className="text">
<strong>{this.props.task.username}</strong>:{this.props.task.text}
</span>
</li>
)
}
}
Task.propTypes = {
// This component gets the task to dipslay through a React prop.
// We can use propTypes to indicate it is required
task: PropTypes.object.isRequired
}
What's the problem my written code seems to be identical with the tutorials code, why then do I get these errors? I got the same error for the update method.
EDIT: After commenting the checks and doing the later steps of the tutorial and then enabling the checks makes them work... but I am not sure which part made them to work
The check function is expecting a String because you passed String as a parameter for checking. But your React Task component is expecting Object.
Task.propTypes = {
task: PropTypes.object.isRequired
}
Just try
'tasks.remove' (taskId) {
check(taskId, Object)
Tasks.remove(taskId)
},
instead of
'tasks.remove' (taskId) {
check(taskId, String)
Tasks.remove(taskId)
},
It still gives error with 'Object'. Try concerting input with javascript String() function -
'tasks.remove'(taskId) {
check(String(taskId), String);
Tasks.remove(taskId);
},
'tasks.setChecked'(taskId, setChecked) {
check(String(taskId), String);
check(setChecked, Boolean);
Tasks.update(taskId, { $set: { checked: setChecked } });
},

Where should I handle sorting in Redux App?

I have an action / reducer / components. In one of my components (component dump) I have a Select. I get information on what type of filter my store. Where can I handle it in action, or reducer?
IMO, the right place to sort data is not directly in the reducers but in the selectors.
From redux docs:
Computing Derived Data
Reselect is a simple library for creating memoized, composable selector functions. Reselect selectors can be used to efficiently compute derived data from the Redux store.
I'm currently using selectors to filter and sort data.
No data repetition in the state. You don't have to store a copy of the item sorted by one specific way.
The same data can be used in different components, each one using a different selector function to sort for example.
You can combine selector applying many data computations using selector that you already have in the application.
If you do right, your selectors will be pure functions, then you can easily test them.
Use the same selector in many components.
I save the items, sortKey and sortKind (asc/desc) in the Redux Store.
In my Angular component (I believe would be same for React), I get the store state as an Observable so that I can display the items, sortKey and sortOrder in the UX.
When the user clicks on the table columns to change sort key (order) I dispatch the new keys/sort order to reducer for the state.
The reducer then performs the new sorting, and returns the new state with the updated values.
The Observable in the component thus sparks an event which updates the UX.
Advantage:
keep sorting logic out of the component
by saving the sortKey and sortKind in the state, you can restore precisely the UX if the user refreshes the browser (I use Redux-LocalStorage to sync)
as the store has the sorted items, you'll only perform sorting when the user actively wants it.
the sorted items are remembered for when the user might return to the component.
My reducer ( "bizzes" is my items list, and I use Immutable.List to store the items)
import { List } from 'immutable';
import { IBizz, IBizzState } from './bizz.types';
import { BIZZES_SET, BIZZES_SORT} from 'store/constants';
const SORT_ASC = 'asc';
const SORT_DESC = 'desc';
const defaultSortKey = 'serialNo';
const defaultSortOrder = SORT_ASC;
const INITIAL_STATE: IBizzState = {
bizzes: List([]),
sortKey: defaultSortKey,
sortOrder: defaultSortOrder
};
export function bizzReducer(state: IBizzState = INITIAL_STATE, action: any): IBizzState {
switch (action.type) {
case BIZZES_SET:
return {
bizzes: List(action.payload.bizzes),
sortKey: action.payload.sortKey || defaultSortKey,
sortOrder: action.payload.sortOrder || defaultSortOrder
};
case BIZZES_SORT:
let sortKey = action.payload.sortKey || defaultSortKey;
if(sortKey === state.sortKey) {
state.sortOrder = state.sortOrder === SORT_ASC ? SORT_DESC : SORT_ASC;
}
return {
bizzes: List(state.bizzes.sort( (a, b) => {
if( a[sortKey] < b[sortKey] ) return state.sortOrder === SORT_ASC ? -1 : 1;
if( a[sortKey] > b[sortKey] ) return state.sortOrder === SORT_ASC ? 1: -1;
return 0;
})),
sortKey: sortKey,
sortOrder: state.sortOrder
};
default: return state;
}
}
And my component ( I use Ng2-Redux to get the store as Observables):
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '#angular/core';
import { select } from 'store';
import { BizzActions } from 'actions/index';
#Component({
selector: 'bizzlist',
templateUrl: './bizz-list.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BizzListComponent implements OnInit {
#select([ 'bizzState']) bizzState$;
public sortOrder: string;
public sortKey: string;
public bizzes = [];
private bizzStateSubscription;
constructor(
public bizzActions: BizzActions
) { }
ngOnInit() {
this.bizzStateSubscription = this.bizzState$.subscribe( bizzState => {
this.bizzes = bizzState.bizzes;
this.sortKey = bizzState.sortKey;
this.sortOrder = bizzState.sortOrder;
});
}
ngOnDestroy() {
this.bizzStateSubscription.unsubscribe();
}
public sortBizzes(key) {
this.bizzActions.sortBizzes(key);
}
}
As you can see, I am using an Action (called BizzActions) to do the actual Redux dispatch. You could do it in your component, but I prefer to separate these things. For good measure, here's my BizzActions (a Service):
import { Injectable } from '#angular/core';
import { NgRedux, IAppState } from 'store';
import {
BIZZES_SET,
BIZZES_SORT
} from 'store/constants';
#Injectable()
export class BizzActions {
constructor (private ngRedux: NgRedux<IAppState>) {}
public setBizzes = (bizzes: any) => {
return this.ngRedux.dispatch({
type: BIZZES_SET,
payload: {
bizzes: bizzes
}
});
};
public sortBizzes = (key:string) => {
return this.ngRedux.dispatch({
type: BIZZES_SORT,
payload: {
sortKey: key
}
});
};
}
You could sort the data when #connect -ing your React component with the Redux store:
function mapStateToProps(state) {
var items = state.items.slice(0);
items.sort()
return {
items: items
}
}
#connect(mapStoreToProps)
class MyComponent extends React.Component {
render() {
var items = this.props.items;
}
}
The Redux documentation shows a similar case in the Todo example: https://redux.js.org/basics/usage-with-react
I've been sorting my reducers using a section dictionary pattern. In other words, I sort my items by headers, say a date, and then store the objects in arrays by the date key:
sectionHeaders: ["Monday", "Tuesday"],
dict:{
Monday: [{obj1},{obj2},{obj3}],
Tuesday: [{obj4}],
}
Then I use this dict in React Native to populate my ListView because ListView will except this object format to render Items with Sections using the cloneWithRowsAndSections method.
This is a performance optimization because my sorting is not trivial. I have to make deep comparisons and this way I only do it once when I first populate the store, and not every time I render the scene.
I've also played around with using a dictionary by ID and storing only the IDs in the sorted dict instead of the actual objects.
There are trade offs though for this, in that updating is more complex and you have to decide when to remove section headers if an item is removed from a section.

Resources