Handle controlled checbox - reactjs

I have a form where I am rendering checkbox from map, how can I handle when someone unchecked box? Now i am using isChecked to set it.
import React, {ChangeEvent, Fragment, useCallback, useEffect, useState} from 'react';
import Button from '#atlaskit/button/standard-button';
import {Checkbox} from '#atlaskit/checkbox';
import {Grid, GridColumn} from '#atlaskit/page';
import Form, {CheckboxField, Field, FormFooter} from '#atlaskit/form';
import {ValueType as Value} from "#atlaskit/select/types";
import Select from "#atlaskit/select";
import {sentinelVulnerabilities} from "../constants";
import {invoke} from "#forge/bridge";
interface Option {
label: string;
value: string;
}
BasicConfiguration.defaultProps = {
jiraIssuePriorities: [],
}
const columns = 12;
export default function BasicConfiguration({jiraIssuePriorities, initPriorites, allowedVulnerabilities}: any) {
const [allowedVul, setAllowedVul] = useState<any | null>(undefined);
useEffect(() => {
(async () => {
await invoke("getStorage", {name: 'vulnerabilities_allowed'}).then(setAllowedVul);
})();
}, [])
const jiraIssuePrioritiesOptions = jiraIssuePriorities.map(({name, id}: any) => ({
label: name,
value: id,
}));
const shouldBySelected = (prioritySentinel: string) => {
if (initPriorites === undefined || Object.keys(prioritySentinel).length === 0.)
return '';
return initPriorites[prioritySentinel];
}
const shouldBeChecked = (vulnName: string): boolean => {
if (allowedVul === undefined || Object.keys(allowedVul).length === 0.) {
return false;
}
return allowedVul.includes(vulnName);
}
const onSubmit = async (data: any) => {
//Store mapping
await invoke("setStorage", {name: "vulnerabilities_allowed", data: data.vulnerabilities});
let priorities = {
note: undefined,
critical: undefined,
high: undefined,
medium: undefined,
low: undefined
};
if (data.hasOwnProperty('critical')) {
priorities.critical = data.critical.label;
}
if (data.hasOwnProperty('high')) {
priorities.high = data.high.label;
}
if (data.hasOwnProperty('medium')) {
priorities.medium = data.medium.label;
}
if (data.hasOwnProperty('low')) {
priorities.low = data.low.label;
}
if (data.hasOwnProperty('note')) {
priorities.note = data.note.label;
}
await invoke("setStorage", {name: 'vuln_priorities', data: priorities});
}
return (
<div style={{
display: 'flex',
width: '600px',
margin: '0 auto',
flexDirection: 'column',
paddingTop: 50,
}}>
<h3>Map Sentinel Vulnerabilities and Jira Issues</h3>
<Form onSubmit={onSubmit}>
{({formProps}) => (
<form {...formProps}>
{
sentinelVulnerabilities.map((element) => {
const isChecked = shouldBeChecked(element.value);
return <div>
<Grid spacing="compact" columns={columns}>
<GridColumn medium={4} css={{paddingTop: '5px'}}>
<CheckboxField name="vulnerabilities" value={element.value}>
{({fieldProps}) => <Checkbox {...fieldProps} label={element.label} isChecked={isChecked}
/>}
</CheckboxField>
</GridColumn>
<GridColumn medium={8}>
<Field<Value<Option>>
name={element.value}
isRequired={true}
defaultValue={{
value: shouldBySelected(element.value).toLowerCase(),
label: shouldBySelected(element.value)
}}
>
</Field>
</GridColumn>
</Grid>
</div>
})
}
</div>
);
}
What i want to achive is when page render have checkbox checked based on function shouldBeChecked() but I want that user can uncheck the box and submit the form. For now user is not able to unchecked the box, checkbox is always checked.

isChecked should be in a state, so it's value can be changed between different renders, otherwise it's value will always be the same returend from const isChecked = shouldBeChecked(element.value); on the first render within the map function.
And it's better to evaluate isChecked outside map function, because every time the component renders, the shouldBeChecked function will be running again which assins value to isChecked. So it'd be better to put this const isChecked = shouldBeChecked(element.value); in useEffect with empty dependency.

Related

Return an object from an array with a Select component

I'd like to return the entire object representation of an item in a list, however, when a selection is made and handleChangeSelectAuto is called the original value from useState is returned, and from there forward the previous selected value is always returned.
How can I select an item from a list and return all its associated data?
import React, { useEffect, useState } from 'react';
import { FormControl, InputLabel, Select, MenuItem } from '#mui/material';
interface AutoSelectorProps {}
const AutoSelector: React.FC<AutoSelectorProps> = () => {
const [auto, setAuto] = useState({} as object);
const [autoName, setAutoName] = useState('' as string);
const [autoList, setAutoList] = useState([
{
id: 1,
name: 'Car',
color: 'red'
},
{
id: 2,
name: 'Truck',
color: 'blue'
},
]);
const handleChangeSelectAuto = async (value: string) => {
const index = autoList.findIndex((item) => {
return item.name === value;
});
setAutoName(value);
setAuto(autoList[index]);
console.log(auto);
// 1st log: {}
// 2nd log: object from previous selection
// 3rd log: object from previous selection, etc.
};
return (
<div>
<FormControl>
<InputLabel>Select Auto</InputLabel>
<Select
value={autoName}
label="Auto"
onChange={(e) => handleChangeSelectAuto(e.target.value as string)}
>
{autoList.map((item) => {
return (
<MenuItem key={item.name} value={item.name}>
{item.name}
</MenuItem>
);
})}
</Select>
</FormControl>
</div>
);
};
export default AutoSelector;
P.S. If I add a button and handler to log auto it will return the correct value, but I'm not seeing a race condition.
useState is asynchronous. It will not show the values immediately. use useEffect to see the updated values
useEffect(() => {
console.log(auto);
}, [auto])

react-modal: How to get modal to auto-expand when react-datepicker is clicked? (code attached)

How to get modal to auto-expand when react-datepicker is clicked? Modal fits initially, but then when you click on the date field and the react-datepicker shows the calendar, the react-modal dialog does not auto-expand?
Before:
Ater clicking date:
Code:
import React, { useState } from 'react';
import Modal from 'react-modal';
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
const customStyles = {
content: {
top: '50%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
transform: 'translate(-50%, -50%)',
},
};
export default function TestPage2() {
const [modalIsOpen, setIsOpen] = React.useState(false);
const [startDate, setStartDate] = useState(new Date());
function openModal() {
setIsOpen(true);
}
function closeModal() {
setIsOpen(false);
}
return (
<div>
<button onClick={openModal}>Open Modal</button>
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="Example Modal"
style={customStyles}
>
<h3>Prior to Date Picker</h3>
<label htmlFor="Checkin Date">Checkin Date</label>
<DatePicker
selected={startDate}
// wrapperClassName="datePicker"
// className="form-control"
dateFormat="d MMMM yyyy"
name="checkinDate"
onChange={(date, event) => {
if (date) {
setStartDate(date)
}
}}
/>
<h3>After Date Picker</h3>
</Modal>
</div>
);
}
add the following property to your content object inside customStyles:
overflow: 'hidden'
and change the property values of react-datepicker-popper class :
.react-datepicker-popper {
position: static!important;
transform: none!important;
}
codesandbox: https://codesandbox.io/s/awesome-feather-mj81tz
I exactly faced the same issue a few weeks ago. I was looking for a easy fix but I' didn't found one. What I did is to split up the Datepicker into 2 components (+ redux).
Is your input <Datepicker />
Is your floating date picker <DatepickerFloatingItem/>
Datepicker
The datepicker is simply the input field and that's the component you can use throughout your react webapp. The biggest change here is that you need to run an action with the dispatcher to show the floating item. Additionally you need to determine the X and Y coordinates of your Datepicker to place the Floating Item at the correct spot.
Here is how the Datepicker component could look like (I've deleted the logic code to not confuse everyone):
class DatetimePicker extends React.Component<IDatetimePickerProps, IDatetimePickerState> {
public componentDidMount() {
this.updateDate(this.props.date);
window.addEventListener("resize", this.updateDimensions);
}
public componentWillUnmount() {
// display the floating datepicker even when resizing the window
window.removeEventListener("resize", this.updateDimensions);
}
///
/// LOGIC CODE (deleted)
///
private showCalendar = (): void => {
const { date, key } = this.state;
const { dispatch } = this.props;
const { showFloating } = this.props.datePickerState
if (!showFloating) {
this.createOutsideClickEvent();
if (date) {
this.updateDate(date);
}
} else {
this.removeOutsideClickEvent();
}
var boundingRect = document.getElementById(key)?.getBoundingClientRect();
if (boundingRect) {
dispatch(updateDatepickerData({ updateDate: this.updateDate, showFloating: true, date, positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, key }))
}
}
private updateDimensions = (): void => {
const { dispatch } = this.props;
const { date, showFloating } = this.props.datePickerState;
const { key } = this.state;
var boundingRect = document.getElementById(key)?.getBoundingClientRect();
if (boundingRect && this.props.datePickerState.key === key) {
dispatch(updateDatepickerData({ positionX: boundingRect.left, positionY: boundingRect.top + boundingRect.height, date, showFloating }))
}
}
public render(): React.ReactNode {
const { input, wrapperRef, key } = this.state;
const { style, disabled, className, onClick, styleWrapper, icon } = this.props;
return <span className="datetimepicker" ref={wrapperRef} onClick={onClick} style={styleWrapper}>
<Input
className={`datetimepicker__input ${className}`}
value={input}
onChange={this.updateInput}
getFocus={this.disableErrorView}
getBlur={this.textInputLostFocus}
rightButtonClicked={this.showCalendar}
style={style}
id={key}
icon={icon}
disabled={disabled}
/>
</span>
}
}
DatepickerFloatingItem
You only need to position the DatepickerFloatingItem once in your application.
It's best to position it at App.js (the root component).
It's also important to have position: relative for the parent element and define position: fixed for the DatepickerFloatingItem. Now you can easily position your floating element using top: and left: with the coordinates of the Datepicker
And this is how the DatepickerFloatingItem could look like (I also removed the unnecessary code to keep it more understandable)
interface IDatepickerFloatingItemStateProps {
date: Date
showFloating: boolean
positionX?: number
positionY?: number
}
class DatepickerFloatingItem extends React.Component<IDatepickerFloatingItemProps, IDatepickerFloatingItemState> {
private clickedFloatingDatepicker = (event: React.MouseEvent<HTMLSpanElement>): void => event.preventDefault()
private updateDate = (date?: Date, closeFloating?: boolean): void => {
const { dispatch } = this.props;
dispatch(updateDatepickerDate(date ? date : new Date()))
if (closeFloating) dispatch(updateDatepickerShowFloating(!closeFloating))
}
public render(): React.ReactNode {
const { showFloating, date, positionX, positionY } = this.props;
const { datepickerView } = this.state;
return <span className={`datetimepicker__floating ${showFloating ? "show" : ""}`} onClick={this.clickedFloatingDatepicker} style={{ top: `${positionY}px`, left: `${positionX}px` }}>
<DateCalendar datepickerView={datepickerView} date={date} updateDate={this.updateDate} />
</span>
}
}
function mapStateToProps(applicationState: ApplicationState): IDatepickerFloatingItemStateProps {
return {
date: applicationState.datepicker.date,
showFloating: applicationState.datepicker.showFloating,
positionX: applicationState.datepicker.positionX,
positionY: applicationState.datepicker.positionY
}
}
export default connect(mapStateToProps)(DatepickerFloatingItem)
Redux
I had to move some stuff to the Redux store to ensure that the FloatingDatePicker as well as the Datepicker have chance to communicate somehow
I've kept the redux store pretty straight forward:
import { Action, Reducer } from 'redux';
export interface DatepickerState {
date: Date
showFloating: boolean
positionX?: number
positionY?: number
updateDate?: (date?: Date) => void
}
export const UPDATE_DATEPICKER_SHOWFLOATING = "UPDATE_DATEPICKER_SHOWFLOATING";
export const UPDATE_DATEPICKER_DATA = "UPDATE_DATEPICKER_DATA";
export const UPDATE_DATEPICKER_DATE = "UPDATE_DATEPICKER_DATE";
export interface UpdateDatepickerShowFloating {
type: "UPDATE_DATEPICKER_SHOWFLOATING"
showFloating: boolean
}
export interface UpdateDatepickerDate {
type: "UPDATE_DATEPICKER_DATE"
date: Date
}
export interface UpdateDatepickerData {
type: "UPDATE_DATEPICKER_DATA"
state: DatepickerState
}
type KnownAction = UpdateDatepickerShowFloating | UpdateDatepickerData | UpdateDatepickerDate
const unloadedState: DatepickerState = { updateDate: () => { }, date: new Date(), showFloating: false, showTime: false, positionX: 0, positionY: 0 }
export const reducer: Reducer<DatepickerState> = (state: DatepickerState | undefined, incomingAction: Action): DatepickerState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case UPDATE_DATEPICKER_SHOWFLOATING:
return { ...state, showFloating: action.showFloating }
case UPDATE_DATEPICKER_DATE:
setTimeout(() => { if (state.updateDate) state.updateDate(action.date) }, 1)
return { ...state, date: action.date }
case UPDATE_DATEPICKER_DATA:
return { ...state, ...action.state }
default:
break;
}
return state;
}
And as you can see at the image, it's actually working inside a modal:
I know this approach is pretty time consuming, but still I hope it was still helping you somehow and I also hope your eyes don't burn from seeing class based components.

How to make data persist on refresh React JS?

I have a code where I mount a table with some firebase data but for some reason the values disappear and I been struggling for the next 2 weeks trying to solve this issue I haven't found a solution to this and I have asked twice already and I have try everything so far but it keeps disappearing.
Important Update
I just want to clarify the following apparently I was wrong the issue wasn't because it was a nested collection as someone mentioned in another question. The issue is because my "user" is getting lost in the process when I refresh.
I bring the user from the login to the app like this:
<Estudiantes user={user} />
and then I receive it as a props
function ListadoPedidos({user})
but is getting lost and because is getting lost when I try to use my firebase as:
estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
since the user is "lost" then the uid will be null. Since is null it will never reach the collection and the docs.
I have a simple solution for you. Simply raise the parsing of localStorage up one level, passing the preloadedState into your component as a prop, and then using that to initialize your state variable.
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
Then initialize state with the prop
const initialState = props.preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
And finally, update the useEffect hook to persist state any time it changes.
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
Full Code
import React, { useState, useEffect } from 'react';
import { db } from './firebase';
import { useHistory } from 'react-router-dom';
import './ListadoEstudiantes.css';
import {
DataGrid,
GridToolbarContainer,
GridToolbarFilterButton,
GridToolbarDensitySelector,
} from '#mui/x-data-grid';
import { Button, Container } from '#material-ui/core';
import { IconButton } from '#mui/material';
import PersonAddIcon from '#mui/icons-material/PersonAddSharp';
import ShoppingCartSharpIcon from '#mui/icons-material/ShoppingCartSharp';
import DeleteOutlinedIcon from '#mui/icons-material/DeleteOutlined';
import { Box } from '#mui/system';
const ListadoEstudiantes = (props) => {
const estData = JSON.parse(window.localStorage.getItem('estudiantes'));
return <Listado preloadedState={estData} {...props} />;
};
const Listado = ({ user, preloadedState }) => {
const history = useHistory('');
const crearEstudiante = () => {
history.push('/Crear_Estudiante');
};
const initialState = preloadedState || [];
const [estudiantesData, setEstudiantesData] = useState(initialState);
const parseData = {
pathname: '/Crear_Pedidos',
data: estudiantesData,
};
const realizarPedidos = () => {
if (estudiantesData == 0) {
window.alert('Seleccione al menos un estudiante');
} else {
history.push(parseData);
}
};
function CustomToolbar() {
return (
<GridToolbarContainer>
<GridToolbarFilterButton />
<GridToolbarDensitySelector />
</GridToolbarContainer>
);
}
const [estudiantes, setEstudiantes] = useState([]);
const [selectionModel, setSelectionModel] = useState([]);
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'nombre', headerName: 'Nombre', width: 200 },
{ field: 'colegio', headerName: 'Colegio', width: 250 },
{ field: 'grado', headerName: 'Grado', width: 150 },
{
field: 'delete',
width: 75,
sortable: false,
disableColumnMenu: true,
renderHeader: () => {
return (
<IconButton
onClick={() => {
const selectedIDs = new Set(selectionModel);
estudiantes
.filter((x) => selectedIDs.has(x.id))
.map((x) => {
db.collection('usuarios')
.doc(user.uid)
.collection('estudiantes')
.doc(x.uid)
.delete();
});
}}
>
<DeleteOutlinedIcon />
</IconButton>
);
},
},
];
const deleteProduct = (estudiante) => {
if (window.confirm('Quiere borrar este estudiante ?')) {
db.collection('usuarios').doc(user.uid).collection('estudiantes').doc(estudiante).delete();
}
};
useEffect(() => {}, [estudiantesData]);
const estudiantesRef = db.collection('usuarios').doc(user.uid).collection('estudiantes');
useEffect(() => {
estudiantesRef.onSnapshot((snapshot) => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes);
});
}, []);
useEffect(() => {
window.localStorage.setItem('estudiantes', JSON.stringify(estudiantes));
}, [estudiantes]);
return (
<Container fixed>
<Box mb={5} pt={2} sx={{ textAlign: 'center' }}>
<Button
startIcon={<PersonAddIcon />}
variant="contained"
color="primary"
size="medium"
onClick={crearEstudiante}
>
Crear Estudiantes
</Button>
<Box pl={25} pt={2} mb={2} sx={{ height: '390px', width: '850px', textAlign: 'center' }}>
<DataGrid
rows={estudiantes}
columns={columns}
pageSize={5}
rowsPerPageOptions={[5]}
components={{
Toolbar: CustomToolbar,
}}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange={(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = estudiantes.filter((row) => selectedIDs.has(row.id));
setEstudiantesData(selectedRowData);
}}
{...estudiantes}
/>
</Box>
<Button
startIcon={<ShoppingCartSharpIcon />}
variant="contained"
color="primary"
size="medium"
onClick={realizarPedidos}
>
Crear pedido
</Button>
</Box>
</Container>
);
};
I suspect that it's because this useEffect does not have a dependency array and is bring run on every render.
useEffect (() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
})
Try adding a dependency array as follows:
useEffect (() => {
if (estudiantes && estudiantes.length>0)
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes))
},[estudiantes])
This will still set the localStorage to [] when it runs on the first render. But when the data is fetched and estudiantes is set, the localStorage value will be updated. So I've added a check to check if it's not the empty array.
Change the dependency array of this useEffect to []:
estudiantesRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
console.log(estudiantes)
})
}, []);
The data flow in your code is somewhat contradictory, so I modify your code, and it works fine.
You can also try delete or add button, it will modify firebase collection, then update local data.
You can click refresh button in codesandbox previewer (not browser) to observe the status of data update.
Here is the code fargment :
// Set value of `localStorage` to component state if it exist.
useEffect(() => {
const localStorageEstData = window.localStorage.getItem("estudiantes");
localStorageEstData && setEstudiantes(JSON.parse(localStorageEstData));
}, []);
// Sync remote data from firebase to local component data state.
useEffect(() => {
// Subscribe onSnapshot
const unSubscribe = onSnapshot(
collection(db, "usuarios", user.id, "estudiantes"),
(snapshot) => {
const remoteDataSource = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data()
}));
console.info(remoteDataSource);
setEstudiantes(remoteDataSource);
}
);
return () => {
//unSubscribe when component unmount.
unSubscribe();
};
}, [user.id]);
// when `estudiantes` state update, `localStorage` will update too.
useEffect(() => {
window.localStorage.setItem("estudiantes", JSON.stringify(estudiantes));
}, [estudiantes]);
Here is the full code sample :
Hope to help you :)

