I have problem with get values from reference object in array. Get data from reducer -> each on collection -> get values from input
When entering to component reference return empty array, but when I save something in code the reference working.
Can anyone help me? I am a beginner with React and Typescript and someone can explain me what I doing wrong.
Some code
interface StorableCreateProps extends RouteComponentProps<MatchParams> {
containers: Container[],
getContainers: () => void
addStorables: (locations: Array<string>, storables: Object[]) => void
}
type StorableCreateState = {
storablesCount: Array<any>,
open: boolean
}
class StorableCreate extends React.Component<StorableCreateProps, StorableCreateState> {
containerCount: React.RefObject<HTMLInputElement>[] = this.props.containers.map(() => React.createRef());
componentDidMount() {
this.props.getContainers()
}
render() {
console.log(this.containerCount.map(el => el.current));
return (
<div>
<Card variant="outlined">
<CardContent>
<Box>
{
this.props.containers.map((row, index) => {
return (
<div key={index}>
<ContainerStorable ref={this.containerCount[index]} container={row} />
</div>
)
})
}
</Box>
</CardContent>
</Card>
</div>
)
}
}
type ContainerStorableProps = {
container: Container,
}
export const ContainerStorable = React.forwardRef<HTMLInputElement, ContainerStorableProps>((props, ref) => {
const [count, setCount] = React.useState<string>("0");
return (
<div>
<Grid container spacing={4} justify="center">
<Grid item md={4}>
<Typography variant="body1">{props.container.container_name}</Typography>
</Grid>
<Grid item md={2}>
<Typography variant="body1">{props.container.short_name}</Typography>
</Grid>
<Grid item md={1}>
<TextField
fullWidth
placeholder="0"
size="small"
label="Count"
value={count}
inputRef={ref}
variant="outlined"
onChange={(e) => setCount(e.target.value)}
name={props.container.id.toString()}
/>
</Grid>
<Grid item md={2}>
<Button
variant="contained"
className="btn-success"
style={{ borderRadius: '50%', width: 40, height: 40, minWidth: 0 }}
>
<Add />
</Button>
</Grid>
</Grid>
</div>
)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Related
I have an image gallery and I want to search by language name through a search bar and It throws this error:
Uncaught TypeError: Cannot read properties of undefined (reading 'map')
at Languages (Languages.jsx:28:25)
at renderWithHooks (react-dom.development.js:16305:18)
at mountIndeterminateComponent (react-dom.development.js:20074:13)
at beginWork (react-dom.development.js:21587:16)
at beginWork$1 (react-dom.development.js:27426:14)
at performUnitOfWork (react-dom.development.js:26557:12
Languages.jsx ( where I map my array through a filter)
import { languages } from "../../../database/languages";
import { Searchbar } from "../components/Searchbar";
const filterData = (query, data) => {
if (!query) data;
else return data.filter((d) => d.alt.toLowerCase().includes(query));
};
export const Languages = () => {
const [searchQuery, setSearchQuery] = useState("");
const filteredData = filterData(searchQuery, languages);
return(
<Searchbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
{filteredData.map((language) => (
<Grid key={language.id} item xs={12} sm={6} md={6} lg={3} xl={3}>
<Card elevation={0} xs={12}>
<Typography color="text.secondary" variant="p">
{language.alt}
</Typography>
<CardMedia
sx={{ height: 250, width: "100%" }}
component="img"
image={language.image}
alt={language.alt}
/>
</Card>
</Grid>
))}
);
};
languages.jsx (this is my array)
export const languages = [
{
id: "1",
image: "boostraplogo.svg",
alt: "BOOSTRAP",
language: "boostrap",
},
{
id: "2",
image: "csslogo.svg",
alt: "CSS",
language: "css",
},
{
id: "3",
image: "htmlogo.svg",
alt: "HTML",
language: "html",
},
];
Searcbar.jsx ( this is my search bar inputt)
export const Searchbar = ({ searchQuery, setSearchQuery }) => {
return (
<form>
<TextField
onInput={(e) => {
setSearchQuery(e.target.value);
}}
label="Type a language"
placeholder="type here.."
size="small"
/>
<IconButton type="submit" aria-label="search">
<Search />
</IconButton>
</form>
);
};
I have tried adding filteredtData.filter.map(...) but it does not work either.
I also need to validate if the image they are looking for does not appear I need a message showing that is not found.
import { languages } from "../../../database/languages";
import { Searchbar } from "../components/Searchbar";
const filterData = (query, data) => {
if (!query) data;
else return data.filter((d) => d.alt.toLowerCase().includes(query));
};
export const Languages = () => {
const [searchQuery, setSearchQuery] = useState("");
const filteredData = filterData(searchQuery, languages);
return(
<Searchbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
{filteredData.map((language) => (
<Grid key={language.id} item xs={12} sm={6} md={6} lg={3} xl={3}>
<Card elevation={0} xs={12}>
<Typography color="text.secondary" variant="p">
{language.alt}
</Typography>
<CardMedia
sx={{ height: 250, width: "100%" }}
component="img"
image={language.image}
alt={language.alt}
/>
</Card>
</Grid>
))}
);
};
If query is not matching data you get an empty array so you cant use map on that.To resolve this check if there is filteredData and to the mapping then, in other case write a message in your UI. Like this:
import { languages } from "../../../database/languages";
import { Searchbar } from "../components/Searchbar";
const filterData = (query, data) => {
if (!query) data;
else return data.filter((d) => d.alt.toLowerCase().includes(query));
};
export const Languages = () => {
const [searchQuery, setSearchQuery] = useState("");
const filteredData = filterData(searchQuery, languages);
return(
<Searchbar searchQuery={searchQuery} setSearchQuery={setSearchQuery} />
{(filteredData && filteredData.length > 0 ) ? filteredData.map((language) => (
<Grid key={language.id} item xs={12} sm={6} md={6} lg={3} xl={3}>
<Card elevation={0} xs={12}>
<Typography color="text.secondary" variant="p">
{language.alt}
</Typography>
<CardMedia
sx={{ height: 250, width: "100%" }}
component="img"
image={language.image}
alt={language.alt}
/>
</Card>
</Grid>
)) : <div>Enter your message for no matching query here</div>}
);
};
I'm new with react form, I'm using Material UI and Controller Component, and I'm sending a React Hook form request but not getting any response, form's (HTML form tag) onSubmit event is occurring but handleSubmit is not working I have one more form like that it is working perfectly fine but I don't know why it's not working, can anybody please help me with that
import { Button, useTheme, Grid, TextField, Typography } from '#mui/material';
import WSSelectBox from 'components/common/WSSelect';
import React, { FC } from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { isMobile } from 'utils/mediaqueries';
import CheckBoxButton from '../CheckBoxButton';
import { formTypes } from './utils';
import { yupResolver } from '#hookform/resolvers/yup';
import * as yup from 'yup';
import { FormPropTypes } from './type';
const schema = yup
.object()
.shape({
name: yup.string().required(),
residentialCountry: yup.number().required(),
initiator: yup.string().required(),
program: yup.string().required(),
list: yup.string().required(),
})
.required();
const Entities: FC<FormPropTypes> = ({
handleClick,
selectedType,
handleSearch,
}) => {
const theme = useTheme();
const methods = useForm({
resolver: yupResolver(schema),
});
const { handleSubmit, control } = methods;
const onSubmit = (data) => {
// Backend is not done yet
console.log('DataData', data);
};
return (
<FormProvider {...methods}>
<form
onSubmit={(e) => {
e.preventDefault();
console.log('skdjskd', 'Line no 44');
handleSubmit(onSubmit);
}}
>
<Grid container spacing={'16px'}>
{formTypes.map((type) => (
<Grid item xs={6} sm={'auto'} md={'auto'} key={type}>
<CheckBoxButton
key={type}
name={'type'}
value={type}
handleClick={handleClick}
active={type == selectedType ? true : false}
/>
</Grid>
))}
</Grid>
<Grid container pt={4} columnSpacing="20px" rowSpacing={'16px'}>
<Grid item xs={12} sm={12} md={6}>
<Controller
name="name"
render={({
// eslint-disable-next-line #typescript-eslint/no-unused-vars
field: { value, ...otherFieldProps },
fieldState,
}) => (
<TextField
fullWidth
id="sanctions-form-name"
label="Name"
variant="outlined"
helperText={
<p
style={{
position: 'relative',
left: -13,
fontSize: 12,
}}
>
Try to enter the full name or part of it. It is possible
to use original language or the Latin alphabet.
</p>
}
required
error={!!fieldState.error}
{...otherFieldProps}
/>
)}
/>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<WSSelectBox
id="sanctions-form-residential-country"
label="Residential country"
name={'residentialCountry'}
data={['Ten', 'Twenty']}
handleSelect={() => {}}
type={'text'}
control={control}
/>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<WSSelectBox
data={['Ten', 'Twenty']}
handleSelect={() => {}}
id="sanctions-form-initiator"
name={'initiator'}
label="Initiator"
type="text"
control={control}
/>
</Grid>
<Grid item xs={12} sm={6} md={6}>
<WSSelectBox
id="sanctions-form-program"
label="Program"
data={['Ten', 'Twenty']}
handleSelect={() => {}}
type="text"
name={'program'}
control={control}
/>
</Grid>
<Grid item xs={12} sm={6} md={6}>
<WSSelectBox
id="sanctions-form-list"
label="List"
data={['Ten', 'Twenty']}
handleSelect={() => {}}
type={'text'}
name={'list'}
control={control}
/>
</Grid>
</Grid>
<Grid
container
pt={{ xs: '20px', md: '30px' }}
rowSpacing="10px"
alignItems="center"
sx={{
[isMobile(theme)]: {
textAlign: 'center',
},
}}
justifyContent="left"
>
<Grid item xs={6} sm={'auto'}>
<Button
variant="contained"
color="primary"
sx={{
minWidth: '200px',
['#media (max-width:460px)']: {
minWidth: '120px',
},
}}
type="submit"
>
Search
</Button>
</Grid>
<Grid item xs={6} sm={'auto'}>
<Button
sx={{
minWidth: '200px',
color: '#3883FA',
['#media (max-width:460px)']: {
minWidth: '120px',
},
}}
type="submit"
>
Reset
</Button>
</Grid>
</Grid>
</form>
<Typography
variant="body2"
sx={{ fontSize: 17, color: '#121E3D', marginTop: '20px' }}
>
Use filters to start your search.
</Typography>
</FormProvider>
);
};
export default Entities;
SELECT BOX
import { TextField, MenuItem, styled, Select } from '#mui/material';
import { Controller, useForm } from 'react-hook-form';
export type SelectBoxProps = {
label: string;
id: string;
data: Array<string>;
handleSelect: VoidFunction;
type: string;
Control: () => {};
};
const SelectBox = styled(TextField)`
& .MuiOutlinedInput-root:focus {
&.Mui-focused fieldset {
border-bottom: none !important;
}
}
`;
const WSSelectBox = ({
label,
id,
data,
handleSelect,
name,
type,
control,
}) => {
return (
<>
<Controller
name={name}
render={({ field }) => (
<SelectBox
defaultValue=""
fullWidth
autoComplete="off"
id={id}
type={type}
label={label}
variant="outlined"
required
select
{...field}
>
{data.map((opt) => (
<MenuItem key={opt} value={opt}>
{opt}
</MenuItem>
))}
</SelectBox>
)}
control={control}
/>
</>
);
};
export default WSSelectBox;
I'm passing JSON data pulled off a REST API via props to the component below. Since I don't pre-set a value to the "Pick a team" select field, it outputs the following warning:
MUI: You have provided an out-of-range value undefined for the select component.
Consider providing a value that matches one of the available options or ''.
which is normal, because there isn't a selected value.
How can I add skeleton effect to the form below until the props data is loaded?
<Skeleton variant="text" />
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={210} height={118} />
FormOne.tsx
import {
Box,
Button,
FormControl,
Grid,
InputLabel,
MenuItem,
Select,
SelectChangeEvent,
Typography,
} from '#mui/material';
import { useState } from 'react';
import { TeamSeason as Team } from './lib/interfaces/TeamSeason';
const FormOne = (props: any) => {
const [selectedTeam, setSelectedTeam] = useState<Team | null>(null);
const [selectedQuery, setSelectedQuery] = useState<number>(1);
const handleQueryChange = (event: SelectChangeEvent) => {
setSelectedQuery(Number(event.target.value));
};
const handleTeamChange = (event: SelectChangeEvent) => {
const team = props.data.find(
(team: Team) => team.statId === Number(event.target.value)
);
setSelectedTeam(team);
};
return (
<>
<Box mb={2}>
<Typography component="h1" variant="h4" align="center">
GUI #1
</Typography>
</Box>
<Box component="form" noValidate autoComplete="off">
<Grid container spacing={2}>
<Grid item xs={12}>
<FormControl variant="outlined" fullWidth>
<InputLabel id="team-label">Pick a team</InputLabel>
<Select
labelId="team-label"
id="team"
value={selectedTeam?.id?.toString()}
onChange={handleTeamChange}
>
{props.data.map((team: Team) => {
return (
<MenuItem key={team.statId} value={team.statId.toString()}>
{team.name} ({team.team})
</MenuItem>
);
})}
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl variant="outlined" fullWidth>
<InputLabel id="query-label">Query</InputLabel>
<Select
labelId="query-label"
id="query"
value={selectedQuery.toString()}
onChange={handleQueryChange}
>
<MenuItem value={1}>What name does he use?</MenuItem>
<MenuItem value={2}>What abbreviature does he have?</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<Button variant="contained">Submit</Button>
</Grid>
<Grid item xs={12}>
{selectedTeam && selectedQuery === 1 && (
<p>team name: {`${selectedTeam?.name}`}</p>
)}
{selectedTeam && selectedQuery === 2 && (
<p>team abbreviature: {`${selectedTeam?.team}`}</p>
)}
</Grid>
</Grid>
</Box>
</>
);
};
export default FormOne;
index.tsx
import { useState, useEffect } from 'react';
import type { NextPage } from 'next';
import Container from '#mui/material/Container';
import Box from '#mui/material/Box';
import { DataGrid, GridColDef } from '#mui/x-data-grid';
import { Grid, Paper } from '#mui/material';
import Skeleton from '#mui/material/Skeleton';
import { blue } from '#mui/material/colors';
import FormOne from './../src/FormOne';
const LoadingSkeleton = () => (
<Box
sx={{
height: 'max-content',
}}
>
{[...Array(10)].map((_, index) => (
<Skeleton variant="rectangular" sx={{ my: 4, mx: 1 }} key={index} />
))}
</Box>
);
const columns: GridColDef[] = [
{ field: 'statId', headerName: 'Stat ID' },
{ field: 'name', headerName: 'Name', width: 200 },
{ field: 'season', headerName: 'Season' },
];
const Home: NextPage = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setInterval(
() =>
fetch('https://localhost:7000/TeamSeason/2021')
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
}),
3000
);
}, []);
return (
<Container
maxWidth={false}
sx={{
height: '100vh',
overflow: 'auto',
background: `linear-gradient(to right, ${blue[200]}, ${blue[500]})`,
}}
>
<Container maxWidth="lg" sx={{ mt: 3, mb: 3 }}>
<Grid container spacing={{ xs: 2, md: 3 }}>
<Grid item xs={12} md={6}>
<Paper sx={{ padding: 3 }}>
<FormOne data={data} />
</Paper>
</Grid>
<Grid item xs={12} md={6}>
<Paper sx={{ padding: 3 }}></Paper>
</Grid>
<Grid item xs={12}>
<Paper sx={{ padding: 3 }}>
<DataGrid
getRowId={(row) => row.statId}
sx={{ height: '650px' }} // either autoHeight or this
rows={data}
columns={columns}
pageSize={10}
// autoHeight
rowsPerPageOptions={[10]}
disableSelectionOnClick
disableColumnMenu
disableColumnSelector
components={{
LoadingOverlay: LoadingSkeleton,
}}
loading={loading}
/>
</Paper>
</Grid>
</Grid>
</Container>
</Container>
);
};
export default Home;
CodeSandbox
Have created a button called image upload, need to view the selected image when selected
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
/>
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
you can set the file object URL in the state during the onChange and use it in an img tag like this,
const [file, setFile] = useState(undefined);
const handleChange = (event) => {
setFile(URL.createObjectURL(event.target.files[0]));
}
use this handleChange method in input onChange event,
<GridItem xs={6}>
<FormControl className={classes.formControl}>
<input
accept="image/*"
className={classes.input}
style={{ display: 'none' }}
id="raised-button-file"
multiple
type="file"
onChange={handleChange}
/>
{/* preview of file */}
{ file && <img src={this.state.file}/>}
<label htmlFor="raised-button-file">
<Button
variant="raised"
component="span"
className={classes.button}
>
Upload Image
</Button>
</label>
</FormControl>
</GridItem>
also if you want to allow the user to crop the image selected there is this library react-image-crop. I use it and it works pretty good for me.
before select
select image
Typescript
import { Grid, IconButton, makeStyles, Tooltip } from '#material-ui/core'
import { PhotoCamera, Publish } from "#material-ui/icons"
import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
import { IRequestState } from '../../assets/interfaces'
const FILE = 'ImageUpload.tsx'
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(1),
},
}, right: {
float: "right"
}, w100: {
width: "100%"
}, input: {
display: "none",
},
faceImage: {
color: theme.palette.primary.light,
}, success: {
color: theme.palette.success.main
}
}))
interface IImageUploadProps {
Submit(event: React.MouseEvent<HTMLButtonElement>): void
}
type IImageUploadState = IRequestState & {
imageFile?: any
}
export default function ImageUpload(props: IImageUploadProps) {
const classes = useStyles()
const dispatch = useDispatch()
const initState: IImageUploadState = { inited: false, requesting: false }
const [values, setState] = useState<IImageUploadState>(initState)
const handleCapture = ({ target }: any) => {
if (target && target.files && target.files.length)
setState({ ...values, imageFile: target.files[0] })
}
return (
<>
<Grid
container
direction="row"
justify="space-evenly"
alignItems="center"
spacing={2}
>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<input
accept="image/jpeg"
className={classes.input}
id="faceImage"
type="file"
onChange={handleCapture}
/>
<Tooltip title="select image">
<label htmlFor="faceImage">
<IconButton
className={classes.faceImage}
color="primary"
aria-label="upload picture"
component="span"
>
<PhotoCamera fontSize="large" />
</IconButton>
</label>
</Tooltip>
</Grid>
</Grid>
</Grid>
<Grid item sm={6} md={3}>
<img className={classes.w100} hidden={!values.imageFile} src={values.imageFile && URL.createObjectURL(values.imageFile)} alt="Logo" />
</Grid>
<Grid item sm={6} md={3}>
<Grid
justify="center"
alignItems="center"
container
>
<Grid item>
<Tooltip title="upload image">
<IconButton
className={classes.success}
aria-label="upload"
onClick={props.Submit}
edge="end"
disabled={!values.imageFile}
>
<Publish />
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Grid>
</Grid>
</>
)
}
I need to transfer filterArray data to component TableGenerator from the component Content. They are both child components of App.js
I have tried to transfer data from component Content.js to component TableGenerator.js, they are both child components of App.js. You can see what I did here:
class App extends Component {
updateData = (value) => {
this.setState({filterArray: value})
}
render() {
return (
<div className="App">
<Header/>
<br></br>
<Content updateData={this.updateData}/>
<br></br>
<TableGenerator data={this.state.filterArray}/>
</div>
);
}
function TableGenerator() {
const allUsers = [
{пользователь: "Александра"},
{пользователь: "Шура"},
{пользователь: "Настя"},
{пользователь: "Ира"},
{пользователь: "Ваня"},
{пользователь: "Жора"},
{пользователь: "Гриша"}
];
return (
<Paper style={{maxWidth: 936, marginLeft: '250px'}}>
<Table>
<TableHead>
<TableRow>
{Object.keys(allUsers[0]).map((TableRow, i) => (
<TableCell key={i} align="center">{TableRow.split("_").join(" ")}</TableCell>
))}
</TableRow>
</TableHead>
<p>Props: {this.props.filterArray}</p>
<TableBody>
{allUsers.map((user, i) => (
<TableRow key={i}>
{Object.values(user).map((v, j) => (
<TableCell key={j} align="center">{v}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</Paper>
);
class Content extends React.Component {
constructor(props) {
super(props);
// initial state
this.state = {
textInput: '',
filterArray: []
}
}
clear = () => {
// return the state to initial
this.setState({
textInput: ''
})
}
parseInput = () => {
let tempArray = this.state.textInput.split(" ");// `convert string into array`
this.state.filterArray = tempArray.filter(word => word.endsWith('*'));
}
render() {
return (
<Paper style={{maxWidth: 936, marginLeft: '250px', overflow: 'hidden'}}>
<AppBar position="static" color="default" elevation={0}>
<Toolbar>
<Grid container spacing={16} alignItems="center">
<Grid item xs>
<TextField
fullWidth
placeholder="Введите фразу которую нужно обучить"
id='textInput'
InputProps={{
disableUnderline: true,
}}
value={this.state.textInput}
onChange={(e) => {
this.setState({textInput: e.target.value})
}}
/>
</Grid>
<Grid item>
<Button
variant="contained"
color="primary"
style={{background: 'linear-gradient(45deg, #00ACD3 30%, #00BE68 90%)'}}
onClick={this.parseInput()}
>
Обучить
</Button>
<Button
variant="contained"
color="primary"
style={{background: 'linear-gradient(45deg, #00ACD3 30%, #00BE68 90%)'}}
onClick={() => {
this.props.updateData(this.state.filterArray)
}}
>
Генерировать
</Button>
<Tooltip title="Сбросить">
<IconButton>
<RefreshIcon color="inherit" style={{display: 'block'}} id='clearButton'
onClick={this.clear}/>
</IconButton>
</Tooltip>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Paper>
);
}
Two errors (1) parseInput() in Content.js, update state using setState() (2) Your props name is data in TableGenerator but you are accessing this.props.filterArray, it should be this.props.datai have created a sandbox as per you code. It is passing data from Content.js to TableGenerator.js.
parseInput = () => {
let tempArray = this.state.textInput.split(" "); // `convert string into array`
let filterArray = tempArray.filter(word => word.endsWith("*"));
this.setState({ filterArray });
};