rxjs: reset to streams and only get one out put - javascript

I have two separate streams that come together in a combineLatest in something like the following:
const programState$ = Rx.Observable.combineLatest(
high$, low$,
(high, low) => {
return program(high, low);
});
This works fine and dandy but I also want to be able to reset both the high$ and the low$ to their initial state and only fire the program once. Those kind of look like the following:
const high$ = initialDataBranchOne$.merge(interactiveHigh$);
const low$ = initialDataBranchTwo$.merge(interactiveLow$);
Both of those come from an initialData stream that is fired fromEvent. While the program runs normally the combineLatest works great. How can I achieve the same result when the initialData fromEvent is fired? Right now the program gets run twice.

We can store the high and low properties in the same object. We can then perform a scan as various events come in to update this state:
// Define your default high/low values
const defaultHighLow = /** **/;
// Different types of updates/actions
const highUpdate$ = high$.map(high => ({ high, type: 'UPDATE HIGH' }));
const lowUpdate$ = low$.map(low => ({ low, type: 'UPDATE LOW' }));
const resetUpdate$ = reset$.map(high => ({ type: 'RESET' }));
// Merge all the different types of actions to single stream
const update$ = Rx.Observable.merge(highUpdate$, lowUpdate$, resetUpdate$);
// Scan over these updates to modify the high/low values
const highLowState$ = update$.scan((state, update) => {
if (update.type === 'UPDATE HIGH') {
return { ...state, high: update.high };
}
if (update.type === 'UPDATE LOW') {
return { ...state, low: update.low };
}
// Return defaultHighLow if reset update is triggered
if (update.type === 'RESET') {
return defaultHighLow;
}
// Return state by default
return state;
}, defaultHighLow).startWith(defaultHighLow);
And finally we can derive the program state as before:
const programState$ = highLowState$.map(hl => program(hl.high, hl.low));

Related

Nothing shows up after setState filling by components