Why is the component fully re-rendering when updating a single state through context?

I have created a page which has two columns:
In one column the idea is to display a list of items
On the other column, I should show some info related to the selected item
The code I have so far is:
import { INavLink, INavLinkGroup, INavStyles, Nav } from "#fluentui/react";
import React, { createContext, useContext, useState } from "react";
interface HistoryTtem {
id: string;
}
interface AppState {
selectedItem: string | undefined;
updateSelectedItem: (value: string | undefined) => void;
items: Array<HistoryTtem>;
}
const AppContext = createContext<AppState>({
selectedItem: undefined,
updateSelectedItem: (value: string | undefined) => {},
items: []
});
const App = () => {
const Column1 = () => {
const rootState: AppState = useContext(AppContext);
const getNavLinks: Array<INavLink> = rootState.items.map((item) => ({
name: item.id,
key: item.id,
url: ""
}));
const groups: Array<INavLinkGroup> = [
{
links: getNavLinks
}
];
const navStyles: Partial<INavStyles> = {
root: {
boxSizing: "border-box",
border: `1px solid #eee`,
overflowY: "auto"
}
};
const onItemClick = (
e?: React.MouseEvent<HTMLElement>,
item?: INavLink
) => {
if (item && item.key) {
rootState.updateSelectedItem(item.key);
}
};
return (
<Nav
onLinkClick={onItemClick}
selectedKey={rootState.selectedItem}
ariaLabel="List of previously searched transactions"
styles={navStyles}
groups={groups}
/>
);
};
const Column2 = () => {
return <div>aaa</div>;
};
const [historyItems, setHistoryItems] = useState<Array<HistoryTtem>>([
{
id: "349458457"
},
{
id: "438487484"
},
{
id: "348348845"
},
{
id: "093834845"
}
]);
const [selectedItem, setSelectedItem] = useState<string>();
const updateSelectedItem = (value: string | undefined) => {
setSelectedItem(value);
};
const state: AppState = {
selectedItem: selectedItem,
updateSelectedItem: updateSelectedItem,
items: historyItems
};
return (
<AppContext.Provider value={state}>
<div>
<Column1 />
<Column2 />
</div>
</AppContext.Provider>
);
};
export default App;
As you can see, I have a root state which will serve to drive the update of the second column triggered from inside the first one. But it is not working. When I click on an item, the whole component in the first column is re-rendering, while it should only change the selected item.
Please find here the CodeSandbox.
You shouldn't nest component functions.
The identity of Column1 changes for every render of App since it's an inner function, and that makes React think it needs to reconcile everything.
Move Column1 and Column2 up to the module level.
What makes react rerender is two things:
Change in State
Change in Props
You have an App Component which is the root of your components and it has a selectedItem state which is changing when an item is clicked so you have a new state and the new state will cause rerender

