Prevent refershing of Child Component in react hooks - javascript
I have created a common table component in which I am calling in the file that are needed under some table there is an field action which have an edit and delete icon whenever I click on edit icon it should trigger a dropdown that will allow to update the status of component. But when I click on edit icon the table component will refresh so the menu is not opening at suitable position. Please help me
TableComponent
import React, { useState } from 'react';
import { Table, TableHead, TableRow, TableCell, makeStyles, TablePagination, TableSortLabel } from '#material-ui/core';
import PropTypes from 'prop-types';
const useStyles = makeStyles(theme => ({
table: {
marginTop: theme.spacing(3),
'& thead th': {
fontWeight: '600',
color:'white',
backgroundColor: '#143174',
},
'& tbody td': {
fontWeight: '300',
},
},
}))
export default function TableComponent(records, headCells,filterFn) {
const classes = useStyles();
const pages = [10, 50, 100]
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(pages[page])
const [order, setOrder] = useState()
const [orderBy, setOrderBy] = useState()
const TblContainer = props => (
<Table className={classes.table}>
{props.children}
</Table>
)
TblContainer.propTypes = {
children:PropTypes.element
}
const TblHead = props => {
const handleSortRequest = cellId => {
const isAsc = orderBy === cellId && order === "asc";
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(cellId)
}
return (<TableHead>
<TableRow>
{
headCells.map(headCell => (
<TableCell key={headCell.id}
sortDirection={orderBy === headCell.id ? order : false}>
{headCell.disableSorting ? headCell.label :
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={() => { handleSortRequest(headCell.id) }}>
{headCell.label}
</TableSortLabel>
}
</TableCell>))
}
</TableRow>
</TableHead>)
}
const handleChangePage = (event, newPage) => {
setPage(newPage);
}
const handleChangeRowsPerPage = event => {
setRowsPerPage(parseInt(event.target.value, 10))
setPage(0);
}
const TblPagination = () => (<TablePagination
component="div"
page={page}
rowsPerPageOptions={pages}
rowsPerPage={rowsPerPage}
count={records.length}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>)
function stableSort(array, comparator) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
const recordsAfterPagingAndSorting = () => {
return stableSort(filterFn.fn(records), getComparator(order, orderBy))
.slice(page * rowsPerPage, (page + 1) * rowsPerPage)
}
return {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
}
}
ParentCoponent
import React,{ useState, useEffect, useCallback } from 'react';
import { Button, Typography, Grid, TextField, MenuItem, Menu, IconButton, Link, TableBody, TableRow, TableCell, Paper, makeStyles } from '#material-ui/core';
import Modals from './Components/Modals';
import DropDownComponent from './Components/DropDownComponent';
import PropTypes from 'prop-types';
import { fetchImage,postImage, removeImage, updateImageStatus } from './app/ActionCreator';
import { connect } from 'react-redux';
import EditStatusComponent from './Components/EditStatusComponent';
import { Edit, Delete } from '#material-ui/icons';
import TableComponent from './Components/TableComponent';
const styles = makeStyles(theme => ({
root:{
marginTop:'60px',
},
typo:{
flexGrow:1,
textAlign:'center',
marginTop:'30px',
color:'black'
},
headingpaper:{
width:'100%',
height:40,
marginTop:'30px',
background:'white',
borderColor:'white',
},
buttonColor:{
color:'white',
background:'#143174'
},
formControl: {
margin:'8px',
minWidth: 120,
},
selectEmpty: {
marginTop: '16px',
},
formRoot:{
margin:'8px',
width: '25ch',
},
modalButton:{
color:'white',
background:'#143174',
marginRight:'10px'
}
}))
const headCells = [
{id:1,label:'SlNo'},
{id:2,label:'Virtual Machine Image '},
{id:3,label:'createdAt'},
{id:4,label:'createdBy'},
{id:5,label:'IpAddress'},
{id:6,label:'Status'},
{id:7,label:'Action',disableSorting:true}
]
function AdminImages(props){
const [imageModal,setImageModal] = useState(false)
const [name,setName] = useState('')
const [cpu,setCpu] = useState('')
const [ram,setRam] = useState('')
const [disk,setDisk] = useState('')
const [imageid,setImageid] = useState('')
const [anchorEl,setAnchorEl] = useState(null)
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const handlecpuChange = (event) => {
setCpu(event.target.value)
}
const handleramChange = (event) => {
setRam(event.target.value)
}
const handlediskChange = (event) => {
setDisk(event.target.value)
}
useEffect(() => {
props.fetchImageData()
},[])
const handleMenu = useCallback((event,id) => {
setAnchorEl(event.currentTarget)
setImageid(id)
},[])
const handleClose = (ev) => {
setAnchorEl(null)
if(ev.target.innerText !== ''){
const data = {
"status":ev.target.innerText
}
props.updateImageStatusData(imageid,data)
}
}
const newInstance = () => {
if(name !== '' && cpu !== '' && ram !== '' && disk !== ''){
props.postImageData(name,cpu,ram,disk)
}
else {
alert("Please Enter All Field")
}
setImageModal(false)
}
const labstatus = (status) => {
if(status === 'Resuming' || status === 'Running'){
return(
<EditStatusComponent
status={status}
statuscolor='#32CD32'
showDropDown={false}/>
)
}
else if(status === 'Suspending' || status === 'suspending' || status === 'error'){
return(
<EditStatusComponent
status={status}
statuscolor='red'
showDropDown={false}/>
)
}
else{
return(
<EditStatusComponent
status={status}
statuscolor='#143174'
showDropDown={false}/>
)
}
}
const handleSearch = e => {
let target = e.target;
setFilterFn({
fn: items => {
if (target.value == "")
return items;
else
return items.filter(x => x.instance_name.toLowerCase().includes(target.value))
}
})
}
const classes = styles()
const { TblContainer, TblHead, TblPagination, recordsAfterPagingAndSorting } = TableComponent(props.Images.images,headCells,filterFn)
return(
<div className={classes.root}>
<Grid container direction="row" justify="flex-end" alignItems="flex-start">
<Button variant="contained" className={classes.buttonColor} onClick={() => setImageModal(true)}>
Create Images
</Button>
</Grid>
<Typography variant="h2" noWrap className={classes.typo}>
Virtual Machine Image
</Typography>
<Paper>
<TblContainer>
<TblHead/>
<TableBody>
{
recordsAfterPagingAndSorting().map((v,i) => (
<TableRow key={v.id}>
<TableCell>{v.id}</TableCell>
<TableCell>{v.instance_name}</TableCell>
<TableCell>{v.created_at}</TableCell>
<TableCell>{v.created_by.first_name + v.created_by.last_name}</TableCell>
<TableCell><Link target={"_blank"} onClick={() => window.open(`http://${v.ip_address}/project`,'_blank')}>{v.ip_address}</Link></TableCell>
<TableCell>{labstatus(v.status)}</TableCell>
<TableCell>
<div style={{display:'flex'}}>
<IconButton
aria-controls="simple-menu"
aria-haspopup="true"
onClick={(e)=> handleMenu(e,v.id)}>
<Edit/>
</IconButton>
<IconButton onClick={() => props.removeImageData(v.id)}>
<Delete/>
</IconButton>
</div>
</TableCell>
</TableRow>
))
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}>
<MenuItem value="Resume" onClick={handleClose}>Resume</MenuItem>
<MenuItem value="Suspend" onClick={handleClose}>Suspend</MenuItem>
<MenuItem value="Freeze" onClick={handleClose}>Freeze</MenuItem>
</Menu>
<div>
<Modals
show={imageModal}
title="Create Instance"
handleClose={() => setImageModal(true)}
showSubmit={true}
onPress={newInstance}
size="xs">
<TextField
id="standard-basic"
label="Name"
style={{width:'50ch',margin:10}}
value={name}
onChange={(e) => setName(e.target.value)}
InputLabelProps={{
shrink: true,
}}/>
<DropDownComponent
dropdownid="standard-select-cpu"
selectedValue={cpu}
handleChange={handlecpuChange}
dropdownLabel="CPU">
<MenuItem value="8">8</MenuItem>
<MenuItem value="10">10</MenuItem>
<MenuItem value="12">12</MenuItem>
<MenuItem value="14">14</MenuItem>
<MenuItem value="16">16</MenuItem>
<MenuItem value="18">18</MenuItem>
<MenuItem value="20">20</MenuItem>
<MenuItem value="22">22</MenuItem>
<MenuItem value="24">24</MenuItem>
<MenuItem value="26">26</MenuItem>
<MenuItem value="28">28</MenuItem>
<MenuItem value="30">30</MenuItem>
<MenuItem value="32">32</MenuItem>
</DropDownComponent>
<DropDownComponent
dropdownid="standard-select-ram"
selectedValue={ram}
handleChange={handleramChange}
dropdownLabel="RAM">
<MenuItem value="16">16</MenuItem>
<MenuItem value="20">20</MenuItem>
<MenuItem value="24">24</MenuItem>
<MenuItem value="28">28</MenuItem>
<MenuItem value="32">32</MenuItem>
<MenuItem value="36">36</MenuItem>
<MenuItem value="40">40</MenuItem>
<MenuItem value="44">44</MenuItem>
<MenuItem value="48">48</MenuItem>
<MenuItem value="52">52</MenuItem>
<MenuItem value="56">56</MenuItem>
<MenuItem value="60">60</MenuItem>
<MenuItem value="64">64</MenuItem>
</DropDownComponent>
<DropDownComponent
dropdownid="standard-select-disk"
selectedValue={disk}
handleChange={handlediskChange}
dropdownLabel="Disk">
<MenuItem value="50">50</MenuItem>
<MenuItem value="100">100</MenuItem>
<MenuItem value="150">150</MenuItem>
<MenuItem value="200">200</MenuItem>
<MenuItem value="250">250</MenuItem>
<MenuItem value="300">300</MenuItem>
<MenuItem value="350">350</MenuItem>
<MenuItem value="400">400</MenuItem>
<MenuItem value="450">450</MenuItem>
<MenuItem value="500">500</MenuItem>
</DropDownComponent>
</Modals>
</div>
</div>
)
}
const mapStateToProps = state => {
return {
Images:state.Images,
}
}
const mapDispatchToProps = dispatch => ({
fetchImageData:() => {
dispatch(fetchImage())
},
postImageData:(name,cpu,ram,storage) => {
dispatch(postImage(name,cpu,ram,storage))
},
removeImageData:(id) => {
dispatch(removeImage(id))
},
updateImageStatusData:(id,data) => {
dispatch(updateImageStatus(id,data))
}
})
AdminImages.propTypes = {
classes:PropTypes.object.isRequired,
Images:PropTypes.object,
fetchImageData:PropTypes.func,
postImageData:PropTypes.func,
removeImageData:PropTypes.func,
updateImageStatusData:PropTypes.func,
}
export default connect(mapStateToProps,mapDispatchToProps)(AdminImages)
You can prevent unnecessary re-renders of a child component in 2 ways
If your component is class based then you can extend React.PureComponent or implement shouldComponentUpdate lifecycle method by yourself.
class MyComponent extends React.PureComponent {
// your component logic
}
class MyComponent extends Component {
shouldComponentUpdate(nextProps, nextState) {
//compare nextProps and this.props
//compare nextState and this.state
//return true for re-render, otherwise false
}
// your component logic
}
If you have a functional component you can wrap it with memo function that will check only for prop changes or you can pass it a function in the second argument that will do the current props and next props comparison and return true/false if you want do manual comparison just like shouldComponentUpdate
const MyComponent = React.memo(function(props) {
/* render using props */
});
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
Here are some useful links from the docs:
class component
function component
Related
Getting the error " React has detected a change in the order of Hooks called by StudentsFilter"
//StudentsFilter.jsx import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Badge, Box, Button, Checkbox, Flex, Radio, RadioGroup, Text, useColorMode, useColorModeValue, VStack, } from "#chakra-ui/react"; import React, { useState } from "react"; import { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { axiosInstance } from "../../../axiosConfig"; import { commonSlidercss, darkSlidercss } from "../../../GlobalStyles"; import { setFilterSearchMode, setSearchingBatch, setSearchingStream, } from "../../../redux/slices/adminUserSlice"; const StudentsFilter = () => { const [streams, setStreams] = useState(null); const [batchYear, setBatchYear] = useState([]); const [checkedStreams, setCheckedStreams] = useState([]); const [checkedBatches, setCheckedBatches] = useState([]); const siteState = useSelector((state) => state.siteReducer); const adminUsers = useSelector((state) => state.adminUserReducer); const filterSearchMode = adminUsers?.filterSearchMode; const site = siteState.siteInfo; const { colorMode } = useColorMode(); const dispatch = useDispatch(); useEffect(() => { getStream(); let batches = []; if (site) { let year = site.year_established; let current_year = new Date().getFullYear(); let year_diff = current_year - site.year_established; for (let i = 0; i <= year_diff; i++) { batches.push(year + i); } setBatchYear(batches); } }, [site]); const getStream = async () => { try { const res = await axiosInstance.get("stream"); setStreams(res?.data?.stream); } catch (error) { console.log("Something went wrong while getting streams", e); } }; const streamsHandler = (e, li) => { e.stopPropagation(); const index = checkedStreams.indexOf(li); if (index > -1) { setCheckedStreams([ ...checkedStreams.slice(0, index), ...checkedStreams.slice(index + 1), ]); } else { setCheckedStreams([...checkedStreams, li]); } }; const batchesHandler = (e, li) => { e.stopPropagation(); const index = checkedBatches.indexOf(li); if (index > -1) { setCheckedBatches([ ...checkedBatches.slice(0, index), ...checkedBatches.slice(index + 1), ]); } else { setCheckedBatches([...checkedBatches, li]); } }; useEffect(() => { dispatch(setSearchingStream(checkedStreams)); dispatch(setSearchingBatch(checkedBatches)); }, [checkedBatches, checkedStreams]); return ( <Flex p="6" direction="column" style={{ height: "inherit" }} align="space-between" justify="space-between" w="300px" maxH={231} overflowY="scroll" css={colorMode === "light" ? commonSlidercss : darkSlidercss} > <Box> <Text fontWeight="medium" fontSize="sm" mb={7}> More filters </Text> <Accordion allowMultiple> <AccordionItem> <AccordionButton> <Box flex="1" fontSize="xs" textAlign="left"> Batch </Box> <AccordionIcon /> </AccordionButton> <AccordionPanel pb={4}> <RadioGroup> <VStack align="start"> {batchYear && batchYear.map((li) => ( <Checkbox // onChange={checkboxChange} key={li} value={li} colorScheme={useColorModeValue( "primaryScheme", "purple" )} size="sm" onChange={(e) => batchesHandler(e, li)} isChecked={checkedBatches.includes(li)} > <Text fontSize="xs">{li}</Text> </Checkbox> ))} </VStack> </RadioGroup> </AccordionPanel> </AccordionItem> <AccordionItem> <AccordionButton> <Box flex="1" textAlign="left" fontSize="xs"> Stream </Box> <AccordionIcon /> </AccordionButton> <AccordionPanel pb={4}> <RadioGroup> <VStack align="start"> {streams && streams.map((li) => ( <Checkbox // onChange={checkboxChange} key={li.id} value={li.id} colorScheme={useColorModeValue( "primaryScheme", "purple" )} size="sm" onChange={(e) => streamsHandler(e, li.id)} isChecked={checkedStreams.includes(li.id)} > <Text fontSize="xs">{li?.name}</Text> </Checkbox> ))} </VStack> </RadioGroup> </AccordionPanel> </AccordionItem> </Accordion> </Box> <Box> <Button width="full" h="40px" borderRadius="10px" fontWeight="500" variant="primary" mt="10px" onClick={() => dispatch(setFilterSearchMode(!filterSearchMode))} > Filter </Button> </Box> </Flex> ); }; export default StudentsFilter; What is the reason why I am getting the error " React has detected a change in the order of Hooks called by StudentsFilter. This will lead to bugs and errors if not fixed" I have seen this warning in 2-3 components and also tried to correct it but I don't know what I am doing wrong? Can someone help me to identify it?
You're calling the useColorModeValue conditionally (and in a loop) in the return statement. That's probably the source of the error. You should use ESLint and the "rules of hooks" rule, it would have been highlighted directly in your editor.
react-simple-keyboard layout won't change to arabic
I am using react-simple-keyboard and I want to change the layout to Arabic but I couldn't. Note : I'm using react 18.2.0 This is my SearchBar component: import IconButton from "#mui/material/IconButton"; import SearchIcon from "#mui/icons-material/Search"; import TextField from "#mui/material/TextField"; import React, { useState, useRef, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import Tooltip from "#mui/material/Tooltip"; import FormControl from '#mui/material/FormControl'; import Keyboard from "react-simple-keyboard"; import "react-simple-keyboard/build/css/index.css"; const SearchBar = () => { const [inputValue, setInputValue] = useState(""); let navigate = useNavigate(); const onValueChange = (event) => { setInputValue(event.target.value); }; const onSearch = (event) => { event.preventDefault(); if (inputValue.length >= 1) { navigate("/SearchResult"); } }; const [input, setInput] = useState(""); const [layout, setLayout] = useState("default"); const keyboard = useRef(); const [keyboardVisibility, setKeyboardVisibility] = useState(false); useEffect(() => { function clickHandler(e) { if ( !(e.target.nodeName === "INPUT") && !e.target.classList.contains("hg-button") && !e.target.classList.contains("hg-row") ) { setKeyboardVisibility(false); } } window.addEventListener("click", clickHandler); return window.removeEventListener("click", clickHandler, true); }, []); const onChange = (input) => { setInput(input); console.log("Input changed", input); }; const handleShift = () => { const newLayoutName = layout === "default" ? "shift" : "default"; setLayout(newLayoutName); }; const onKeyPress = (button) => { console.log("Button pressed", button); if (button === "{shift}" || button === "{lock}") { handleShift(); } if (button === "{<enter}") { onSearch(); } else { console.log("Button pressed", button); } }; const handleChange = (e) => { const input = e.target.value; setInput(input); keyboard.current.setInput(input); }; return ( <form> <TextField InputProps={{ startAdornment: ( <FormControl sx={{ mr: 35, }}> <Tooltip title="Search" placement="bottom"> <IconButton type="search" aria-label="search" onClick={onSearch}> <SearchIcon sx={{ p: '10px', }}/> </IconButton> </Tooltip> </FormControl> ), }} id="search-bar" className="text" onChange={(e) => {onValueChange(e); handleChange(e);}} value={input} variant="outlined" placeholder="" size="small" inputProps={{min: 0, style: { textAlign: 'right' }}} onFocus={() => { setKeyboardVisibility(true); }} /> { keyboardVisibility && ( <Keyboard keyboardRef={r => (keyboard.current = r)} layoutName={layout} onChange={onChange} onKeyPress={onKeyPress} /> ) } </form> ); }; export default SearchBar;
Using Material-UI Autocomplete with useEffect
I'm trying to make a search component with Material-UI Autocomplete. The issue I am facing is that the data from the API doesn't reload. So when I load, it fetch results, but then, if I change the input, it doesn't make another request. I tried calling the api function in the onChange function, but it still doesn't do anything. I am still new to React Hooks, any feedback will be greatly appreciated. import fetch from "cross-fetch"; import React from "react"; import TextField from "#material-ui/core/TextField"; import Autocomplete from "#material-ui/lab/Autocomplete"; import CircularProgress from "#material-ui/core/CircularProgress"; function sleep(delay = 0) { return new Promise((resolve) => { setTimeout(resolve, delay); }); } export default function Asynchronous() { const [open, setOpen] = React.useState(false); const [options, setOptions] = React.useState([]); const [input_var, setInput] = React.useState(""); const loading = open && options.length === 0; const api = React.useEffect(() => { let active = true; if (!loading) { return undefined; } (async () => { var response = await fetch(`${baseUrl}/users?q=${input_var}`, { method: "get", headers: { "Content-Type": "application/json", Authorization: auth } }); await sleep(1e3); // For demo purposes. const countries = await response.json(); console.log(countries); countries.items.map((item) => console.log(item.name)); if (active) { setOptions(countries.items); } })(); return () => { active = false; }; }, [loading, input_var]); React.useEffect(() => { if (!open) { setOptions([]); } }, [open]); return ( <Autocomplete id="asynchronous-demo" style={{ width: 300 }} open={open} onOpen={() => { setOpen(true); }} onClose={() => { setOpen(false); }} getOptionSelected={(option, value) => option.name === value.name} getOptionLabel={(option) => option.name} options={options} loading={loading} renderInput={(params) => ( <TextField {...params} label="Asynchronous" variant="outlined" onChange={async (newValue) => { await api; await setInput(newValue); }} InputProps={{ ...params.InputProps, endAdornment: ( <React.Fragment> {loading ? ( <CircularProgress color="inherit" size={20} /> ) : null} {params.InputProps.endAdornment} </React.Fragment> ) }} /> )} /> ); }
Using axios to make use of a service not working
I'm trying to use S3 service to upload an image and it's telling me that certain variables aren't defined when I have defined them. I have imported axios and all the other stuff that are required import React, { useState } from "react"; import ReactDOM from "react-dom"; import axios from "axios"; import Grid from "#material-ui/core/Grid"; import Button from "#material-ui/core/Button"; import Card from "#material-ui/core/Card"; import TextField from "#material-ui/core/TextField"; import CreateIcon from "#material-ui/icons/Create"; import Box from "#material-ui/core/Box"; import CardMedia from "#material-ui/core/CardMedia"; import MuiAlert from "#material-ui/lab/Alert"; import Snackbar from "#material-ui/core/Snackbar"; import { withStyles } from "#material-ui/core/styles"; import { makeStyles } from "#material-ui/core/styles"; import Chip from "#material-ui/core/Chip"; import Avatar from "#material-ui/core/Avatar"; import Slider from "#material-ui/core/Slider"; import Typography from "#material-ui/core/Typography"; import InputAdornment from "#material-ui/core/InputAdornment"; import { connect } from "react-redux"; function mapStateToProps(state) { return {}; } const useStyles = makeStyles((theme) => ({ root: { "& > *": { margin: theme.spacing(1), marginLeft: theme.spacing(15), }, }, input: { display: "none", }, })); const useSliderStyles = makeStyles({ root: { width: 250, }, input: { width: 100, }, }); const UploadButton = () => { const classes = useStyles(); return ( <div className={classes.root}> <input accept='image/*' className={classes.input} id='contained-button-file' multiple type='file' /> <label htmlFor='contained-button-file'> <Button variant='contained' color='primary' component='span'> Upload </Button> </label> </div> ); }; const StyledCard = withStyles({ root: { height: 600, width: 350 }, })(Card); const PetitionForm = () => { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [open, setOpen] = useState(false); const [petition, validPetition] = useState(false); const [noTitle, titleError] = useState(false); const [noDescription, descriptionError] = useState(false); const [hashtag, setHashtag] = useState(""); const [arrayOfHashtags, addHashtag] = useState([]); const [money, setMoney] = React.useState(500); const slider = useSliderStyles(); const handleTitleChange = (event) => setTitle(event.target.value); const handleDescriptionChange = (event) => setDescription(event.target.value); const handleClose = (event, reason) => { if (reason === "clickaway") { return; } }; const Alert = (props) => ( <MuiAlert elevation={6} variant='filled' {...props} /> ); const clearField = (event) => { setOpen(true); if (title.length > 0 && description.length > 0) { validPetition(true); setTitle(""); setDescription(""); addHashtag([]); setHashtag(""); axios({ url: `call/s3/backend`, method: "post", data: { images: imageArray.toByteArray(), }, }) .then((res) => { imageUrlArr = res.data; axios({ url: `api/petition_posts`, method: "post", data: { petition_post: { title: title, description: description, hashtags: arrayOfHashtags.join(" "), amount_donated: 0, media: imageUrlArr, goal: money, card_type: "petition", org_profile_id: 1, }, }, }) .then((res) => { console.log(res.data); }) .catch((error) => console.log(error)); }) .catch((error) => console.log(error)); } titleError(true ? title.length === 0 : false); descriptionError(true ? description.length === 0 : false); }; const handleDelete = (h) => () => { addHashtag((arrayOfHashtags) => arrayOfHashtags.filter((hashtag) => hashtag !== h) ); }; const handleHashtagChange = (event) => setHashtag(event.target.value); const handleSliderChange = (event, newValue) => { setMoney(newValue); }; const handleInputChange = (event) => { setMoney(event.target.value === "" ? "" : Number(event.target.value)); }; const newHashtag = () => { if (arrayOfHashtags.length < 3) { addHashtag((arrayOfHashtags) => arrayOfHashtags.concat(hashtag)); } else { console.log("Too many hashtags"); } }; const Hashtags = arrayOfHashtags.map((h) => ( <Chip key={h.length} size='small' avatar={<Avatar>#</Avatar>} label={h} onDelete={handleDelete(h)} /> )); return ( <StyledCard> <Box mt={1}> <Grid container justify='center'> <TextField id='outlined-multiline-static' multiline rows={1} variant='outlined' placeholder='Title' value={title} onChange={handleTitleChange} helperText={ open // only displays helper text if button has been clicked and fields haven't been filled ? !noTitle || petition ? "" : "Can't be an empty field" : "" } /> </Grid> </Box> <Box mt={1}> <Grid container justify='center'> <CardMedia title='Petition'> <UploadButton /> </CardMedia> </Grid> </Box> <div className={slider.root}> <Typography>Amount to raise</Typography> <Box> <Grid container justify='center'> <Slider min={500} max={10000} value={typeof money === "number" ? money : 0} onChange={handleSliderChange} aria-labelledby='input-slider' /> <TextField className={slider.input} value={money} onChange={handleInputChange} InputProps={{ startAdornment: ( <InputAdornment position='start'>$</InputAdornment> ), }} helperText={ money < 500 || money > 10000 ? "Please enter a value between 500 and 10000" : "" } /> </Grid> </Box> </div> <Box mt={1} mb={1}> <Grid container justify='center'> <TextField size='small' inputProps={{ style: { fontSize: 15 }, }} id='outlined-multiline-static' multiline rows={1} placeholder='Hashtags' variant='outlined' value={hashtag} onChange={handleHashtagChange} helperText={ arrayOfHashtags.length === 3 ? "You reached the maximum amount of hashtags" : "" } /> <Button color='primary' onClick={newHashtag}> Create! </Button> {arrayOfHashtags.length > 0 ? Hashtags : ""} </Grid> </Box> <Box mt={1} justify='center'> <Grid container justify='center'> <TextField size='small' inputProps={{ style: { fontSize: 15 }, }} id='outlined-multiline-static' multiline rows={5} placeholder='Description' variant='outlined' value={description} onChange={handleDescriptionChange} helperText={ // only displays helper text if button has been clicked and fields haven't been filled open ? !noDescription || petition ? "" : "Can't be an empty field" : "" } /> </Grid> </Box> <Box mt={1}> <Grid container justify='center'> <Button onClick={clearField}> <CreateIcon /> Create Petition! </Button> {open && petition && ( <Snackbar open={open} autoHideDuration={2000} onClose={handleClose}> <Alert severity='success'> You have successfully create a petition! </Alert> </Snackbar> )} {open && !petition && ( <Snackbar open={open} autoHideDuration={2000} onClose={handleClose}> <Alert severity='error'>You're missing one or more fields</Alert> </Snackbar> )} </Grid> </Box> </StyledCard> ); }; export default connect(mapStateToProps)(PetitionForm); This is the error I'm getting, someone mentioned something about the scope and I think that shouldn't matter when I'm trying to assign a value to a variable as far I as know Line 109:19: 'imageArray' is not defined no-undef Line 113:11: 'imageUrlArr' is not defined no-undef Line 123:24: 'imageUrlArr' is not defined no-undef Search for the keywords to learn more about each error.
React JS Material UI Autocomplete: Change Options
I want to use an Autocomplete field for my React JS Project. For the design of the UI I use Material UI. In the documentation you can see the following example: <Autocomplete required id="combo-box-demo" filterOptions={(x) => x} value={this.state.departure} options={top100Films} getOptionLabel={(option) => option.title} renderInput={(params) => <TextField {...params} label="Startpunkt" variant="outlined" />} /> The options objects have the following default value: let top100Films = [ { title: 'The Shawshank Redemption', year: 1994 }, { title: 'Monty Python and the Holy Grail', year: 1975 }, ]; For my purpose I want to dynamically change the options since I use an Rest API where I get the results for the input. My question is therefore how I can change the options dynamically when the user is typing.
You can use onInputChange prop in your case: <Autocomplete required id='combo-box-demo' filterOptions={(x) => x} value={this.state.departure} options={top100Films} getOptionLabel={(option) => option.title} onInputChange={(event: object, value: string, reason: string) => { if (reason === 'input') { changeOptionBaseOnValue(value); } }} renderInput={(params) => ( <TextField {...params} label='Startpunkt' variant='outlined' /> )} /> Then you can define changeOptionBaseOnValue to handle your options.
You can check this example: import fetch from 'cross-fetch'; import React from 'react'; import TextField from '#material-ui/core/TextField'; import Autocomplete from '#material-ui/lab/Autocomplete'; import CircularProgress from '#material-ui/core/CircularProgress'; function sleep(delay = 0) { return new Promise((resolve) => { setTimeout(resolve, delay); }); } export default function Asynchronous() { const [open, setOpen] = React.useState(false); const [options, setOptions] = React.useState([]); const loading = open && options.length === 0; React.useEffect(() => { let active = true; if (!loading) { return undefined; } (async () => { const response = await fetch('https://country.register.gov.uk/records.json?page-size=5000'); await sleep(1e3); // For demo purposes. const countries = await response.json(); if (active) { setOptions(Object.keys(countries).map((key) => countries[key].item[0])); } })(); return () => { active = false; }; }, [loading]); React.useEffect(() => { if (!open) { setOptions([]); } }, [open]); return ( <Autocomplete id="asynchronous-demo" style={{ width: 300 }} open={open} onOpen={() => { setOpen(true); }} onClose={() => { setOpen(false); }} getOptionSelected={(option, value) => option.name === value.name} getOptionLabel={(option) => option.name} options={options} loading={loading} renderInput={(params) => ( <TextField {...params} label="Asynchronous" variant="outlined" InputProps={{ ...params.InputProps, endAdornment: ( <React.Fragment> {loading ? <CircularProgress color="inherit" size={20} /> : null} {params.InputProps.endAdornment} </React.Fragment> ), }} /> )} /> ); } Source
I'm doing this as part of an address search/verification by using the OnChange in the text field with a handleAddressChange function that calls a findAddresses function. findAddresses uses Axios to make a call to an API, and then saves those results and displays them as the options for the results in the autocomplete. Here's a simplified version of my code: import React, { useState, ChangeEvent } from 'react'; import { TextField, InputAdornment } from "#material-ui/core"; import Autocomplete from '#material-ui/lab/Autocomplete'; import { Search } from "#material-ui/icons"; import axios from "axios"; const AddressSearch = (props) => { const [addressesList, setAddressesList] = useState([]); const [inputAddress, setInputAddress] = useState<string>(""); const handleAddressChange = (event: ChangeEvent<{ value: unknown }>) => { setInputAddress(event.target.value as string); findAddresses(event.target.value as string); }; const baseUrl = 'https://api.website.com/'; const findAddresses = (text?: string) => { let params = `Key=value` if (!!text) { params += (`&Text=` + text); let addressesResponse; return ( axios.get(baseUrl + params) .then(response => { addressesResponse = response.data.Items if (!Array.isArray(addressesResponse) || !addressesResponse.length) { return; } setAddressesList(addressesResponse); }) .catch(error => console.log(error)) ) } } return ( <div> <Autocomplete id="address-autocomplete" freeSolo options={addressesList} getOptionLabel={(option) => option.Text} popupIcon={<Search />} renderInput={(params) => <TextField id="address-input" {...params} onChange={handleAddressChange} placeholder="Quickly find your address" InputProps={{ ...params.InputProps, startAdornment: ( <InputAdornment position="start"><Search /></InputAdornment> ) }} /> } /> </div> ); } export default AddressSearch;