I'm new to JavaScript and React and am trying to move away from tutorials so have started making a simple app for my own learning benefit but have run into a roadblock with functions running asynchronously.
In onSearchSubmit, there is a setState which has the following in its callback:
this.findSalaryRangeMin(data.advertiser.id, data.teaser);
this.findSalaryRangeMax(data.advertiser.id, data.teaser);
How can I get these two functions above to run synchronously? findSalaryRangeMax uses this.state.salaryLower which is set in findSalaryRangeMin, but the console.log below reveals that findSalaryRangeMax is firing before findSalaryRangeMin has completed.
findSalaryRangeMax = (advertiserId, teaser) => {
console.log(`this.state.salaryLower: `, this.state.salaryLower);
// ... More code
};
I've read some resources which mention using promises, but I wasn't able to figure out how to apply it... I also am wondering whether it can be achieved with async/await.
Full(ish) Code:
(I've removed some code for simplicity)
import React from "react";
import JobSearch from "../api/jobSearch"; // axios
import SearchBar from "./SearchBar";
class App extends React.Component {
state = {
jobTitle: "",
advertiser: "",
salaryLower: "",
salaryLowerTop: "",
salaryUpper: "",
salaryUpperTop: ""
};
findSalaryRangeMin = (advertiserId, teaser) => {
this.setState({ salaryLower: 0, salaryLowerTop: 200000 }, async () => {
let salaryLowerPrev;
for (var i = 0; i < 20; i++) {
const response = await JobSearch.get(
`http://localhost:3001/salary-range/${advertiserId}/${this.state.salaryLower}/${this.state.salaryLowerTop}/${teaser}`
);
console.log(response);
if (response.data.totalCount === 1) {
salaryLowerPrev = this.state.salaryLowerTop;
this.setState({
salaryLowerTop: Math.round(
(this.state.salaryLowerTop - this.state.salaryLower) / 2 +
this.state.salaryLower
)
});
} else {
this.setState(
{
salaryLowerTop: salaryLowerPrev
},
() => {
this.setState({
salaryLower: Math.round(
(this.state.salaryLowerTop - this.state.salaryLower) / 2 +
this.state.salaryLower
)
});
}
);
}
}
});
};
findSalaryRangeMax = (advertiserId, teaser) => {
console.log(`this.state.salaryLower: `, this.state.salaryLower);
// ... More code
};
onSearchSubmit = async term => {
const response = await JobSearch.get(
`http://localhost:3001/job-info/${term}`
);
if (response.data.totalCount === 1) {
const data = response.data.data[0];
this.setState(
{
jobTitle: data.title,
advertiser: data.advertiser.description
},
() => {
this.findSalaryRangeMin(data.advertiser.id, data.teaser);
this.findSalaryRangeMax(data.advertiser.id, data.teaser);
}
);
} else {
console.log("totalCount not equal to 1: ", response.data.totalCount);
}
};
render() {
return (
<div>
<SearchBar onSearchSubmit={this.onSearchSubmit} />
<hr />
<div>
Job Title: {this.state.jobTitle}
Advertiser: {this.state.advertiser}
Salary Lower Range: {this.state.salaryLower}
Salary Upper Range: {this.state.salaryUpper}
</div>
</div>
);
}
}
export default App;
To give some context, the app I'm trying to make, queries an API for a jobs listing site. The API response doesn't reveal a salary range for an individual job, but the salary can fairly accurately be determined by querying salary ranges.
You are correct in your understanding that async or promises are needed if you want the functions to run synchronously and the existing code will run to the following line findSalaryRangeMax before returning with the data needed.
async/await and promises will definitely help, but often it's worth considering a few code changes too. As an example, you could combine the two functions into a single function like
findSalaryRanges(data.advertiser.id, data.teaser)
and fetch the data, process and set state once.
some pseudo code:
findSalaryRanges = async () => {
// get all the data needed first
const maxSalaryData = await JobSearch.get(`http://localhost:3001/salary-range/${advertiserId}/${this.state.salaryLower}/${this.state.salaryLowerTop}/${teaser}`);
const minSalaryData = await JobSearch.get(...);
// process data as needed
...
// set state once
this.setState({
salaryTop: salaryTop,
salaryLower: salaryLower
});
};
setState is async, so if you are dependent on the value of the state before running the next function you could do something like:
this.setState({
salaryLowerTop: Math.round(
(this.state.salaryLowerTop - this.state.salaryLower) / 2 +
this.state.salaryLower
)
}, () => this.findSalaryRangeMax(data.advertiser.id, data.teaser))
Can I execute a function after setState is finished updating?
Related
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]
)
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]);
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 :)
I am using the TagPicker to get data dynamically and present a set of the results that matches with the term. The issue is that looking into the docs there is not clear indication how to determine that the component data is loading or searching. The interface that had those was dropped (ISuggestionsProps) and the loadingText prop does not seem to work for me or I am probably using it wrong.
here is how I was able to load data from a list into the tagpicker:
const filterSuggestedTags = async (filterText: string, tagList: ITag[]) => {
//* possibly here to call an api if needed?
if (filterText) {
const url = 'url'
const resp = await fetch(url,{method:'GET',headers:{Accept:'application/json; odata=verbose'}})
return (await resp.json()).d.results.map(item => ({ key: item, name: item.Title }));
} else return []
};
codepen:
https://codepen.io/deleite/pen/MWjBMjY?editors=1111
This obviously has a lot of problems, first and the worst every keystroke is a promise fired. So, question is how to call an api using the search term and result the suggestions?
Thank you all.
I am ussing Office ui Fabric react v5 ("office-ui-fabric-react": "^5.135.5").
I am using TagPicker to laod external API data (long resolve time, large data set).
Loading suggestions is delayed for 700ms (after key pressed).
Loading suggestions is fired after 3 chars are typed.
During loading there is loading circle visible. I am loading suggestion in pages for 20 suggestions, if there is more items to be loaded on the bottom there is Loading more anchor which loads another page and add new suggestions to already loaded. I had to extend IBasePickerSuggestionsProps interface for moreSuggestionsAvailable?: boolean; in BasePicker.types.d.ts based on this issue: https://github.com/microsoft/fluentui/issues/6582
Doc: https://developer.microsoft.com/en-us/fluentui#/components/pickers
Codepen: https://codepen.io/matej4386/pen/ZEpqwQv
Here is my code:
const {disabled} = this.props;
const {
selectedItems,
errorMessage
} = this.state;
<TagPicker
onResolveSuggestions={this.onFilterChanged}
getTextFromItem={this.getTextFromItem}
resolveDelay={700}
pickerSuggestionsProps={{
suggestionsHeaderText: strings.suggestionsHeaderText,
noResultsFoundText: strings.noresultsFoundText,
searchForMoreText: strings.moreSuggestions,
moreSuggestionsAvailable: this.state.loadmore
}}
onGetMoreResults={this.onGetMoreResults}
onRenderSuggestionsItem={this.onRenderSuggestionsItem}
selectedItems={selectedItems}
onChange={this.onItemChanged}
itemLimit={1}
disabled={disabled}
inputProps={{
placeholder: strings.TextFormFieldPlaceholder
}}
/>
private onFilterChanged = async (filterText: string, tagList:IPickerItem[]) => {
if (filterText.length >= 3) {
let resolvedSugestions: IPickerItem[] = await this.loadListItems(filterText);
const {
selectedItems
} = this.state;
// Filter out the already retrieved items, so that they cannot be selected again
if (selectedItems && selectedItems.length > 0) {
let filteredSuggestions = [];
for (const suggestion of resolvedSugestions) {
const exists = selectedItems.filter(sItem => sItem.key === suggestion.key);
if (!exists || exists.length === 0) {
filteredSuggestions.push(suggestion);
}
}
resolvedSugestions = filteredSuggestions;
}
if (resolvedSugestions) {
this.setState({
errorMessage: "",
showError: false,
suggestions: resolvedSugestions,
loadmore: true,
loadMorePageNumber: 1
});
return resolvedSugestions;
} else {
return [];
}
} else {
return null
}
}
private onGetMoreResults = async (filterText: string, selectedItems?: any[]): Promise<IPickerItem[]> => {
let arrayItems: IPickerItem[] = [];
try {
let listItems: IOrganization[] = await this.GetOrganizations(this.Identity[0].id, filterText, 1);
...
private loadListItems = async (filterText: string): Promise<IPickerItem[]> => {
let { webUrl, filter, substringSearch } = this.props;
let arrayItems: IPickerItem[] = [];
try {
...
Ok, you can mitigate this problem with the prop 'resolveDelay' but still I did not find any standard way to handle items from an api this is the closest I came up.
Any samples or ideas would be appreciated.
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));