React js google api suggestion AutocompleteService getPlacePredictions restriction to a specific state - javascript

I have the following component, which you suggest me an address.
The problem is that I need the addresses you suggest to me to refer to a single state.
Is there a way to specify this restriction via API?
Or is there a way that I could do via code in your opinion?
I thought I was doing something inside, do you want it to think it's a correct way?
Could I have problems?
React.useEffect(() => {
let active = true;
if (!autocompleteService.current && window.google)
autocompleteService.current = new window.google.maps.places.AutocompleteService();
if (!autocompleteService.current) return undefined;
if (value === '') {
setOptions([]);
return undefined;
}
fetch({ input: value }, res => {
//edit
const loc = res.map(a => (a.description.split(',').pop().trim() === 'Italia' ? a : false)).filter(Boolean);
if (active) setOptions(loc || []);
});
return () => {
active = false;
};
}, [value, fetch]);
Code:
import React from 'react';
import { useTranslation } from 'react-i18next';
import { makeStyles, TextField, Grid, Typography } from '#material-ui/core';
import { Autocomplete } from '#material-ui/lab';
import { LocationOn } from '#material-ui/icons';
import parse from 'autosuggest-highlight/parse';
import throttle from 'lodash/throttle';
function loadScript(src, position, id) {
if (!position) return;
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', id);
script.src = src;
position.appendChild(script);
}
const autocompleteService = { current: null };
const useStyles = makeStyles(theme => ({
icon: {
color: theme.palette.text.secondary,
marginRight: theme.spacing(2)
}
}));
export default function AutocompleteGoogleMaps(props) {
const { api, value, onChange, changeinfo } = props;
const classes = useStyles();
const [options, setOptions] = React.useState([]);
const loaded = React.useRef(false);
const { i18n } = useTranslation();
let language = localStorage.getItem('lang');
if (language === null) language = i18n.language;
if (typeof window !== 'undefined' && !loaded.current) {
if (!document.querySelector('#google-maps')) {
loadScript(
`https://maps.googleapis.com/maps/api/js?key=${api}&libraries=places&language=${language}`,
document.querySelector('head'),
'google-maps'
);
}
loaded.current = true;
}
const handleChange = ({ target: { value } }) => {
onChange(value);
};
const onTagsChange = (event, val) => {
if (val !== null && val.description !== null) {
onChange({ target: { value: val.description } });
if (changeinfo) geocodeByAddress(val.description).then(changeinfo);
}
};
const fetch = React.useMemo(
() =>
throttle((input, callback) => {
autocompleteService.current.getPlacePredictions(input, callback);
}, 200),
[]
);
React.useEffect(() => {
let active = true;
if (!autocompleteService.current && window.google)
autocompleteService.current = new window.google.maps.places.AutocompleteService();
if (!autocompleteService.current) return undefined;
if (value === '') {
setOptions([]);
return undefined;
}
fetch({ input: value }, res => {
if (active) setOptions(res || []);
});
return () => {
active = false;
};
}, [value, fetch]);
const geocodeByAddress = address => {
const geocoder = new window.google.maps.Geocoder();
const { OK } = window.google.maps.GeocoderStatus;
return new Promise((resolve, reject) => {
geocoder.geocode({ address }, (results, status) => {
if (status !== OK) {
return reject(status);
}
return resolve(results);
});
});
};
return (
<Autocomplete
id="google-map"
getOptionLabel={option => (typeof option === 'string' ? option : option.description)}
filterOptions={x => x}
options={options}
autoComplete
includeInputInList
freeSolo
value={value}
renderInput={params => <TextField {...params} onChange={handleChange} {...props} />}
onChange={onTagsChange}
renderOption={({
structured_formatting: {
main_text_matched_substrings: matches,
main_text: city,
secondary_text: street
}
}) => {
const parts = parse(
city,
matches.map(match => [match.offset, match.offset + match.length])
);
return (
<Grid container alignItems="center">
<Grid item>
<LocationOn className={classes.icon} />
</Grid>
<Grid item xs>
{parts.map(({ highlight, text }, key) => (
<span key={key} style={{ fontWeight: highlight ? 700 : 400 }}>
{text}
</span>
))}
<Typography variant="body2" color="textSecondary">
{street}
</Typography>
</Grid>
</Grid>
);
}}
/>
);
}

We have a similar entry for this feature request in the Google Issue Tracker so that we can provide information on it to all Google Maps Platform APIs users, as the technical aspects of this issue are of interest to a number of our customers,
Issues and Feature Requests in the Issue Tracker are directly curated by Places API specialists, who will provide updates in the Issue Tracker whenever there's news from the engineering team.
We would like to warmly invite you to view the issue in the Issue Tracker, and to star it to register your interest. This will subscribe you to receive technical updates on the issue. Starring the issue also provides us with valuable feedback on the importance of the issue to our customers, and increases the issue's priority with the product engineering team.
You can view and star the issue here:
https://issuetracker.google.com/35822067
This Issue Tracker entry is the authoritative source for public information regarding this issue, and all publicly-relevant updates will be posted there.

Related

Make other block disappear when chose multiple values

