Handling JSON Objects in RxJS - rxjs

I am new to cyclejs and rxjs in general and was hoping someone could help me solve my problem.
I am trying to build a demo application for my understanding and stuck with rendering JSON objects on the DOM.
My demo application calls the NASA near earth objects API for the past 7 days and tries to display them.
There is a Load More button at the bottom which on clicking will load data of the previous 7 days (Today - 7 upto Today - 14).
The response I get from the API is as follows
{
"links" : {
"next" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-09-06&end_date=2016-09-12&detailed=false&api_key=DEMO_KEY",
"prev" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-08-25&end_date=2016-08-31&detailed=false&api_key=DEMO_KEY",
"self" : "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-08-31&end_date=2016-09-06&detailed=false&api_key=DEMO_KEY"
},
"element_count" : 39,
"near_earth_objects" : {
"2016-09-06" : [{
some data
},
{
some data
}],
2016-08-31: [{...}],
...
}
}
I am interested in near_earth_objects JSON object but I am unable to map it beacause of it being an Object.
How do I handle such a situations? Below is the code that I have
function main(sources) {
const api_key = "DEMO_KEY";
const clickEvent$ = sources.DOM.select('.load-more').events('click');
const request$ = clickEvent$.map(() => {
return {
url: "https://api.nasa.gov/neo/rest/v1/feed?start_date=2015-09-06&end_date=2016-09-13&api_key=" + api_key,
method: "GET"
}
}).startWith({
url: "https://api.nasa.gov/neo/rest/v1/feed?start_date=2016-08-31&end_date=2016-09-06&api_key=" + api_key,
method: "GET"
});
const response$$ = sources.HTTP.filter(x$ => x$.url.indexOf("https://api.nasa.gov/neo/rest/v1/feed") != -1).select(response$$);
const response$ = response$$.switch(); //flatten the stream
const nasa$ = response$.map(response => {
return response.body
});
const sinks = {
DOM: nasa$.map(nasa =>
([nasa.near_earth_objects]).map(objects => {
var vdom = [];
//I am not very happy with this part. Can this be improved?
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
vdom.push(objects[key].map(obj => div([
h1(obj.name)
])))
}
}
//returning the vdom does not render on the browser. vdom is an array of arrays. How should i correct this?
console.log(vdom);
return vdom;
})
),
HTTP: request$
};
return sinks;
};

Conceptually, you want to extract the entries of nasa.near_earth_objects (i.e., turn the Object into an Array), then flat map that Array into an Observable sequence.
I'll assume you're already using lodash in your project (you can do it without lodash, but you'll just need to write more glue code manually). I'll also assume you're importing RxJS' Observable as Rx.Observable; adjust the names below to suite your code.
You can accomplish the first task using _.toPairs(nasa.near_earth_objects), and the second part by calling .flatMap(), and returning Rx.Observable.from(near_objects). The resulting Observable will emit items for each key in nasa.near_earth_objects. Each item will be an array, with item[0] being the item's key (e.g., 2016-09-06) and item[1] being the item's value.
Using that idea, you can replace your DOM sink with something like:
nasa$.map(nasa => _.toPairs(nasa.near_earth_objects))
.flatMap(near_objects => Rx.Observable.from(near_objects))
.map(near_object => div([
h1(near_object[1].name)
]))
),

Related

How to remove element from BehaviorSubject array?

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

Basics of react-table: where do I put useTable when pulling data from an API?

I am trying to set up react-table but am running into the issue of not being able to call hooks inside regular functions, specifically the useTable hook. Say I have something like this:
const Search = () => {
const [tableRows, changeTableRows] = useState([])
const columns = [
{ Header: 'One', accessor: 'header1'},
{ Header: 'Two', accessor: 'header2'}
]
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns, tableRows })
const apiSearch = (field, query) => {
axios.get(`ip/querypath?query=${query}&field=${field}`)
.then(({data}) => {
changeTableRows(data)
})
.catch(err => console.log(err))
return (
/* Render forms to input data and call apiSearch, and render actual table */
)
}
The line with useTable is only being called once, with no initial row data. I need it to update when I make the API call but I can't put a hook inside my apiSearch function. Even if I could, I don't know the right way to do it. This throws a syntax error:
let getTableProps, getTableBodyProps, headerGroups, rows, prepareRow
const apiSearch = (field, query) => {
...
{ getTableProps, getTablyBodyProps, headerGroups, rows, prepareRow } = useTable({ columns, tableRows })
}
I know I'm missing something basic and would appreciate any help.