Client: React, mobx
Server: NodeJS, MongoDB
Short question:
I have an array of elements which fills inside of useEffect function, expected result: each element of array should be rendered, actual result: nothing happens. Render appears only after code changing in VSCode.
Tried: changing .map to .forEach, different variations of spread operator in setState(...[arr]) or even without spread operator, nothing changes.
Info:
Friends.jsx part, contains array state and everything that connected with it, also the fill-up function.
const [requestsFrom, setRequestsFrom] = useState([]) //contains id's (strings) of users that will be found in MongoDB
const [displayRequestsFrom, setDisplayRequestsFrom] = useState([]) //should be filled by elements according to requestsFrom, see below
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom]
requestsFrom.map(async(f) => {
if (requestsFrom.length === 0) {
console.log(`empty`) //this part of code never executes
return
} else {
const _candidate = await userPage.fetchUserDataLite(f)
_arr.push( //template to render UserModels (below)
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
console.log(_arr)
}
})
setDisplayRequestsFrom(_arr)
// console.log(`displayRequestsFrom:`)
console.log(displayRequestsFrom) //at first 0, turns into 3 in the second moment (whole component renders twice, yes)
}
Render template function:
const render = {
requests: () => {
return (
displayRequestsFrom.map((friendCandidate) => {
return (
<FriendModel link={friendCandidate.link} username={friendCandidate.username} userId={friendCandidate.userId}/>
)
})
)
}
}
useEffect:
useEffect(() => {
console.log(`requestsFrom.length === ${requestsFrom.length}`)
if (!requestsFrom.length === 0) {
return
} else if (requestsFrom.length === 0) {
setRequestsFrom(toJS(friend.requests.from))
if (toJS(friend.requests.from).length === 0) {
const _arr = [...requestsFrom]
_arr.push('0')
setRequestsFrom(_arr)
}
}
if (displayRequestsFrom.length < 1 && requestsFrom.length > 0) {
getUsersToDisplayInFriendRequestsFrom()
//displayRequestsFrom and requestsFrom lengths should be same
}
},
[requestsFrom]
)
Part of jsx with rendering:
<div className={styles.Friends}>
<div className={styles['friends-container']}>
{render.requests()}
</div>
</div>
UPD: my console.log outputs in the right order from beginning:
requestsFrom.length === 0
requestsFrom.length === 3
displayRequestsFrom === 0
displayRequestsFrom === 3
As we can see, nor requestsFrom, neither displayRequestsFrom are empty at the end of the component mounting and rendering, the only problem left I can't find out - why even with 3 templates in displayRequestsFrom component doesn't render them, but render if I press forceUpdate button (created it for debug purposes, here it is:)
const [ignored, forceUpdate] = React.useReducer(x => x + 1, 0);
<button onClick={forceUpdate}>force update</button>
PRICIPAL ANSWER
The problem here is that you are executing fetch inside .map method.
This way, you are not waiting for the fetch to finish (see comments)
Wrong Example (with clarification comments)
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom];
// we are not awating requestsFrom.map() (and we can't as in this example, cause .map is not async and don't return a Promise)
requestsFrom.map(async (f) => {
const _candidate = await userPage.fetchUserDataLite(f)
// This is called after setting the state in the final line :(
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
} )
setDisplayRequestsFrom(_arr) // This line is called before the first fetch resolves.
// The _arr var is still empty at the time of execution of the setter
}
To solve, you need to await for each fetch before updating the state with the new array.
To do this, your entire function has to be async and you need to await inside a for loop.
For example this code became
const getUsersToDisplayInFriendRequestsFrom = async () => { // Note the async keyword here
const _arr = [...displayRequestsFrom]
for (let f of requestsFrom) {
const _candidate = await fetchUserData(f)
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
}
setDisplayRequestsFrom(_arr)
}
You can also execute every fetch in parallel like this
const getUsersToDisplayInFriendRequestsFrom = async () => { // Note the async keyword here
const _arr = [...displayRequestsFrom]
await Promise.all(requestsFrom.map((f) => {
return fetchUserData(f).then(_candidate => {
_arr.push(
{
isRequest: true,
link: '#',
username: _candidate.login,
userId: _candidate._id
}
)
});
}));
setDisplayRequestsFrom(_arr);
}
Other problems
Never Calling the Service
Seems you are mapping on an empty array where you are trying to call your service.
const getUsersToDisplayInFriendRequestsFrom = () => {
const _arr = [...displayRequestsFrom]
/* HERE */ requestsFrom.map(async(f) => {
if (requestsFrom.length === 0) {
return
If the array (requestsFrom) is empty ( as you initialized in the useState([]) ) the function you pass in the map method is never called.
Not sure what you are exactly trying to do, but this should be one of the problems...
Don't use state for rendered components
Also, you shoudn't use state to store rendered components
_arr.push(
<FriendModel key={_candidate.id} isRequest={true} link='#' username={_candidate.login} userId={_candidate._id}/>
)
, instead you should map the data in the template and then render a component for each element in your data-array.
For example:
function MyComponent() {
const [myData, setMyData] = useState([{name: 'a'}, {name: 'b'}])
return (<>
{
myData.map(obj => <Friend friend={obj} />)
}
</>)
}
Not:
function MyComponent() {
const [myDataDisplay, setMyDataDisplay] = useState([
<Friend friend={{name: 'a'}} />,
<Friend friend={{name: 'b'}} />
])
return <>{myDataDisplay}</>
}
Don't use useEffect to initialize your state
I'm wondering why you are setting the requestsFrom value inside the useEffect.
Why aren't you initializing the state of your requestsFrom inside the useState()?
Something like
const [requestsFrom, setRequestsFrom] = useState(toJS(friend.requests.from))
instead of checking the length inside the useEffect and fill it
So that your useEffect can became something like this
useEffect(() => {
if (displayRequestsFrom.length < 1 && requestsFrom.length > 0) {
getUsersToDisplayInFriendRequestsFrom()
}
},
[requestsFrom]
)

React useEffect does not work as expected

useEffect(() => {
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map(param => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(param => document.getElementById(param[0][0]).value);
let result = runAlgo(...params); //this runs the emscripten stuff
results.push(...(JSON.parse(result)));
setGlobalState('dataOutput', results);
}
let newMethodsToRun = methodsToRun.slice(1);
if (methodsToRun.length>0) {
setGlobalState('methodsToRun', newMethodsToRun);
}
} , [dataOutput, methodsToRun]);
Hello, I am working on a ReactJS app that uses Webassembly with Emscripten.
I need to run a series of algorithms from Emscripten in my Js and I need to show the results as they come, not altogether at the end. Something like this:
Run first algo
Show results on screen form first algo
Run second algo AFTER the results from the first algo are rendedred on the screen (so that user can keep checking them)
Show results on screen form second algo.....
and so on
I already tried a loop that updates a global state on each iteration with timeouts etc, but no luck, so I tried this approach using useEffect, I tried various things but the results keep coming altogether and not one by one. Any clue?
I tried to mock your example HERE:
import React, { useState, useEffect } from 'react';
import './style.css';
export default function App() {
const [methodsToRun, setMethodsToRun] = useState([method1, method2]);
const [globalState, setGlobalState] = useState([]);
useEffect(() => {
if (methodsToRun.length <= 0) return;
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = runAlgo(...params); //this runs the emscripten stuff
results.push(...JSON.parse(result));
setGlobalState((global) => [...global, results]);
}
setMethodsToRun((methods) => methods.slice(1));
}, [methodsToRun]);
console.log('METHODS TO RUN', methodsToRun);
console.log('GLOBAL RESULT', globalState);
return (
<div>
<div> Global state 1: {globalState[0] && globalState[0][0]}</div>
<div> Global state 2: {globalState[1] && globalState[1][0]}</div>
<div id="1">DIV 1 </div>
<div id="4">DIV 2</div>
<div id="7">DIV 3</div>
</div>
);
}
const method1 = [
'1param0',
[
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
],
];
const method2 = [
'2param0',
[
['11', '223', '3'],
['44', '55', '66'],
['77', '88', '99'],
],
];
//mock wasm
window.wasm = {
cwrap:
(...args) =>
() =>
JSON.stringify(args),
};
This example assumes your wasm methods are synchronous, your DOM will update and rerender with a new globalState after each method is executed, the cycle is ensured by the fact you are setting a new methods array sliced by one in the state, that will trigger a rerender and a new execution of the useEffect since it has the methodsToRun array in the deps array. Infinite render loop is prevented by the check if (methodsToRun.length <= 0) return; .
The drawback of this approach though is that if your wasms methods are synchronous and heavy, your UI will be frozen until it returns, and that's a terrible user experience for the end user. I'm not sure what kind of task you are trying to perform there, if it can be made async and awaited ( the logic would be the same ) or if you have to move that on a service worker.
So from what you've explained here, I think the issue can be resolved in 2 ways:
1 is with synchronous requests/process.
Assuming you have 2 functions (AlgoX and AlgoY) you want to run one after the other, you can return the expected response. e.g.
return AlgoX();
return AlgoY();
The return keyword will block the running process from continuing until you have result.
Another alternative I will suggest will be to use Self-Invoking-Expression:
useEffect(() => {
(
async function AlgoHandler(){
const method = methodsToRun[0];
let results = [];
if (method) {
let paramsTypes = method[1].map(param => param[0][2]);
let runAlgo = await window.wasm.cwrap(method[0], 'string', paramsTypes); //this is emscripten stuff
let params = method[1].map(param => document.getElementById(param[0][0]).value);
let result = await runAlgo(...params); //this runs the emscripten stuff
results.push(...(JSON.parse(result)));
setGlobalState('dataOutput', results);
}
let newMethodsToRun = methodsToRun.slice(1);
if (methodsToRun.length>0) {
setGlobalState('methodsToRun', newMethodsToRun);
}
}
)();
} , [dataOutput, methodsToRun]);
Notice where I used async and await in the second solution. Let me know if its helpful.
I'm not sure if this answers your question, but I wrote some example code that may help you fulfill your needs. It will need to be tweaked to fit your use case though, as this should be considered pseudo code:
const initialState = {
count: 0,
results: [],
};
function reducer(state, action) {
switch (action.type) {
case 'addResult':
return {
...state,
count: state.count + 1,
results: [...state.results, action.result],
};
default:
return state;
}
}
function Example({ methodsToRun }) {
const [{ count, results }, dispatch] = useReducer(reducer, initialState);
const next = useCallback(async() => {
if(methodsToRun.length > count) {
const result = await methodsToRun[count]();
dispatch({ type: 'addResult', result });
}
}, [count, methodsToRun]);
useEffect(() => {
next();
}, [next]);
return (
<div>
<p>Results:</p>
<ol>
{results.map(result => (
<li key={result/* choose a unique key from result here, do not use index */}>{result}</li>
))}
</ol>
</div>
);
}
So basically, my idea is to create a callback function, which at first run will be the method at index 0 in your array of methods. When the result arrives, it will update state with the result, as well as update the counter.
The updated count will cause useCallback to change to the next method in the array.
When the callback changes, it will trigger the useEffect, and actually call the function. And so on, until there are no more methods to run.
I have to admit I haven't tried to run this code, as I lack the context. But I think it should work.
At the end of the day, this solution looks like is working for me:
useEffect(() => {
(
async function AlgoHandler(){
if (methodsToRun.length <= 0) {
setGlobalState('loadingResults', false);
return;
}
const method = methodsToRun[0];
let paramsTypes = method[1].map((param) => param[0][2]);
let runAlgo = window.wasm.cwrap(method[0], 'string', paramsTypes);
let params = method[1].map(
(param) => document.getElementById(param[0][0]).value
);
let result = await runAlgo(...params);
setGlobalState('dataOutput', (prev) => [...prev, ...JSON.parse(result)]);
await sleep(100);
setGlobalState('methodsToRun', (prev) => prev.filter(m => m != method));
})();}, [methodsToRun]);

Javascript Conditional return object

I used aws-sns to create one webhook. Two lambda functions are checked by this webhook. One of the lambda functions publishes 'orderId' and'startTime', while another publishes 'orderId' and 'roundName'. Both lambdas fire at different times. As a result, publishing can happen at two different times. One or both of the'startTime' and 'roundName' parameters may be undefined.
If 'roundName' exists, the 'updateOrder' variable will return 'roundName,' and the database will be updated. When'startTime' is set and 'roundName' is left blank, the 'roundName' will be rewritten from the database, which I don't want. Because if there is a 'roundName,' there will always be a 'roundName,' the value of 'roundName' can change but it will never be undefined.If startTime changes as well as roundName change then it will update the database. But my current logic is wrong. Struggling to implementing diffrent scenario logic.
const data = {
Records: [
{
Sns: {
Message:
'[{\n "orderId": "a4013438-926f-4fdc-8f6a-a7aa402b40ea",\n "roundName": "RO1"}]',
},
},
],
};
const existingData = [
{
modifiedAt: "2022-03-09T13:18:06.211Z",
lastMile: "progress",
createdAt: "2022-02-26T06:38:50.967+00:00",
orderId: "a4013438-926f-4fdc-8f6a-a7aa402b40ea",
},
];
// parse the data
const parseData = data.Records.flatMap((record) =>
JSON.parse(record.Sns.Message)
);
// check if the data exist or not
const existingOrder = existingData.filter(
(o1) => parseData.some((o2) => o1.orderId === o2.orderId)
);
// if there is no existingOrder then return false
if (existingOrder.length === 0) return;
// if there is exisiting order then add roundName and startTime from SNS event
const updateOrder = existingOrder.map((i) => {
const roundName = parseData.find((r) => {
return r.orderId === i.orderId;
}).roundName;
const startTime = parseData.find((r) => {
return r.orderId === i.orderId;
}).startTime;
return {
roundName: roundName ?? "",
startTime: startTime ?? "",
};
});
console.log(updateOrder);

React hook useEffect runs continuously forever/infinite loop, making duplicate of data fetching from APi,

im fetching data from APi and doing filtering from severside, i'm using useInfiniteScroll to fetch only limited amount of data on the first page, and with this im doing pagination too...
const [casesList, setCasesList] = useState<CaseModel[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isFetchingData, setIsFetchingData] = useState<boolean>(true);
const { inputValue } = React.useContext(MenuContext);
const debouncedValue = useDebounce(inputValue, 10);
these are my hook, (casesList) in which im saving all my incoming data from APi, input value is the value that im typing in search box for filtering the data, and debouncedValue is my custom hook, so the inputValue first goes to debouncedValue and then my debouncedValue will get the value of my inputValue,
const [pagination, setPagination] = useState<Pagination>({
continuationToken: "",
hasMoreResults: true,
});
const [isFetchingMore, setIsFetchingMore] = useInfiniteScroll();
these are my pagination and useInfiniteScroll() hooks...
so the actual problem that i'm facing is that,
const getDashboardCases = useCallback(
async (continuationToken: string) => {
setIsLoading(true);
let casesPageLimit = CASES_PAGE_LIMIT;
if (casesList.length === 0) {
const table = document.querySelector("#cases-items");
if (table) {
const caseItemHeight = 80;
const heightDifference =
table?.getBoundingClientRect().y > 0
? window.innerHeight - table?.getBoundingClientRect().y
: -1;
casesPageLimit = Math.max(
casesPageLimit,
Math.ceil(heightDifference / caseItemHeight)
);
}
}
const options: GetCasesListOptions = {
continuationToken,
filter: [],
sort: [],
pageLimit: casesPageLimit,
search: [debouncedValue]
};
const data: IData = await dashboardService.getCasesList(options);
setIsFetchingMore(false);
setIsLoading(false);
if (data.result) {
setIsFetchingData(false)
if (data.continuationToken !== undefined) {
const newContinuationToken = data.continuationToken;
setPagination((prevPagination) => ({
...prevPagination,
hasMoreResults: data.hasMoreResults,
continuationToken: newContinuationToken,
}));
} else {
setPagination((prevPagination) => ({
...prevPagination,
hasMoreResults: false,
}));
}
setCasesList((prevCases) => [...prevCases, ...data.result]);
dispatch(setAllowedClassifications(data.options));
}
},
[casesList.length, dashboardService, debouncedValue]
);
this code is fetching the data from the APi and for filtering i created an Object name Options
const options: GetCasesListOptions = {
continuationToken,
filter: [],
sort: [],
pageLimit: casesPageLimit,
search: [debouncedValue]
};
im saving my debouncedValue to the search Array in the Options object and then im using Options object in APi to filter the data
const data: IData = await dashboardService.getCasesList(options);
for example if i have 15 objects in APi, i need to get first 10 objects, and then i scroll down my callback function executes one more time and get the rest of the data...
if (data.result) {
setIsFetchingData(false)
if (data.continuationToken !== undefined) {
const newContinuationToken = data.continuationToken;
setPagination((prevPagination) => ({
...prevPagination,
hasMoreResults: data.hasMoreResults,
continuationToken: newContinuationToken,
}));
} else {
setPagination((prevPagination) => ({
...prevPagination,
hasMoreResults: false,
}));
}
setCasesList((prevCases) => [...prevCases, ...data.result]);
dispatch(setAllowedClassifications(data.options));
}
it's already done there...
now i want that, if i type something in the search box my api should run again to add my search value in the APi and filters the data...
but i'm facing problems doing this...
im calling my usecallback function like this...
useEffect(() => {
if (isFetchingMore && pagination.hasMoreResults) {
getDashboardCases(pagination.continuationToken);
}
}, [
getDashboardCases,
isFetchingMore,
pagination.continuationToken,
pagination.hasMoreResults,
debouncedValue
]);
if isFetchingMore && pagination.hasMoreResults is true, then it executes the function, but if type something in searchbox it is not running my function again...
i also tried to remove the if condition in the useEffect but it started infinite scrolling making duplicates of data, and i get this error...
Encountered two children with the same key, `d77c39f2-2dcd-4c4e-b7ee-1fde07b6583f`. Keys should be unique so that components maintain their identity across updates
so i need to re-run the function if i type something in search box and not get duplicated data back, and also i want to run the if condition that i typed in the useEffect...
please help, Thank you :)

React Redux dispatch 2 actions based on condition

I am trying to change store on (addList) event and check if this is the first list an array of lists to fire (selectList) event and add it to (state.selectedList.list). Now (selectList) is used whenever onClick event is working on an array of lists.
The question is when and how should I handle the event of adding first added a list to selectedList and than use (selectList) only onClick event as I used before.
export default connect(
state => ({
lists: getEntities('lists')(state),
selectedList: state.selectedList.list
}),
dispatch => ({
addList: (name) => dispatch({type: 'ADD_LIST', payload: name}),
selectList: (listId) => dispatch({type: 'CHANGE_SELECTED_LIST', payload: listId})
})
)(Lists)
If you have both of name and id of list you adding by click, then it could be done right in the addList handler:
addList(name, id) { // bound with click
const size = this.props.lists.length
this.props.addList(name)
if(!size) {
this.props.selectList(id)
}
}
Otherwise, you need to wait for list is created (to get the id) and then dispatch selectList action. The dirtiest solution could be just:
addList(name) { // bound with click
const size = this.props.lists.length
this.props.addList(name)
if(!size) {
// don't do that! it's just for proof of concept
setTimeout(() => this.props.selectList(this.props.lists[size].id))
}
}
Going further, you may use componentWillReceiveProps hook to catch the lists.length becames 1 and then trigger selectList normally:
componentWillReceiveProps(nextProps) {
const previousSize = this.props.lists.length
const size = nextProps.lists.length
if(previousSize === 0 && size === 1) {
this.props.selectList(nextProps.lists[size].id))
}
}
Another option would be a reducer where we don't deal with new list id, but only with a previous lists size (and no need for additional action trigger):
case ADD_LIST:
return {...state,
lists: [...state.lists,
action.newList
],
selectList: {...state.selectList,
list: !state.lists.length ? action.newList : state.selectList.list
}
}
At last, instead of all above, the action creator (the favorite one for me):
export function addList(name) {
return (dispatch, getState) => {
const size = getState().lists.length
const newList = generateNewListByName(name)
dispatch({
type: ADD_LIST,
newList
})
if(size === 0)
dispatch({
type: CHANGE_SELECTED_LIST,
selectedListId: newList.id
})
}
}
}
The last pice needs to be adapted to your infrastructure, but the main idea remains the same: do some logic by some previous state condition (size === 0).

Categories

Resources