How can I make other filter button disappear when picked 1 (or multiple) value in same filter block.
Here is my code base:
const FilterBlock = props => {
const {
filterApi,
filterState,
filterFrontendInput,
group,
items,
name,
onApply,
initialOpen
} = props;
const { formatMessage } = useIntl();
const talonProps = useFilterBlock({
filterState,
items,
initialOpen
});
const { handleClick, isExpanded } = talonProps;
const classStyle = useStyle(defaultClasses, props.classes);
const ref = useRef(null);
useEffect(() => {
const handleClickOutside = event => {
if (ref.current && !ref.current.contains(event.target)) {
isExpanded && handleClick();
}
};
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, [isExpanded]);
const list = isExpanded ? (
<Form>
<FilterList
filterApi={filterApi}
filterState={filterState}
name={name}
filterFrontendInput={filterFrontendInput}
group={group}
items={items}
onApply={onApply}
/>
</Form>
) : null;
return (
<div
data-cy="FilterBlock-root"
aria-label={itemAriaLabel}
ref={ref}
>
<Menu.Button
data-cy="FilterBlock-triggerButton"
type="button"
onClick={handleClick}
aria-label={toggleItemOptionsAriaLabel}
>
<div>
<span>
{name}
</span>
<svg
width="8"
height="5"
viewBox="0 0 8 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.97291 0.193232C7.20854"
fill="currentColor"
/>
</svg>
</div>
</Menu.Button>
<div>
<div>
{list}
</div>
</div>
</div>
);
};
I am trying to achieve when I chose 1 value or more than 1 value inside filter block the other block will disappear but right now I achieved that when I chose 1 value the other filter block disappear but when I chose another value in the same block the other filter block appear again. Anyone have idea how can I work on this?
I am using React and Redux for this project
Thank you for helping me on this!!!!
Update:
Added parent component for FilterBlock.ks:
const FilterSidebar = props => {
const { filters, filterCountToOpen } = props;
const [selectedGroup, setSelectedGroup] = useState(null);
const talonProps = useFilterSidebar({ filters });
const {
filterApi,
filterItems,
filterNames,
filterFrontendInput,
filterState,
handleApply,
handleReset
} = talonProps;
const filterRef = useRef();
const classStyle = useStyle(defaultClasses, props.classes);
const handleApplyFilter = useCallback(
(...args) => {
const filterElement = filterRef.current;
if (
filterElement &&
typeof filterElement.getBoundingClientRect === 'function'
) {
const filterTop = filterElement.getBoundingClientRect().top;
const windowScrollY =
window.scrollY + filterTop - SCROLL_OFFSET;
window.scrollTo(0, windowScrollY);
}
handleApply(...args);
},
[handleApply, filterRef]
);
const result = Array.from(filterItems)
.filter(
([group, items]) =>
selectedGroup === null ||
selectedGroup === filterNames.get(group)
)
.map(([group, items], iteration) => {
const blockState = filterState.get(group);
const groupName = filterNames.get(group);
const frontendInput = filterFrontendInput.get(group);
return (
<FilterBlock
key={group}
filterApi={filterApi}
filterState={blockState}
filterFrontendInput={frontendInput}
group={group}
items={items}
name={groupName}
onApply={(...args) => {
console.log('args: ', ...args);
setSelectedGroup(prev =>
prev !== null ? null : groupName
);
return handleApplyFilter(...args);
}}
initialOpen={iteration < filterCountToOpen}
iteration={iteration}
/>
);
});
return (
<div className="container px-4 mx-auto">
<Menu
as="div"
className="my-16 justify-center flex flex-wrap py-5 border-y border-black border-opacity-5"
>
{result}
</Menu>
</div>
);
};
Updated added useFilterSideBar.js:
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useQuery } from '#apollo/client';
import { useHistory, useLocation } from 'react-router-dom';
import { useAppContext } from '#magento/peregrine/lib/context/app';
import mergeOperations from '../../util/shallowMerge';
import { useFilterState } from '../FilterModal';
import {
getSearchFromState,
getStateFromSearch,
sortFiltersArray,
stripHtml
} from '../FilterModal/helpers';
import DEFAULT_OPERATIONS from '../FilterModal/filterModal.gql';
const DRAWER_NAME = 'filter';
export const useFilterSidebar = props => {
const { filters } = props;
const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations);
const { getFilterInputsQuery } = operations;
const [isApplying, setIsApplying] = useState(false);
const [{ drawer }, { toggleDrawer, closeDrawer }] = useAppContext();
const [filterState, filterApi] = useFilterState();
const prevDrawer = useRef(null);
const isOpen = drawer === DRAWER_NAME;
const history = useHistory();
const { pathname, search } = useLocation();
const { data: introspectionData } = useQuery(getFilterInputsQuery);
const attributeCodes = useMemo(
() => filters.map(({ attribute_code }) => attribute_code),
[filters]
);
// Create a set of disabled filters.
const DISABLED_FILTERS = useMemo(() => {
const disabled = new Set();
// Disable category filtering when not on a search page.
if (pathname !== '/search.html') {
disabled.add('category_id');
disabled.add('category_uid');
}
return disabled;
}, [pathname]);
// Get "allowed" filters by intersection of filter attribute codes and
// schema input field types. This restricts the displayed filters to those
// that the api will understand.
const possibleFilters = useMemo(() => {
const nextFilters = new Set();
const inputFields = introspectionData
? introspectionData.__type.inputFields
: [];
// perform mapping and filtering in the same cycle
for (const { name } of inputFields) {
const isValid = attributeCodes.includes(name);
const isEnabled = !DISABLED_FILTERS.has(name);
if (isValid && isEnabled) {
nextFilters.add(name);
}
}
return nextFilters;
}, [DISABLED_FILTERS, attributeCodes, introspectionData]);
const isBooleanFilter = options => {
const optionsString = JSON.stringify(options);
return (
options.length <= 2 &&
(optionsString.includes(
JSON.stringify({
__typename: 'AggregationOption',
label: '0',
value: '0'
})
) ||
optionsString.includes(
JSON.stringify({
__typename: 'AggregationOption',
label: '1',
value: '1'
})
))
);
};
// iterate over filters once to set up all the collections we need
const [
filterNames,
filterKeys,
filterItems,
filterFrontendInput
] = useMemo(() => {
const names = new Map();
const keys = new Set();
const frontendInput = new Map();
const itemsByGroup = new Map();
const sortedFilters = sortFiltersArray([...filters]);
for (const filter of sortedFilters) {
const { options, label: name, attribute_code: group } = filter;
// If this aggregation is not a possible filter, just back out.
if (possibleFilters.has(group)) {
const items = [];
// add filter name
names.set(group, name);
// add filter key permutations
keys.add(`${group}[filter]`);
// TODO: Get all frontend input type from gql if other filter input types are needed
// See https://github.com/magento-commerce/magento2-pwa/pull/26
if (isBooleanFilter(options)) {
frontendInput.set(group, 'boolean');
// add items
items.push({
title: 'No',
value: '0',
label: name + ':' + 'No'
});
items.push({
title: 'Yes',
value: '1',
label: name + ':' + 'Yes'
});
} else {
// Add frontend input type
frontendInput.set(group, null);
// add items
for (const { label, value } of options) {
items.push({ title: stripHtml(label), value });
}
}
itemsByGroup.set(group, items);
}
}
return [names, keys, itemsByGroup, frontendInput];
}, [filters, possibleFilters]);
// on apply, write filter state to location
useEffect(() => {
if (isApplying) {
const nextSearch = getSearchFromState(
search,
filterKeys,
filterState
);
// write filter state to history
history.push({ pathname, search: nextSearch });
// mark the operation as complete
setIsApplying(false);
}
}, [filterKeys, filterState, history, isApplying, pathname, search]);
const handleOpen = useCallback(() => {
toggleDrawer(DRAWER_NAME);
}, [toggleDrawer]);
const handleClose = useCallback(() => {
closeDrawer();
}, [closeDrawer]);
const handleApply = useCallback(() => {
setIsApplying(true);
handleClose();
}, [handleClose]);
const handleReset = useCallback(() => {
filterApi.clear();
setIsApplying(true);
}, [filterApi, setIsApplying]);
const handleKeyDownActions = useCallback(
event => {
// do not handle keyboard actions when the modal is closed
if (!isOpen) {
return;
}
switch (event.keyCode) {
// when "Esc" key fired -> close the modal
case 27:
handleClose();
break;
}
},
[isOpen, handleClose]
);
useEffect(() => {
const justOpened =
prevDrawer.current === null && drawer === DRAWER_NAME;
const justClosed =
prevDrawer.current === DRAWER_NAME && drawer === null;
// on drawer toggle, read filter state from location
if (justOpened || justClosed) {
const nextState = getStateFromSearch(
search,
filterKeys,
filterItems
);
filterApi.setItems(nextState);
}
// on drawer close, update the modal visibility state
if (justClosed) {
handleClose();
}
prevDrawer.current = drawer;
}, [drawer, filterApi, filterItems, filterKeys, search, handleClose]);
useEffect(() => {
const nextState = getStateFromSearch(search, filterKeys, filterItems);
filterApi.setItems(nextState);
}, [filterApi, filterItems, filterKeys, search]);
return {
filterApi,
filterItems,
filterKeys,
filterNames,
filterFrontendInput,
filterState,
handleApply,
handleClose,
handleKeyDownActions,
handleOpen,
handleReset,
isApplying,
isOpen
};
};
Update FilterList component:
const FilterList = props => {
const {
filterApi,
filterState,
filterFrontendInput,
name,
group,
itemCountToShow,
items,
onApply,
toggleItemOptionsAriaLabel
} = props;
const classes = useStyle(defaultClasses, props.classes);
const talonProps = useFilterList({ filterState, items, itemCountToShow });
const { isListExpanded, handleListToggle } = talonProps;
const { formatMessage } = useIntl();
// memoize item creation
// search value is not referenced, so this array is stable
const itemElements = useMemo(() => {
if (filterFrontendInput === 'boolean') {
const key = `item-${group}`;
return (
<li
key={key}
className={classes.item}
data-cy="FilterList-item"
>
<FilterItemRadioGroup
filterApi={filterApi}
filterState={filterState}
group={group}
name={name}
items={items}
onApply={onApply}
labels={labels}
/>
</li>
);
}
return items.map((item, index) => {
const { title, value } = item;
const key = `item-${group}-${value}`;
if (!isListExpanded && index >= itemCountToShow) {
return null;
}
// create an element for each item
const element = (
<li
key={key}
className={classes.item}
data-cy="FilterList-item"
>
<FilterItem
filterApi={filterApi}
filterState={filterState}
group={group}
item={item}
onApply={onApply}
/>
</li>
);
// associate each element with its normalized title
// titles are not unique, so use the element as the key
labels.set(element, title.toUpperCase());
return element;
});
}, [
classes,
filterApi,
filterState,
filterFrontendInput,
name,
group,
items,
isListExpanded,
itemCountToShow,
onApply
]);
const showMoreLessItem = useMemo(() => {
if (items.length <= itemCountToShow) {
return null;
}
const label = isListExpanded
? formatMessage({
id: 'filterList.showLess',
defaultMessage: 'Show Less'
})
: formatMessage({
id: 'filterList.showMore',
defaultMessage: 'Show More'
});
return (
<li className={classes.showMoreLessItem}>
<button
onClick={handleListToggle}
className="text-sm hover_text-indigo-500 transition-colors duration-sm"
data-cy="FilterList-showMoreLessButton"
>
{label}
</button>
</li>
);
}, [
isListExpanded,
handleListToggle,
items,
itemCountToShow,
formatMessage,
classes
]);
return (
<Fragment>
<ul className={classes.items}>
{itemElements}
{showMoreLessItem}
</ul>
</Fragment>
);
};
FilterList.defaultProps = {
onApply: null,
itemCountToShow: 5
};
Update FilterRadioGroup:
const FilterItemRadioGroup = props => {
const { filterApi, filterState, group, items, onApply, labels } = props;
const radioItems = useMemo(() => {
return items.map(item => {
const code = `item-${group}-${item.value}`;
return (
<FilterItemRadio
key={code}
filterApi={filterApi}
filterState={filterState}
group={group}
item={item}
onApply={onApply}
labels={labels}
/>
);
});
}, [filterApi, filterState, group, items, labels, onApply]);
const fieldValue = useMemo(() => {
if (filterState) {
for (const item of items) {
if (filterState.has(item)) {
return item.value;
}
}
}
return null;
}, [filterState, items]);
const field = `item-${group}`;
const fieldApi = useFieldApi(field);
const fieldState = useFieldState(field);
useEffect(() => {
if (field && fieldValue === null) {
fieldApi.reset();
} else if (field && fieldValue !== fieldState.value) {
fieldApi.setValue(fieldValue);
}
}, [field, fieldApi, fieldState.value, fieldValue]);
return (
<RadioGroup field={field} data-cy="FilterDefault-radioGroup">
{radioItems}
</RadioGroup>
);
};
FilterItemRadioGroup.defaultProps = {
onApply: null
};
The code seems overly complicated for what it does.
Could you perhaps make the filter block a state object like this:
filterGroup = {
title: "",
hasSelectedItems: boolean,
filterItems: []
}
Then it would be a simple matter of checking hasSelectedItems to conditionally render only one filterGroup in the UI.
I apologize I don't have the time to go in depth and try out your code, but I would put the filterBlock rendering inside the return statement and do something like this:
return(
{ (!!filterValue && (filterGroup === selectedGroup))
&&
<FilterGroup />
}
);
I find the best results come from using my state values as conditions inside my return statement.
That way, I don't have to manage complex logic in the .map() functions or with useEffect.
It looks like FilterSideBar is where you build the the array of FilterBlocks.
According to your question, I think you want to display all filter blocks if NO filters are selected, but once a filter is selected, you want the other FilterBlocks to not render, correct?
So, in your FilterSidebar module, I'd modify your "result" variable to include a check to see if anything has already been selected.
If something has been selected, then only render the FilterBlock that corresponds to that selected list item.
const result = Array.from(filterItems)
.filter(
([group, items]) =>
selectedGroup === null ||
selectedGroup === filterNames.get(group)
)
.map(([group, items], iteration) => {
const blockState = filterState.get(group);
const groupName = filterNames.get(group);
const frontendInput = filterFrontendInput.get(group);
return (
// This is the conditional rendering
(!!blockState.filterGroupSelected && blockState.filterGroupSelected === group) ?
<FilterBlock
key={group}
...
/>
: null
);
});
Then you'll have to add an item to your state object (unless you already have it) to track the group that was selected (e.g. "Printer Type"), and only allow that one to render.
If no group has a selected filter item, they should all render.
Also, you'll have to be sure to clear the selectedGroup whenever there are no selected items.
If this does not work, it may be that your state changes are not triggering a re-render. In which case it can be as simple as adding a reference to your state object in your component's return method like this:
{blockState.filterGroupSelected && " " }
It's a hack but it works, and keeps you from adding calls to useEffect.

