What I'm trying to do:
A decouple service that subscribes to multiple NGRX Store selectors
This decouple service defines BehaviourSubjects corrsponding to each Store selector
Store selector subscription emit their values to the corresponding BehaviourSubjects defined on this service
Note: This service is supposed to decouple the store from other component, doing stuff with store emitted value on the way to the components, so exposing the store subscriptions directly to components is not an option.
At first I managed with the following pattern of subscription:
const assignmentImportanceTypes$ = this.store.pipe(select(getAssignmentImportanceTypes), skipWhile(types => !types));
const assignmentImportanceTypesSub = assignmentImportanceTypes$.subscribe((assignmentImportanceTypes) => {
this.assignmentImportances_value = assignmentImportanceTypes;
this.assignmentImportances.next(assignmentImportanceTypes);
});
const assignmentTypes$ = this.store.pipe(select(getAssignmentTypes), skipWhile(types => !types));
const assignmentTypesSub = assignmentTypes$.subscribe((assignmentTypes) => {
this.assignmentTypes_value = assignmentTypes;
this.assignmentTypes.next(assignmentTypes);
});
const assignmentStatusesTypes$ = this.store.pipe(select(getAssignmentStatusesTypes), skipWhile(types => !types));
const assignmentStatusesTypesSub = assignmentStatusesTypes$.subscribe((assignmentStatusesTypes) => {
this.assignemntStatus_value = assignmentStatusesTypes;
this.assignemntStatus.next(assignmentStatusesTypes);
});
I then didnt like the redundancy of this, so I refactored the previous to the following, creating a model to loop over and a loop that subscribes to each selector.
The model looks as follows:
assignmentStoreSelectors = [
{
selector: getAssignmentImportanceTypes, // Store selector
subject: this.assignmentImportances, // BehaviourSubject on service
lastValue:null
},
{
selector: getAssignmentStatusesTypes, // Store selector
subject: this.assignemntStatus, //BehaviourSubject on service
lastValue:null
},
{
selector: getAssignmentTypes, // Store selector
subject: this.assignmentTypes, //BehaviourSubject on service
lastValue:null
}
]
I then loop over this model and subscribe to each selector, emitting subscription value insdie the subscription to the BehaviorSubject defined on this service:
this.assignmentStoreSelectors.forEach((storeSelectorObj: any) => {
this.store.pipe(select(storeSelectorObj.selector), skipWhile(enumValue => !enumValue))
.subscribe((enumValue) => {
storeSelectorObj.lastValue = enumValue;
storeSelectorObj.subject.next(enumValue);
});
});
This shortened the redundancy of the initial pattern & made it all more generic, but it brought in a model object and I feel like there should be an RXJS way of doing this that I'm not aware of.
Whats a better way to do this?
Or what is the RXJS way of doing this?
Related
I am trying to understand functionality with Redux toolkit and RTK Query.
I have a functioning query in one view. My question is how do I pull this information in another view?
this is how I launch the query in view #1
const Sidebar = () => {
const { data: clients = [], isFetching: clientsFetching } =
useGetClientsQuery("x#x.com");
this is my single api slice object
//define our single api slice object
export const tradesApi = createApi({
//the cache reducer expects to be added at 'state.api' (already default - this is optional)
reducerPath: "tradesApi",
//all of our requests will have urls starting with '/fakeApi'
baseQuery: fetchBaseQuery({ baseUrl: "https://localhost:44304/api/" }),
//the 'endpoints' represent operations and requests for this server
endpoints: (builder) => ({
//the 'getposts' endpoint is a "query" operation that returns data
getClients: builder.query<ClientData[], string>({
query: (username) => `/dashboard/returnClients?username=${username}`,
}),
getTradeDetail: builder.query<TradeData, number>({
query: (id) => `/dashboard/returnTest?id=${id}`,
}),
}),
});
export const { useGetTradesQuery, useGetClientsQuery, useGetTradeDetailQuery } =
tradesApi;
when I am in view #2, how do I pull this information without querying the api again? I simply want to grab it from the store.
thank you!
You just call useGetClientsQuery("x#x.com"); in the other component. It won't make another request, but just pull the data from the store.
There is an array in public users = new BehaviorSubject<User[]>([]).
I want to delete element from this observable and refresh it.
My solution is:
const idRemove = 2;
this.users.next(this.user.getValue().filter((u) => u.id !== idRemove)
But I seem I use wrong way of using RXJS
Toward Idiomatic RxJS
Using subscribe instead of .value.
interface User {
id: number
}
const users$ = new BehaviorSubject<User[]>([
{id:1},
{id:2},
{id:3}
]);
function removeId(idRemove: number) {
users$.pipe(
take(1),
map(us => us.filter(u => u.id !== idRemove))
).subscribe(
users$.next.bind(users$)
);
}
users$.subscribe(us =>
console.log("Current Users: ", us)
);
removeId(2);
removeId(1);
removeId(3);
Output:
Current Users: [ { id: 1 }, { id: 2 }, { id: 3 } ]
Current Users: [ { id: 1 }, { id: 3 } ]
Current Users: [ { id: 3 } ]
Current Users: []
To handle state within RxJS pipes you can use the Scan operator
Useful for encapsulating and managing state. Applies an accumulator (or "reducer function") to each value from the source after an initial state is established -- either via a seed value (second argument), or from the first value from the source.
const { Subject, merge } = rxjs;
const { scan, map } = rxjs.operators;
// This function is used to apply new users to the state of the scan
const usersFn = users => state => users
// This function is used to remove all matching users with the given id from the state of the scan
const removeFn = removeId => state => state.filter(user => user.id !== removeId)
// This Subject represents your old user BehaviorSubject
const users$$ = new Subject()
// This Subject represents the place where this.users.next(this.user.getValue().filter((u) => u.id !== idRemove) was called
const remove$$ = new Subject()
// This is your new user$ Observable that handles a state within its pipe. Use this Observable in all places where you need your user Array instead of the user BehaviorSubject
const user$ = merge(
// When users$$ emits the usersFn is called with the users argument (1. time)
users$$.pipe(map(usersFn)),
// When remove$$ emits the removeFn is called with the removeId argument (1. time)
remove$$.pipe(map(removeFn))
).pipe(
// Either the usersFn or removeFn is called the second time with the state argument (2. time)
scan((state, fn) => fn(state), [])
)
// Debug subscription
user$.subscribe(console.log)
// Test emits
users$$.next([
{id: 1, name: "first"},
{id: 2, name: "second"}
])
remove$$.next(2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
Ben Lesh (main Contributor of RxJS) wrote an anser about why not to use getValue in RxJS: The only way you should be getting values "out of" an Observable/Subject is with subscribe!
Using getValue() is discouraged in general for reasons explained here even though I'm sure there are exception where it's fine to use it. So a better way is subscribing to get the latest value and then changing it:
this.users
.pipe(take(1)) // take(1) will make sure we're not creating an infinite loop
.subscribe(users => {
this.users.next(users.filter((u) => u.id !== idRemove);
});
I am trying to go a simple step beyond the nest doc example in implementing #Sse() in a controller but I never used rxjs untill now so Im a bit confused.
The flow is :
client send a POST request with a file payload
server (hopefully) sends back the newly created project with a prop status:UPLOADED
client subscribe to sse route described below passing as param the projectId it just received from server
in the meantime server is doingSomeStuff that could take from 10sec to a min. When doingSomeStuff is done, project status is updated in db from UPLOADED to PARSED
My need is for the #Sse decorated function to execute at x interval of time a "status-check" and return project.status (that may or may not have been updated at the time)
My present code :
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable<any> {
const projId$ = from(this.projectService.find(projectId)).pipe(
map((p) => ({
data: {
status: p.status,
},
})),
);
return interval(1000).pipe(switchMap(() => projId$));
}
I don't put code of the service here as it a simple mongooseModel.findById wrapper.
My problem is the status returned remains UPLOADED and is never updated. It doesnt seem the promise is reexecuted at every tick. If I console.log inside my service I can see my log being printed only once with the initial project value while I expect to see a new log at each tick.
This is a two-step process.
We create an observable out of the promise generated by this.service.findById() using the from operator in rxjs. We also use the map operator to set the format of the object we need when someone subscribes to this observable.
We want to return this observable every x seconds. interval(x) creates an observable that emits a value after every x milliseconds. Hence, we use this and then switchMap to the projId$ whenever the interval emits a value. The switchMap operator switches to the inner observable whenever the outer observable emits a value.
Please note: Since your server may take 10 sec, to min for doing the operation, you should set the intervalValue accordingly. In the code snippet below, I've set it to 10,000 milli seconds which is 10 seconds.
const intervalValue = 10000;
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable < any > {
return interval(intervalValue).pipe(
switchMap(() => this.projectService.find(projectId)),
map((p) => ({
data: {
status: p.status,
}
})));
}
// OR
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable < any > {
const projId$ = defer(() => this.service.findById(projectId)).pipe(
map(() => ({
data: {
_: projectId
}
}))
);
return interval(intervalValue).pipe(switchMap(() => projId$));
}
#softmarshmallow
You can watch model changes and use observable stream to send it.
Something like this
import { Controller, Param, Sse } from '#nestjs/common'
import { filter, map, Observable, Subject } from 'rxjs'
#Controller('project')
export class ProjectStatusController {
private project$ = new Subject()
// watch model event
// this method should be called when project is changed
onProjectChange(project) {
this.project$.next(project)
}
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable<any> {
return this.project$.pipe(
filter((project) => project.projectId === projectId),
map((project) => ({
data: {
status: project.status,
},
})),
)
}
}
Rather than create a massive E2E test, I want to verify what the useMutation is receiving from the component. There is a lot of business logic before my component "posts" and I want to test that the GraphQL mutation function is receiving the shape of data.
Manually mocking the query doesn't provide value in this case, as it skips the business logic I want to keep track of. There is no value right now to let the query return full data & response, as my component will unmount and URL will change after successful data is returned.
So ideally, we just stop the test when the useMutation hook is called.
I am hoping to do something like:
const mutationSpy = jest.spyOn(graphQL, 'useMutation');
...
expect(mutationSpy).toHaveBeenCalledWith(myDataShape);
The best way to track whether a mutation has been called is to use the MockedProvider from #apollo/react-testing (react-native) or #apollo/client (react) to supply a callback function to your mock response that changes a variable in the test scope so that it may be checked once the test has been run. Something like this.
Here is the documentation:
https://www.apollographql.com/docs/react/development-testing/testing/#testing-mutation-components
test.js
let createMutationCalled = false
const mocks = [
{
request,
result: () => {
createMutationCalled = true
return { data }
}
}
];
describe('test', () => {
test('should call createTicket mutation', async () => {
const { getByTestId } = render(
<MockedProvider mocks={mocks}>
<SelfReportPage />
</MockedProvider>
)
let input = getByTestId('sr-description')
let submit = getByTestId('sr-submit')
fireEvent.changeText(input, 'Test text.')
fireEvent.press(submit)
await new Promise(r => setTimeout(r, 0));
expect(createMutationCalled).toBe(true)
})
})
I've tested in various ways... Still, It isn't working.
I don't seem to doing anything wrong
exactly same code as reselect doc
redux store is all normalized
reducers are all immutable
From parent component, I just pass down a prop with id and from child component, connected with redux and used selector to get that exact item by id(from parent component)
### This is what Parent components render looks like
render() {
return (
<div>
<h4>Parent component</h4>
{this.props.sessionWindow.tabs.map(tabId =>
<ChildComponentHere key={tabId} tabId={tabId} />
)}
</div>
);
}
### This is what Child component looks like
render() {
const { sessionTab } = this.props (this props is from connect() )
<div>
<Tab key={sessionTab.id} tab={sessionTab} />
</div>
))
}
### Selectors for across multiple components
const getTheTab = (state: any, ownProps: IOwnProps) => state.sessionWindows.sessionTab[ownProps.tabId];
const makeTheTabSelector = () =>
createSelector(
[getTheTab],
(tab: object) => tab
)
export const makeMapState = () => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props)
}
}
return mapStateToProps
}
Weirdly Working solution: just change to deep equality check.(from anywhere)
use selectors with deep equality works as expected.
at shouldComponentUpdate. use _.isEqual also worked.
.
1. const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
)
2. if (!_isEqual(this.props, nextProps) || !_isEqual(this.state, nextState)){return true}
From my understanding, my redux is always immutable so when something changed It makes new reference(object or array) that's why react re-renders. But when there is 100 items and only 1 item changed, only component with that changed props get to re-render.
To make this happen, I pass down only id(just string. shallow equality(===) works right?)using this id, get exact item.(most of the components get same valued input but few component get different valued input) Use reselect to memoize the value. when something updated and each component get new referenced input compare with memoized value and re-render when something trully changed.
This is mostly what I can think of right now... If I have to use _isEqual anyway, why would use reselect?? I'm pretty sure I'm missing something here. can anyone help?
For more clarification.(hopefully..)
First,My redux data structure is like this
sessionWindow: {
byId: { // window datas byId
"windowId_111": {
id: "windowId_111",
incognito: false,
tabs: [1,7,3,8,45,468,35,124] // this is for the order of sessionTab datas that this window Item has
},
"windowId_222": {
id: "windowId_222",
incognito: true,
tabs: [2, 8, 333, 111]
},{
... keep same data structure as above
}
},
allIds: ["windowId_222", "windowId_111"] // this is for the order of sessionWindow datas
}
sessionTab: { // I put all tab datas here. each sessionTab doesn't know which sessionWindow they are belong to
"1": {
id: 1
title: "google",
url: "www.google.com",
active: false,
...more properties
},
"7": {
id: 7
title: "github",
url: "www.github.com",
active: true
},{
...keep same data structure as above
}
}
Problems.
1. when a small portion of data changed, It re-renders all other components.
Let's say sessionTab with id 7's url and title changed. At my sessionTab Reducer with 'SessionTabUpdated" action dispatched. This is the reducer logic
const updateSessionTab = (state, action) => {
return {
...state,
[action.tabId]: {
...state[action.tabId],
title: action.payload.title,
url: action.payload.url
}
}
}
Nothing is broken. just using basic reselect doesn't prevent from other components to be re-rendered. I have to use deep equality version to stop re-render the component with no data changed
After few days I've struggled, I started to think that the problem is maybe from my redux data structure? because even if I change one item from sessionTab, It will always make new reference like {...state, [changedTab'id]: {....}} In the end, I don't know...
Three aspects of your selector definition and usage look a little odd:
getTheTab is digging down through multiple levels at once
makeTheTabSelector has an "output selector" that just returns the value it was given, which means it's the same as getTheTab
In mapState, you're passing the entire props object to theTabSelector(state, props).
I'd suggest trying this, and see what happens:
const selectSessionWindows = state => state.sessionWindows;
const selectSessionTabs = createSelector(
[selectSessionWindows],
sessionWindows => sessionWindows.sessionTab
);
const makeTheTabSelector = () => {
const selectTabById = createSelector(
[selectSessionTabs, (state, tabId) => tabId],
(sessionTabs, tabId) => sessionTabs[tabId]
);
return selectTabById;
}
export const makeMapState() => {
const theTabSelector = makeTheTabSelector();
const mapStateToProps = (state: any, props: IOwnProps) => {
return {
sessionTab: theTabSelector(state, props.tabId)
}
}
return mapStateToProps
}
No guarantees that will fix things, but it's worth a shot.
You might also want to try using some devtool utilities that will tell you why a component is re-rendering. I have links to several such tools in the Devtools#Component Update Monitoring section of my Redux addons catalog.
Hopefully that will let you figure things out. Either way, leave a comment and let me know.