In this code why subject.onNext(3) is printing first as I set subject.sample(500) but setTimeout(200)?
const Rx = require('rx');
const subject = new Rx.Subject();
const sampleObservable = subject.sample(500);
sampleObservable.subscribe(
data => console.log(data),
error => console.log(error),
() => console.log('FINISHED')
);
subject.onNext(0);
subject.onNext(1);
setTimeout(() => {
subject.onNext(2);
subject.onNext(3);
subject.onCompleted();
}, 200);
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;
}
so I have an array that will change over time in size and would like to convert to a hot stream.
tried with no luck:
const array = [10, 20, 30];
const result = from(array, asyncScheduler);
result.subscribe(x => {
console.log(x);
});
setTimeout(() => {
array.push('waiting for me');
}, 6000);
as 'waiting for me' never gets consoled after 6 sec.
tried also share() with no avail
thanks
You can do this with a subject !
const subject$ = new Subject();
subject$.subscribe((x) => {
console.log(x);
});
[10, 20, 30].forEach((v) => subject$.next(v));
setTimeout(() => {
subject$.next('waiting for me');
}, 6000);
NB: You need to subscribe before pushing, because the values will be sent synchronously in the particular case.
or you can also merge 2 streams :
const subject$ = new Subject();
const array = [10, 20, 30];
const result = from(array);
setTimeout(() => {
subject$.next('waiting for me');
}, 6000);
merge(result, subject$).subscribe((x) => {
console.log(x);
});
I have the following selector and effect
const filterValues = useSelector<State, string[]>(
state => state.filters.filter(f => f.field === variableId).map(f => f.value),
(left, right) => {
return left.length === right.length && left.every(l => right.includes(l));
},
);
const [value, setValue] = useState<SelectionRange>({ start: null, end: null });
useEffect(() => {
const values = filterValues
.filter(av => av).sort((v1, v2) => v1.localeCompare(v2));
const newValue = {
start: values[0] ?? null,
end: values[1] ?? null,
};
setValue(newValue);
}, [filterValues]);
the selector above initially returns an empty array, but a different one every time and I don't understand why because the equality function should guarantee it doesn't.
That makes the effect trigger, sets the state, the selector runs again (normal) but returns another different empty array! causing the code to run in an endless cycle.
Why is the selector returning a different array each time? what am I missing?
I am using react-redux 7.2.2
react-redux e-runs the selector if the selector is a new reference, because it assumes the code could have changed what it's selecting entirely
https://github.com/reduxjs/react-redux/issues/1654
one solution is to memoize the selector function
const selector = useMemo(() => (state: State) => state.filters.filter(f => f.field === variableId).map(f => f.value), [variableId]);
const filterValues = useSelector<State, string[]>(
selector ,
(left, right) => {
return left.length === right.length && left.every(l => right.includes(l));
},
);
You can try memoizing the result of your filter in a selector and calculate value in a selector as well, now I'm not sure if you still need the local state of value as it's just a copy of a derived value from redux state and only causes an extra render when you copy it but here is the code:
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { createSelector, defaultMemoize } = Reselect;
const { useState, useEffect, useMemo } = React;
const initialState = {
filters: [
{ field: 1, value: 1 },
{ field: 2, value: 2 },
{ field: 1, value: 3 },
{ field: 2, value: 4 },
],
};
//action types
const TOGGLE = 'NEW_STATE';
const NONE = 'NONE';
//action creators
const toggle = () => ({
type: TOGGLE,
});
const none = () => ({ type: NONE });
const reducer = (state, { type }) => {
if (type === TOGGLE) {
return {
filters: state.filters.map((f) =>
f.field === 1
? { ...f, field: 2 }
: { ...f, field: 1 }
),
};
}
if (type === NONE) {
//create filters again should re run selector
// but not re render
return {
filters: [...state.filters],
};
}
return state;
};
//selectors
const selectFilters = (state) => state.filters;
const createSelectByVariableId = (variableId) => {
const memoArray = defaultMemoize((...args) => args);
return createSelector([selectFilters], (filters) =>
memoArray.apply(
null,
filters
.filter((f) => f.field === variableId)
.map((f) => f.value)
)
);
};
const createSelectSelectValue = (variableId) =>
createSelector(
[createSelectByVariableId(variableId)],
//?? does not work in SO because babel is too old
(values) => ({
start: values[0] || null,
end: values[1] || null,
})
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
var last;
const App = ({ variableId }) => {
const selectValue = useMemo(
() => createSelectSelectValue(variableId),
[variableId]
);
const reduxValue = useSelector(selectValue);
if (last !== reduxValue) {
console.log('not same', last, reduxValue);
last = reduxValue;
}
//not sure if you still need this, you are just
// copying a value you already have
const [value, setValue] = useState(reduxValue);
const dispatch = useDispatch();
useEffect(() => setValue(reduxValue), [reduxValue]);
console.log('rendering...', value);
return (
<div>
<button onClick={() => dispatch(toggle())}>
toggle
</button>
<button onClick={() => dispatch(none())}>none</button>
<pre>{JSON.stringify(value, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(
<Provider store={store}>
<App variableId={1} />
</Provider>,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<div id="root"></div>
I'm trying to get my websocket code to automatically attempt a reconnect (indefinitely) until successful. By sending a "ping" message every x seconds, I can detect when a pipe is broken, and the closeObserver is called.
However, I'm not sure how to get a reconnect sequence to initiate.
const notificationConnectionEpic: Epic<ActionTypes, any, RootState> = (
action$,
state$
) =>
action$.pipe(
filter(isActionOf(actions.connectNotificationPipeline.request)),
switchMap(async action => {
const resp = await requireValidToken(action$, state$, params =>
AdminHubs.getHubNotificationsToken({
...params,
id: action.payload.hubId
})
);
return resp.pipe(
switchMap(v => {
if (isAction(v)) {
return of(v);
}
if (!v.ok) {
return of(
actions.connectNotificationPipeline.failure({
hubId: action.payload.hubId,
error: v.error
})
);
}
const webSocketOpen$ = new Subject();
const webSocketClose$ = new Subject();
const webSocket$ = webSocket<AdminHubs.HubNotification>({
url: v.value,
openObserver: webSocketOpen$,
closeObserver: webSocketClose$
});
const message$ = webSocket$.pipe(
map(message => actions.receiveNotification({ message })),
takeUntil(action$.ofType(HubActionConsts.NOTIFICATION_PIPE_CLOSED))
);
const ping$ = interval(1000).pipe(
map(_ => webSocket$.next("ping" as any)),
ignoreElements()
);
const open$ = webSocketOpen$.pipe(
take(1),
map(_ =>
actions.connectNotificationPipeline.success({
hubId: action.payload.hubId
})
)
);
const close$ = webSocketClose$.pipe(
// called when a network drop happens. handle reconnect?
); // also happens on net error
return merge(message$, open$, ping$, close$);
})
);
}),
mergeMap(v => v)
);
When the WebSocket connection closes, just dispatch actions.connectNotificationPipeline.request again. That will re-run this code and create a new WebSocket connection.
I'm trying to block main stream until config is initialized using withLatestFrom operator and secondary stream (which contains info about config state).
Here is my code:
#Effect()
public navigationToActiveRedemptionOffers = this.actions$.ofType(ROUTER_NAVIGATION)
.filter((action: RouterNavigationAction<RouterStateProjection>) => {
return action.payload.event.url.includes('/redemption-offers/list/active');
})
.do(() => console.log('before withLatestFrom'))
.withLatestFrom(
this.store.select(selectConfigInitialized)
.do(initialized => console.log('before config filter', initialized))
.filter(initialized => initialized)
.do(initialized => console.log('after config filter', initialized)),
)
.do(() => console.log('after withLatestFrom'))
.switchMap(data => {
const action = data[0] as RouterNavigationAction<RouterStateProjection>;
return [
new MapListingOptionsAction(action.payload.routerState.queryParams),
new LoadActiveOffersAction(),
];
});
Problem is that second do (after withLatestFrom) block never gets called.
Log:
before config filter false
before withLatestFrom
before config filter true
after config filter true
Thank you for your suggestions.
I think what you want is .combineLatest.
withLatestFrom only treats what comes before it as the trigger.
combineLatest treats both the source and the observables provided to it as the trigger.
So your issue is that the source (route change) fires and the observable passed to withLatest (state initialized) has not emited yet because of its filter. It only emits a value after the source fires. So it is ignored.
Here is a running example of what you are doing:
const first = Rx.Observable.create((o) => {
setTimeout(() => {
o.next();
}, 2000);
});
const second = Rx.Observable.create((o) => {
setTimeout(() => {
o.next(false);
}, 1000);
setTimeout(() => {
o.next(true);
}, 3000);
});
first
.do(() => console.log('before withLatestFrom'))
.withLatestFrom(
second
.do(initialized => console.log('before config filter', initialized))
.filter(initialized => initialized)
.do(initialized => console.log('after config filter', initialized)),
)
.subscribe(() => console.log('after withLatestFrom'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
Here is with combineLatest:
const first = Rx.Observable.create((o) => {
setTimeout(() => {
o.next();
}, 2000);
});
const second = Rx.Observable.create((o) => {
setTimeout(() => {
o.next(false);
}, 1000);
setTimeout(() => {
o.next(true);
}, 3000);
});
first
.do(() => console.log('before withLatestFrom'))
.combineLatest(
second
.do(initialized => console.log('before config filter', initialized))
.filter(initialized => initialized)
.do(initialized => console.log('after config filter', initialized)),
)
.subscribe(() => console.log('after withLatestFrom'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.2/Rx.js"></script>
The withLatestFrom emits only when its source Observable emits. What happens is that this.store.select emits before the source to withLatestFrom. You can see it from the order of emissions:
before config filter false
before withLatestFrom
...
Now it depends on what you want to achieve. You can trigger this.store.select(selectConfigInitialized) only when the source emits by using concatMap:
navigationToActiveRedemptionOffers = this.actions$.ofType(ROUTER_NAVIGATION)
.filter(...)
.concatMap(() => this.store.select(selectConfigInitialized)...)
.switchMap(data => { ... })
...
Or you can pass along results from both Observables:
navigationToActiveRedemptionOffers = this.actions$.ofType(ROUTER_NAVIGATION)
.filter(...)
.concatMap(nav => this.store.select(selectConfigInitialized)
.map(result => [nav, result])
)
.switchMap(data => { ... })
...
You can make it simpler by inverting the observables.
public navigationToActiveRedemptionOffers =
return this.store.select(selectConfigInitialized)
.filter(initialized => initialized)
.flatMap(x =>
return this.actions$.ofType(ROUTER_NAVIGATION)
.filter((action: RouterNavigationAction<RouterStateProjection>) => {
return action.payload.event.url.includes('/redemption-offers/list/active');
})
.map(action =>
return [
new MapListingOptionsAction(action.payload.routerState.queryParams),
new LoadActiveOffersAction(),
];
)
As this is a common pattern, I use a generic waitFor method.
export const waitFor$ = function() {
return this.filter(data => !!data ).take(1);
};
Usage,
return this.config$
.waitFor$()
.flatMap(config => {
return observableWithRequiredValue()
}
Fluent syntax provided by
Observable.prototype.waitFor$ = waitFor$;
declare module 'rxjs/Observable' {
interface Observable<T> {
waitFor$: typeof waitFor$;
}
}
Note, this method of operator creation has been superseded by Lettable Operators in rxjs v5.5 (but the above still works).