High memory usage in React Native FlatList

I am developing an app which allows the user to search for books and then display it in the search results. For displaying the results, I am using a FlatList with 3 columns and displaying the book cover and some basic information about the book.
I am storing the results from the API response in state without the comoponent. As more results are added, the memory consumption increases but the data is in JSON format, no images are store in state.
I have tried, using removeClippedSubviews and few other options that allow setting the window size but that has little to no difference on the memory usage.
Am I missing something here or is there a way to optimise this? Sample code is uploaded to this github repo
Here is the code snippet I am using:
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow strict-local
*/
import type { Node } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
ActivityIndicator,
FlatList,
Platform,
SafeAreaView,
StatusBar,
StyleSheet,
useColorScheme,
View,
} from 'react-native';
import { Button, SearchBar, useTheme } from 'react-native-elements';
import { searchBooks } from './api/GoogleBooksService';
import HttpClient from './network/HttpClient';
import BookCard from './components/BookCard';
const searchParamsInitialState = {
startIndex: 1,
maxResults: 12,
totalItems: null,
};
let debounceTimer;
const debounce = (callback, time) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(callback, time);
};
const isEndOfList = searchParams => {
const { startIndex, maxResults, totalItems } = searchParams;
if (totalItems == null) {
return false;
}
console.log('isEndOfList', totalItems - (startIndex - 1 + maxResults) < 0);
return totalItems - (startIndex - 1 + maxResults) < 0;
};
const App: () => Node = () => {
const isDarkMode = useColorScheme() === 'dark';
const [isLoading, setIsLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [globalSearchResults, setGlobalSearchResults] = useState([]);
const [searchParams, setSearchParams] = useState(searchParamsInitialState);
let searchCancelToken;
let searchCancelTokenSource;
// This ref will be used to track if the search Term has changed when tab is switched
const searchRef = useRef();
const clearSearch = () => {
console.log('Clear everything!');
searchRef.current = null;
setGlobalSearchResults([]);
setSearchParams(searchParamsInitialState);
setIsLoading(false);
searchCancelTokenSource?.cancel();
searchCancelToken = null;
searchCancelTokenSource = null;
};
useEffect(() => {
debounce(async () => {
setIsLoading(true);
await searchGlobal(searchTerm);
setIsLoading(false);
}, 1000);
}, [searchTerm]);
/**
* Search method
*/
const searchGlobal = async text => {
if (!text) {
// Clear everything
clearSearch();
return;
}
setIsLoading(true);
try {
// Use the initial state values if the search term has changed
let params = searchParams;
if (searchRef.current !== searchTerm) {
params = searchParamsInitialState;
}
const { items, totalItems } = await searchBooks(
text,
params.startIndex,
params.maxResults,
searchCancelTokenSource?.token,
);
if (searchRef.current === searchTerm) {
console.log('Search term has not changed. Appending data');
setGlobalSearchResults(prevState => prevState.concat(items));
setSearchParams(prevState => ({
...prevState,
startIndex: prevState.startIndex + prevState.maxResults,
totalItems,
}));
} else {
console.log(
'Search term has changed. Updating data',
searchTerm,
);
if (!searchTerm) {
console.log('!searchTerm', searchTerm);
clearSearch();
return;
}
setGlobalSearchResults(items);
setSearchParams({
...searchParamsInitialState,
startIndex:
searchParamsInitialState.startIndex +
searchParamsInitialState.maxResults,
totalItems,
});
}
searchRef.current = text;
} catch (err) {
if (HttpClient.isCancel(err)) {
console.error('Cancelled', err.message);
}
console.error(`Error searching for "${text}"`, err);
}
setIsLoading(false);
};
const renderGlobalItems = ({ item }) => {
return <BookCard book={item} />;
};
const { theme } = useTheme();
return (
<SafeAreaView style={styles.backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
/>
<View style={styles.container}>
<SearchBar
showLoading={isLoading}
placeholder="Enter search term here"
onChangeText={text => {
setSearchTerm(text);
}}
value={searchTerm}
platform={Platform.OS}
/>
{isLoading && globalSearchResults.length <= 0 && (
<ActivityIndicator animating style={styles.loader} />
)}
{globalSearchResults.length > 0 && (
<FlatList
removeClippedSubviews
columnWrapperStyle={styles.columnWrapper}
data={globalSearchResults}
numColumns={3}
showsHorizontalScrollIndicator={false}
keyExtractor={item => item + item.id}
renderItem={renderGlobalItems}
ListFooterComponent={
<>
{!isLoading &&
!isEndOfList(searchParams) &&
searchParams.totalItems > 0 && (
<Button
type="clear"
title="Load more..."
onPress={async () => {
await searchGlobal(searchTerm);
}}
/>
)}
{isLoading && searchParams.totalItems != null && (
<ActivityIndicator
size="large"
style={{
justifyContent: 'center',
}}
color={theme.colors.primary}
/>
)}
</>
}
/>
)}
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
backgroundStyle: 'white',
container: {
height: '100%',
width: '100%',
},
columnWrapper: {
flex: 1,
},
loader: {
flex: 1,
justifyContent: 'center',
},
});
export default App;
There is something called PureComponent in react native. If you create FlatList as PureComponent, you can see lot of improvement.
It will not rerender items until data has been changed.
for example:
class MyList extends React.PureComponent {
}
For more reference check this
Can you try to chuck your array of list items into small sub-arrays, this package uses this mechanism https://github.com/bolan9999/react-native-largelist
The package has been praised by complex app teams including the Discord Mobile Team - https://discord.com/blog/how-discord-achieves-native-ios-performance-with-react-native

How can I customize my MUI Autocomplete in class component to accept new input and value in V5

I have a class component where I used a customized MUI Autocomplete. It was working fine in V4 but after I switch to V5 there is bug in the code.
Here is my custom Autocomplete code
import React from 'react';
import TextField from '#mui/material/TextField';
import Autocomplete from '#mui/material/Autocomplete';
const SingleSoloSelect2 = (props) => {
const filter = (options, params) => {
let inList = false;
const filtered = options.filter(o => {
if (!params.inputValue) return true;
const value = o[props.labelField].toLowerCase();
const input = params.inputValue.toLowerCase();
if (value === input) inList = true;
if (value.includes(input)) return true;
return false;
});
return { inList, filtered };
}
return (
<Autocomplete
fullWidth
freeSolo={props.freeSolo !== undefined ? props.freeSolo : true}
id={props.id}
value={props.value}
options={props.options || []}
disabled={props.disabled}
selectOnFocus
clearOnBlur
handleHomeEndKeys
onChange={(e, v) => {
let isNew = false;
if (typeof v === 'string') {
isNew = v;
// delete v;
} else if (v && v.inputValue) {
isNew = v.inputValue;
// delete v.inputValue;
} else {
isNew = v;
}
props.onChange(v, isNew, props.id);
}}
getOptionLabel={props.getOptionLabel}
filterOptions={(options, params) => {
const { inList, filtered } = filter(options, params);
if (props.freeSolo === undefined || props.freeSolo === true) {
if (params.inputValue !== '' && !inList) {
filtered.push({
[props.valueField]: params.inputValue,
[props.labelField]: params.inputValue,
new: true,
});
}
}
return filtered;
}}
renderOption={(option) => {
return option.new
? `Add "${option[props.labelField]}"`
: option[props.labelField];
}
}
isOptionEqualToValue={props.isOptionEqualToValue}
renderInput={(params) => (<TextField {...params} label={props.label} variant={props.variant} size={props.size} error={props.error} />)}
/>
);
}
export default SingleSoloSelect2;
In my main component I used it as
<SingleSoloSelect2
id="free-solo-with-text-demo"
label="my label"
options={options}
value={value || null}
onChange={this.handleSingleSelect}
labelField="name"
valueField="name"
getOptionLabel={(option) => option.name}
/>
My option is an array of objects like
[{id: 1, name: name1},
...
]
The this.handleSingleSelect is defined as
handleSingleSelect = (v, isNew) => {
const q = this.state.activeQuestion;
q.value = v;
if (isNew) q.options.push(q.value);
this.setState({ activeQuestion: q });
}
When I load the form, the option label is blank but I can see it in the console when selected.
When I add new input I am not seeing the Add and input label on screen

how to using a custom function to generate suggestions in fluent ui tag picker

I am trying to use the tagPicker from fluent ui. I am using as starting point the sample from the site:
https://developer.microsoft.com/en-us/fluentui#/controls/web/pickers
The problem is that the object I have has 3 props. the objects in the array are {Code:'string', Title:'string', Category:'string'}. I am using a state with a useeffect to get the data. SO far works fine, the problem is that the suggestion are rendered blank. It filter the items but does not show the prop I want.
Here is my code:
import * as React from 'react';
import {
TagPicker,
IBasePicker,
ITag,
IInputProps,
IBasePickerSuggestionsProps,
} from 'office-ui-fabric-react/lib/Pickers';
import { mergeStyles } from 'office-ui-fabric-react/lib/Styling';
const inputProps: IInputProps = {
onBlur: (ev: React.FocusEvent<HTMLInputElement>) => console.log('onBlur called'),
onFocus: (ev: React.FocusEvent<HTMLInputElement>) => console.log('onFocus called'),
'aria-label': 'Tag picker',
};
const pickerSuggestionsProps: IBasePickerSuggestionsProps = {
suggestionsHeaderText: 'Suggested tags',
noResultsFoundText: 'No color tags found',
};
const url="url_data"
export const TestPicker: React.FunctionComponent = () => {
const getTextFromItem = (item) => item.Code;
const [state, setStateObj] = React.useState({items:[],isLoading:true})
// All pickers extend from BasePicker specifying the item type.
React.useEffect(()=>{
if (!state.isLoading) {
return
} else {
caches.open('cache')
.then(async cache=> {
return cache.match(url);
})
.then(async data=>{
return await data.text()
})
.then(data=>{
const state = JSON.parse(data).data
setStateObj({items:state,isLoading:false})
})
}
},[state.isLoading])
const filterSuggestedTags = (filterText: string, tagList: ITag[]): ITag[] => {
return filterText
? state.items.filter(
tag => tag.Code.toLowerCase().indexOf(filterText.toLowerCase()) === 0 && !listContainsTagList(tag, tagList),
).slice(0,11) : [];
};
const listContainsTagList = (tag, state?) => {
if (!state.items || !state.items.length || state.items.length === 0) {
return false;
}
return state.items.some(compareTag => compareTag.key === tag.key);
};
return (
<div>
Filter items in suggestions: This picker will filter added items from the search suggestions.
<TagPicker
removeButtonAriaLabel="Remove"
onResolveSuggestions={filterSuggestedTags}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
itemLimit={1}
inputProps={inputProps}
/>
</div>
);
};
I just got it, I need to map the items to follow the {key, name} from the sample. Now it works.
setStateObj({items:state.map(item => ({ key: item, name: item.Code })),isLoading:false})

How can the render be synced with the state on pagination click with react?

I created a suggestions search and its built to break up the fetch based on the current page. The state is console.loged correctly, but the render is one page click event behind. This is obviously not the behavior we want. It seems like the state is being updated fine. I have tried to refactor the code difference ways, and even tried this.forceUpdate()
Here is the code
SearchOrderBar.js
import React, { Component } from "react";
import {Input, Label, Table, Icon, Header, Menu} from 'semantic-ui-react';
import "./SearchOrderBar.css";
// import { resolve } from "dns";
// import PropTypes from 'prop-types';
import Pagination from '../Search/Pagination';
class SearchOrderBar extends Component {
constructor(props) {
super(props);
this.text = "";
this.state = {
suggestions: [],
addToQuery: false,
Query: [],
pagesNeeded: 0,
page: 1
};
let searchTerm = null;
const {pageLimit = null, keyTimer = null, } = props;
this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 10;
this.handlePageClick = this.handlePageClick.bind(this);
this.fetchCallBack = this.fetchCallBack.bind(this);
// this.addToQuery = this.addToQuery.bind(this);
this.keyUpHandler = this.keyUpHandler.bind(this);
this.keyDownHandler = this.keyDownHandler.bind(this);
}
handlePageClick(page){
this.forceUpdate();
this.setState({
page: page
})
this.fetchCallBack();
}
//This fetch should be called in a dynamic switch case
fetchCallBack() {
let y = this.pageLimit;
let x = this.state.page > 1 ? (this.pageLimit*this.state.page) - this.pageLimit : 0;
// Return a promise
return new Promise((resolve, reject) => {
let searchTerm = this.searchTerm;
return fetch(`http://localhost:5000/api/searchorders/${searchTerm}/${x}/${y}`)
.then(res => {
if (!res.ok) {
throw res;
}
// Convert serialized response into json
return res.json()
}).then(data => {
//Use data
let searchTerm = data.map(data => {
let rData = {};
rData = data;
return rData;
})
this.item = searchTerm;
//console.log('here from callback')
this.setState({
suggestions: []
})
return searchTerm;
}).then( data => {
// console.log(this.totalRecords)sd
//console.log(data)
if (searchTerm.length === 0) {
this.setState({
suggestions: [],
rangeCount_URL: `http://localhost:5000/api/searchorderscount/${searchTerm}`
});
} else {
const suggestions = data.filter(function(v){
if(Object.values(v).includes(searchTerm.toLowerCase()) !== -1 || Object.values(v).includes(searchTerm.toUpperCase()) !== -1){
return v
}
})
console.log(suggestions)
this.text = searchTerm;
this.setState({ suggestions: suggestions.sort()});
}
})
})
}
pageCountCallBack(){
return new Promise((resolve, reject) => {
let searchTerm = this.searchTerm;
return fetch(`http://localhost:5000/api/searchorderscount/${searchTerm}/`)
.then(res => {
if (!res.ok) {
throw res;
}
// Convert serialized response into json
return res.json()
}).then(data => {
//Use data
let searchTerm = data.map(data => {
let rData = {};
rData = data;
return rData;
})
this.item = searchTerm;
// console.log('here from Page Count callback')
this.renderSuggestions();
resolve(searchTerm)
})
})
}
keyUpHandler = (e) => {
if(e.target.value.length >= 3){
this.keyTimer = setTimeout(this.countFetch(e), 1500);
} else {
this.setState(() => {
return {
suggestions : [],
pagesNeeded : 0
}
})
clearTimeout(this.keyTimer);
}
}
keyDownHandler = (e) => {
clearTimeout(this.keyTimer);
}
//Any time text is changed in the text field
countFetch = (e) => {
const value = e.target.value;
this.searchTerm = value;
this.pageCountCallBack().then(data => {
const totalRecords = data[0].rows;
this.setState(() => {
return {pagesNeeded : Math.ceil(totalRecords / this.pageLimit)}
})
//console.log("total" + totalRecords);
//console.log("page limit"+this.pageLimit);
//console.log("Needed" + this.state.pagesNeeded );
})
this.fetchCallBack();
}
renderSuggestions() {
//const { suggestions } = this.state;
const tableStyle = {
'tableLayout': 'fixed',
'overflowWrap': 'break-word'
}
return (
<Table style={tableStyle} celled>
{this.state.suggestions.length === 0 ?
(<Table.Body>
<Table.Cell colSpan="7">
<div className="ui fluid warning icon message">
<Icon name="exclamation triangle" size="huge" color="orange"/>
<div className="content">
<Header>No Records Found</Header>
<p>Try Seaching by one of the following:</p>
<ul>
<dt>Name</dt>
<dt>Order Number</dt>
<dt>Address (Shipping or Billing )</dt>
<dt>Phone Number</dt>
<dt>Email</dt>
</ul>
</div>
</div>
</Table.Cell>
</Table.Body>)
: (
<>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Order#</Table.HeaderCell>
<Table.HeaderCell>Billing Address</Table.HeaderCell>
<Table.HeaderCell>Shipping Address</Table.HeaderCell>
<Table.HeaderCell>Email</Table.HeaderCell>
<Table.HeaderCell>Phone Number</Table.HeaderCell>
<Table.HeaderCell>Sales Channel</Table.HeaderCell>
<Table.HeaderCell>Order Date</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{this.state.suggestions.map((item, index) => (
<Table.Row className="hoverRow">
<Table.Cell key={index} onClick={() => this.addToQuery(item)}>
{item.customerPO}
</Table.Cell>
<Table.Cell>
{item.billToAddress}
</Table.Cell>
<Table.Cell>{item.shipToAddress}</Table.Cell>
<Table.Cell>{item.email}</Table.Cell>
<Table.Cell>{item.phone}</Table.Cell>
<Table.Cell>{item.customerContact}</Table.Cell>
<Table.Cell>{item.dateCreated}</Table.Cell>
</Table.Row>
))}
</Table.Body>
</>
)
}
<Pagination key={this.state.pagesNeeded} tableCols="7" pagesNeeded={this.state.pagesNeeded} btnLimit={5} pageClick={this.handlePageClick} currPage={this.state.page} pageLimit={this.pageLimit}/>
</Table>
);
}
handleIconClick(){
console.log('icon clicked ' + this.state.Query )
}
render() {
const {text} = this.state
//console.log(this.state)
return (
<>
<div className="App-Component">
<div className="App-Search">
<Input icon={{ name: 'search', circular: true, link: true, onClick: () => this.handleIconClick() }} placeholder="Search" value={text} type="text" onKeyUp={this.keyUpHandler} onKeyDown={this.keyDownHandler} className="App-Search"/>
{this.renderSuggestions()}
</div>
</div>
</>
);
}
}
export default SearchOrderBar;
Here is the pagination but I don't think this matters as much for the solution. It is relevant for the page button click.
import React, {Component} from 'react';
import {Input, Label, Table, Icon, Header, Menu} from 'semantic-ui-react'
/**
* Helper Method for creating a range of Numbers
* Range )( )
*/
const range = (from, to, step = 1) => {
let i = from;
const range = [];
while (i<=to) {
range.push(i);
i+=step;
}
}
export default class Pagination extends Component {
constructor(props){
super(props)
const { totalRecords = null, pageNeighbours = 0, rangeCount_URL = this.props.rangeCount_URL, pageArray = [] } = props;
this.pageArray = typeof pageArray === 'array' ? pageArray : [];
}
renderPagination = () => {
//console.log("hello from pagination");
let n = this.props.pagesNeeded;
let pArray = [];
let page = this.props.currPage;
//console.log(n)
if (page > 1){
pArray.push(<Menu.Item as='a' icon onClick={() => this.props.pageClick(page-1)}>
<Icon name='chevron left' />
</Menu.Item>)
}
for(let i = (page >1 ? page-1: page); pArray.length < (page > this.props.btnLimit ? this.props.btnLimit+1 : this.props.btnLimit); i++){
//console.log(i);
pArray.push(<Menu.Item index={i} className={i == page ? 'active' : ''} onClick={() => this.props.pageClick(i)} as='a'>{i}</Menu.Item>)
}
if (page < n){
pArray.push(<Menu.Item as='a' icon onClick={() => this.props.pageClick(page+1)}>
<Icon name='chevron right' />
</Menu.Item>)
}
this.pageArray = pArray;
return pArray;
}
render(){
const pageCount = (() => {
const totalRecords = this.totalRecords;
if(totalRecords > 0){
return (this.totalPages = Math.ceil(this.totalRecords / this.props.pageLimit))
}
})();
//console.log(this.pageArray);
return(
<Table.Footer>
{ this.props.pagesNeeded > 1 &&
<Table.Row>
<Table.HeaderCell colSpan={this.props.tableCols}>
<Menu floated='right' pagination>
{this.renderPagination()}
</Menu>
</Table.HeaderCell>
</Table.Row>
}
</Table.Footer>
)
}
}
setState is batched and invoked asynchronously, meaning when you call to this.setState({page}) then read this.state.page in fetchCallBack you probably get the "old" page and not the new page.
Either pass the page directly to fetchCallBack
this.fetchCallBack(page)
And read the page from it and not directly from the state
Or call it as the second argument of setState which is a callback that react will invoke right after the state has been updated.
this.setState({ page }, this.fetchCallBack);
At the point fetchCallBack is called, this.state.page is not updated yet because setState is called asynchronously, that's why it's using the old value. Try this:
handlePageClick(page) {
this.setState({ page }, this.fetchCallBack);
}
The callback syntax allows you to run the function in the next iteration.

Categories

Resources