How to render only 5 items in react autosuggest?

I'am using react autosuggest npm package to get the json data and display it. I want to display only 5 items. How to do it?
Form.js
import React from 'react'
import Autosuggest from 'react-autosuggest';
import cities from 'cities.json';
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
);
};
const getSuggestionValue = suggestion => suggestion.name;
const renderSuggestion = suggestion => (
<div>
{console.log('suggestion', suggestion)}
{suggestion.name}
</div>
);
class Form extends React.Component {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<br/>
</div>
)
}
}
export default Form;
I want to render only 5 items, otherwise, computer hangs while loading huge data. Is there any other autocomplete react npm package, since I want only cities and country list. i.e when city is inputted, automatically the city name must be suggested with its relevant country.Any solution or suggestion highly appreciated. Thanks in advance
i modified you're getSuggestions() method a little i guess this should work for you.
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,5);
};
Use the Slice method with start index and last Index
suggestions={suggestions.slice(0, 5)}
import {
React
,Avatar
,axiosbase
} from '../../import-files';
import Autosuggest from 'react-autosuggest';
import './autosuggest.css';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Paper from '#material-ui/core/Paper';
import MenuItem from '#material-ui/core/MenuItem';
let suggestions = [ { label: 'Afghanistan' } ];
function renderInputComponent(inputProps) {
const { classes, inputRef = () => {}, ref, ...other } = inputProps;
return (
<TextField
className={classes.textField}
fullWidth
variant="outlined"
InputProps={{
inputRef: node => {
ref(node);
inputRef(node);
},
classes: {
input: classes.input,
},
}}
{...other}
/>
);
}
function renderSuggestion(suggestion, { query, isHighlighted }) {
return (
<MenuItem selected={isHighlighted} component="div">
<div>
<strong key={String(suggestion.id)} style={{ fontWeight: 300 }}>
<span className="sugg-option">
<span className="icon-wrap">
<Avatar src={suggestion.Poster}></Avatar>
</span>
<span className="name">
{suggestion.Title}
</span>
</span>
</strong>
</div>
</MenuItem>
);
}
function initSuggestions(value) {
suggestions = value;
}
function getSuggestionValue(suggestion) {
return suggestion.Title;
}
function onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) {
console.log('HandleSuggestion() '+suggestionValue);
}
const styles = theme => ({
root: {
height: 50,
flexGrow: 1,
},
container: {
position: 'relative',
},
suggestionsContainerOpen: {
position: 'absolute',
zIndex: 998,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
overflowY: 'scroll',
maxHeight:'376%'
},
suggestion: {
display: 'block',
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
divider: {
height: theme.spacing.unit * 2,
},
});
class IntegrationAutosuggest extends React.Component {
state = {
single: '',
popper: '',
suggestions: [],
};
componentDidMount() {
initSuggestions(suggestions);
}
// Filter logic
getSuggestions = async (value) => {
const inputValue = value.trim().toLowerCase();
var _filter = JSON.stringify({
filter : inputValue,
});
return await axiosbase.post(`${apiCall}`, _filter);
};
handleSuggestionsFetchRequested = ({ value }) => {
this.getSuggestions(value)
.then(data => {
if (data.Error) {
this.setState({
suggestions: []
});
} else {
const responseData = [];
data.data.itemsList.map((item, i) => {
let File = {
id: item.idEnc,
Title: item.englishFullName +' '+item.arabicFullName,
englishFullName: item.englishFullName,
arabicFullName: item.arabicFullName,
Poster: item.photoPath,
}
responseData.push(File);
});
this.setState({
suggestions: responseData
});
}
})
};
handleSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
handleChange = name => (event, { newValue }) => {
this.setState({
[name]: newValue,
});
if(event.type=='click'){
if(typeof this.props.handleOrderUserFirstNameChange === "function"){
this.props.handleOrderUserFirstNameChange(newValue);
}
this.state.suggestions.filter(f=>f.Title===newValue).map((item, i) => {
//id
//Title
// Poster
if(typeof this.props.handleUserIDChange === "function"){
this.props.handleUserIDChange(item.id);
}
});
}
};
render() {
const { classes } = this.props;
// console.log('Re-render!!');
// console.log(this.props);
// console.log(this.state.suggestions);
const autosuggestProps = {
renderInputComponent,
suggestions: this.state.suggestions,
onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested,
onSuggestionsClearRequested: this.handleSuggestionsClearRequested,
onSuggestionSelected: this.props.onSelect,
getSuggestionValue,
renderSuggestion,
};
return (
<div className={classes.root}>
<Autosuggest
{...autosuggestProps}
inputProps={{
classes,
placeholder: this.props.placeHolder,
value: this.state.single,
onChange: this.handleChange('single'),
}}
theme={{
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
/>
<div className={classes.divider} />
</div>
);
}
}
export default withStyles(styles)(IntegrationAutosuggest);

Resources