reselect across multiple comoponents work only with deepEqual check

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.

Recursive observable

I'm working with RxJs and I have to make a polling mechanism to retrieve updates from a server.
I need to make a request every second, parse the updates, emit it and remember its id, because I need it to request the next pack of updates like getUpdate(lastId + 1).
The first part is easy so I just use interval with mergeMap
let lastId = 0
const updates = Rx.Observable.interval(1000)
.map(() => lastId)
.mergeMap((offset) => getUpdates(offset + 1))
I'm collecting identifiers like this:
updates.pluck('update_id').scan(Math.max, 0).subscribe(val => lastId = val)
But this solution isn't pure reactive and I'm looking for the way to omit the usage of "global" variable.
How can I improve the code while still being able to return observable containing just updates to the caller?
UPD.
The server response for getUpdates(id) looks like this:
[
{ update_id: 1, payload: { ... } },
{ update_id: 3, payload: { ... } },
{ update_id: 2, payload: { ... } }
]
It may contain 0 to Infinity updates in any order
Something like this? Note that this is an infinite stream since there is no condition to abort; you didn't give one.
// Just returns the ID as the update_id.
const fakeResponse = id => {
return [{ update_id: id }];
};
// Fakes the actual HTTP call with a network delay.
const getUpdates = id => Rx.Observable.of(null).delay(250).map(() => fakeResponse(id));
// Start with update_id = 0, then recursively call with the last
// returned ID incremented by 1.
// The actual emissions on this stream will be the full server responses.
const updates$ = getUpdates(0)
.expand(response => Rx.Observable.of(null)
.delay(1000)
.switchMap(() => {
const highestId = Math.max(...response.map(update => update.update_id));
return getUpdates(highestId + 1);
})
)
updates$.take(5).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
To define the termination of the stream, you probably want to hook into the switchMap at the end; use whatever property of response to conditionally return Observable.empty() instead of calling getUpdates again.

Is there a better way to form this code example?

I'm new to rxjs and using redux-observable. The short of it is that I need to make a couple promise requests when i get a connection then output the results. I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read. Thanks ahead of time for your comments.
export const handleBleConnectionSuccess = (action$,store,{bleCommunicator}) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS)
.do((a)=>{
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.firmwareRevision.uuid))
.do((value)=>store.dispatch({type:c.DEVICE_FIRMWARE_VERSION,device:{...a.device,firmwareVersion:value}}))
.retry(3);
Observable.fromPromise(bleCommunicator.readCharacteristic(a.device.id,gattInfo.uuid,gattInfo.modelNumber.uuid))
.do(value=>store.dispatch({type:c.DEVICE_MODEL_NUMBER,device:{...a.device,modelNumber:value}}))
.retry(3);
})
.mapTo({type:'DEVICE_INFORMATION_REQUESTED'});
I'm wondering if there is a way to join this into a single map at the end and not have to call store.dispatch multiple times and have the retry work for each individual read
Yes, there is a better way, and it's possible to do what you want.
From the syntax, I'm guessing that you use ngrx (effects) (and not redux-observable).
So with ngrx/effects you could do it like that:
export const handleBleConnectionSuccess = (
action$,
store,
{ bleCommunicator }
) =>
action$.ofType(c.BLE_CONNECTION_SUCCESS).switchMap(a => {
const readCharacteristic = deviceOrFirmwareUuid =>
bleCommunicator.readCharacteristic(a.device.id, gattInfo.uuid, deviceOrFirmwareUuid);
return Observable.merge(
readCharacteristic(gattInfo.firmwareRevision.uuid)
.map(value => ({
type: c.DEVICE_FIRMWARE_VERSION,
device: { ...a.device, firmwareVersion: value },
}))
.retry(3),
readCharacteristic(gattInfo.modelNumber.uuid)
.map(value => ({
type: c.DEVICE_MODEL_NUMBER,
device: { ...a.device, modelNumber: value },
}))
.retry(3),
{ type: 'DEVICE_INFORMATION_REQUESTED' }
);
});

Resources