React page won't load when fatch data function has 3 items - reactjs

I have this detail page when the detail button is clicked it goes to the detail page. However, sometimes the page won't load. The fatchData function has 3 items and each one calls the webapi to get different dataset. If I remove any of 2 in fatchData function and only leave one in it and it will load fine. However, the rest of the data will be missing and I need it in the page. How do I solve it? I think it has something to do with the useEffect hook that causes the stackover flow.
Here is the error it always hit: throw Error( "Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops." );
Here is the entire page code:
import React, { useEffect, useState } from 'react';
import 'dotenv/config';
import Button from '#mui/material/Button';
import MenuItem from '#mui/material/MenuItem';
import Select from '#mui/material/Select';
import ClearIcon from '#mui/icons-material/Clear';
import ListItem from '#mui/material/ListItem';
import ListItemIcon from '#mui/material/ListItemIcon';
import ListItemButton from '#mui/material/ListItemButton';
import ListItemText from '#mui/material/ListItemText';
import List from '#mui/material/List';
import { useParams } from 'react-router-dom'
// Fields
import TextField from '#mui/material/TextField';
// Table
import scriptResultTableColumns from './../table/ScriptGroupResultTable';
import TableComponent from './../table/TableComponent';
import { InputLabel } from '#mui/material';
import FormControl from '#mui/material/FormControl';
function ScriptGroupResultsTable(props) {
const columns = scriptResultTableColumns();
return (<TableComponent columns={columns} url={`${process.env.REACT_APP_API_URL}/api/script-
group-result/script-group-results-by-group-id?id=` + props.groupid} buttonVisible={false}
tableType={'script-group-detail'} />);
}
export default function ScriptDetailsPage(props) {
const [validationError] = useState(null);
const [item, setItem] = useState([]);
const [scriptList, setScriptList] = useState({ items:[] });
const [selectedScript, setSelectedScript] = useState('');
const [selectedScripts, setSelectedScripts] = useState([]);
let { id } = useParams();
const [scriptDetailId] = useState(id);
const [url, setUrl] = useState(`${process.env.REACT_APP_API_URL}/api/script-group/script-group?id=` + scriptDetailId);
function fetchData() {
const fetchItem =fetch(url).then(response => response.json()).then(json => {
setItem(json);
},
(error) => {
console.log(error);
});
const fetchScriptList = fetch(`${process.env.REACT_APP_API_URL}/api/script/scripts`).then(response => response.json()).then(json => {
setScriptList(json);
},
(error) => {
console.log(error);
});
const fetchSelectedScripts = fetch(`${process.env.REACT_APP_API_URL}/api/script-group-member/script-group-members-by-group-id?id=` + scriptDetailId).then(groupResponse => groupResponse.json()).then(groupJson => {
groupJson = groupJson.map((member, i) => ({ ...member, index: i }));
setSelectedScripts(groupJson);
},
(groupError) => {
console.log(groupError);
setSelectedScripts([]);
});
Promise.all([fetchItem, fetchScriptList, fetchSelectedScripts]);
}
function onCancel(e) {
fetchData();
}
function onSubmit(e) {
const requestOptions = {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(item)
};
var scriptList = [];
for (var i = 0; i < selectedScripts.length; i++) {
scriptList.push(selectedScripts[i].scriptId);
}
const updateMembersOptions = {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(scriptList)
}
fetch(`${process.env.REACT_APP_API_URL}/api/script-group?id=` + scriptDetailId, requestOptions).then(
fetch(`${process.env.REACT_APP_API_URL}/api/script-group/replace-group-scripts?id=` + scriptDetailId, updateMembersOptions)
).then(setUrl(url));
}
function getMaxIndex() {
return Math.max.apply(Math, selectedScripts.map(function (o) { return o.index })) + 1;
}
const handleScriptChange = (event) => {
const {
target: { value },
} = event;
var newScriptVal = { ...value, index: getMaxIndex() };
setSelectedScripts([...selectedScripts, newScriptVal])
setSelectedScript('');
};
function removeScript(e) {
if (e.currentTarget.id) {
var index = parseInt(e.currentTarget.id);
setSelectedScripts(selectedScripts.filter((s) => s.index !== index));
}
}
function getBoolStringVal(value) {
if (value)
return "Yes";
else
return "No";
}
useEffect(fetchData, [url, scriptDetailId]);
return (
<div style={{ marginLeft: '10%', maxWidth: '80%' }}>
<TextField
id="groupName"
label="Group Name"
error={validationError}
margin="normal"
fullWidth
value={item.groupName || ''}
onChange={e => setItem({ ...item, groupName: e.target.value })}
/>
<br />
<TextField
aria-readonly
id="groupState"
label="State"
fullWidth
error={validationError}
margin="normal"
value={item.currentStateDescription || ''}
/>
<br />
<TextField
aria-readonly
id="continueOnFailure"
label="Continues on Failure"
error={validationError}
margin="normal"
value={getBoolStringVal(item.continueOnFailure) || ''}
/>
<br />
<FormControl margin="normal">
<InputLabel id="script-select-label" >Add Script</InputLabel>
<Select
labelId="script-select-label"
label="Add Script"
value={selectedScript}
onChange={handleScriptChange}
style={{ width: '350px' }}
>
{
scriptList.items.map((script) => (
<MenuItem
key={script.scriptId}
value={script}
>
{script.scriptName}
</MenuItem>
))}
</Select>
</FormControl>
<br />
<h4>Current Scripts:</h4>
<List dense style={{ width: '300px' }}>
{selectedScripts.map((script) => (
<ListItem key={script.index}>
<ListItemButton>
<ListItemIcon>
<ClearIcon id={script.index} onClick={removeScript} />
</ListItemIcon>
<ListItemText primary={script.scriptName} />
</ListItemButton>
</ListItem>
))}
</List>
<Button variant='outlined' aria-label='Update' style={{ margin: 10, float: 'right' }} onClick={onSubmit}>Update</Button>
<Button variant='outlined' aria-label='Cancel' style={{ margin: 10, float: 'right' }} onClick={onCancel}>Cancel</Button>
<ScriptGroupResultsTable style={{ marginTop: '100px' }} groupid={scriptDetailId} />
</div>
);
}

Related

useQuery in apollo client returns undefined

I'm using apollo client in my Reactjs application along with Redux. I'm fetching client details from mongodb by authenticating via JWT token. For this I have used useLazy Query. When user starts typing in the autocomplete box, There would be an authetication going on behind the screen and fetches the client details once the authentication is successful. However, I have a filter condition ,in which, I want to load the countries without any event so I used useQuery. But the results are not fetching in the beginning itself. It works when I use "useQuery" as well as "useLazyQuery" for the same (FIND_COUNTRY)
import * as React from "react"
import TextField from "#mui/material/TextField"
import Autocomplete from "#mui/material/Autocomplete"
import parse from "autosuggest-highlight/parse"
import match from "autosuggest-highlight/match"
import { FIND_CLIENT_NAME, FIND_COUNTRY,CLIENT_AUTOCOMPLETE } from "../graphql-operations"
import { useLazyQuery, useQuery } from "#apollo/client"
import Button from "#mui/material/Button"
import SearchIcon from "#mui/icons-material/Search"
import FilterAltTwoToneIcon from "#mui/icons-material/FilterAltTwoTone"
import { Grid, Stack } from "#mui/material"
import { Container } from "#mui/system"
import AccordionDetails from "#mui/material/AccordionDetails"
import Chip from "#mui/material/Chip"
import { useDispatch } from "react-redux"
import { useMsal } from "#azure/msal-react"
import { loginRequest } from "../authConfig"
import { fetchUsersToken } from "../userSlice"
import { useEffect } from "react"
import ClientListing from "./ClientListing"
import { store } from "../store"
import { useState } from "react"
//Country Filter ------------------------------------------
function Filter(country, setCountry, countryArray, setSearchResults) {
return (
<center>
<AccordionDetails sx={{ maxWidth: 800, height: "auto", minWidth: 240 }}>
<div>
<Autocomplete
data-testid="filter"
size="small"
multiple
id="fixed-tags-demo"
freeSolo
value={country}
onChange={(event, newValue) => {
setCountry([...newValue])
setSearchResults(false)
}}
options={countryArray}
getOptionLabel={option => option.country}
renderTags={(tagValue, getTagProps) =>
tagValue.map((option, index) => (
<Chip
size="small"
key={option.countryCode}
label={option.country}
{...getTagProps({ index })}
/>
))
}
style={{ maxwidth: 350, margin: 0, minWidth: 80 }}
renderInput={params => (
<TextField
data-testid="filter-text"
{...params}
label="Country"
variant="filled"
placeholder="Add Country"
/>
)}
/>
</div>
</AccordionDetails>
</center>
)
}
//------------------------------------------------------
export default function LandingPage() {
const [clients, setClients] = React.useState([]) //stores data for autocomplete search from useLazyQuery.
const [filterFlag, setFilterFlag] = React.useState(false)// flag for filter open/close.
const [inputValue, setInputValue] = React.useState("")// stores input value for client search autocomplete.
const [country, setCountry] = React.useState([])//stores country chosen by user.
const [searchResults, setSearchResults] = React.useState(false)//Flag to enable submit after autocomplete onChange complete.
const [SearchInput, setSearchInput] = React.useState()//setting client search value for pagination on submit.
const [SearchCountry, setSearchCountry] = React.useState([])//setting country filter search value for pagination on submit.
const [renderPagination, setRenderPagination] = React.useState(false)//Flag to render pagination table and disable banner image.
//useQuery to load country values for filter country autocomplete----------------------------------------
  var countryArray = []
  var sorted = []
  const { loading: loadingCountry, data: countryData } = useQuery(FIND_COUNTRY)
  if (loadingCountry) <p>Loading..</p>
  if (countryData != undefined) {
    sorted = [...countryData.getCountries.data].sort((a, b) =>
    {
      if(a.country !== null)
        return a.country.localeCompare(b.country)}
    )
    sorted.map(countryData => {
      if (countryData.country) countryArray.push(countryData)
    })
console.log("Fetched Countried from UseQuery \n ", countryData);
  }
//Redux begins here to acquire token from reducer
const { accounts } = useMsal();
const request = {
...loginRequest,
account: accounts[0]
}
store.dispatch(fetchUsersToken(request))
//Redux ends here
//useLazyQuery to load client Name values for client search autocomplete----------------------------------------
var tempStorage = []
const [clientSearch, { data, loading: loadingQuery }] = useLazyQuery(CLIENT_AUTOCOMPLETE)
async function handleSearch(inputValue) {
//Dispatch action to get access token and store it in RTK (Redux-ToolKit)
try {
if (inputValue) {
const input = inputValue
const clientData = await clientSearch({
variables: { input },
})
clientData.data.autocompleteClient.data.map(({ name }) => {
tempStorage.push({ name })
})
setClients([...tempStorage])
}
} catch (error) {
console.log(error)
}
}
const [getCountryData, { loading, dataTask }] = useLazyQuery(FIND_COUNTRY);
async function filterOpenClose() {
getCountryData().then((fetchedData) => {
sorted = [...fetchedData.data.getCountries.data].sort((a, b) =>
{
if(a.country !== null)
return a.country.localeCompare(b.country)}
)
sorted.map(countryData => {
if (countryData.country) countryArray.push(countryData)
})
})
setFilterFlag(!filterFlag)
setSearchResults(false)
}
const handleSubmit = () => {
if (inputValue) {
setSearchResults(true)
setRenderPagination(true)
setSearchInput(inputValue)
setSearchCountry(country)
}
}
return (
<>
{!renderPagination && <div className="banner-image" role="image" />}
<div>
<br />
<center>
<Container>
<Grid
container
spacing={2}
direction="row"
justifyContent="center"
alignItems="center"
>
<Grid item xs={12} md={12} lg={9}>
<Autocomplete
data-testid="Autocomplete-search"
size="small"
clearOnBlur={false}
id="highlights-demo"
options={clients}
getOptionLabel={option => option.name}
renderInput={params => (
<TextField
sx={{ maxWidth: 775, marginRight: 0 }}
{...params}
placeholder="Search by Client Name/ Client ID /DUNS Number"
margin="normal"
onChange={event => {
handleSearch(event.target.value)
setInputValue(event.target.value)
setSearchResults(false)
}}
onSelect={val => {
setInputValue(val.target.value)
}}
/>
)}
renderOption={(props, option, { inputValue }) => {
const matches = match(option.name, inputValue, {
insideWords: true,
})
const parts = parse(option.name, matches)
return (
<li {...props}>
<div>
{parts.map((part, index) => (
<span
key={index}
style={{
fontWeight: part.highlight ? 700 : 400,
}}
>
{part.text}
</span>
))}
</div>
</li>
)
}}
/>
</Grid>
<Grid item xs={12} md={6} lg={3}>
<Stack direction="row" spacing={1}>
<Button
data-testid="button"
variant="contained"
startIcon={<SearchIcon />}
className="primary-btn"
onClick={handleSubmit}
>
Search
</Button>
<Button
data-testid="filter-icon"
variant="contained"
startIcon={<FilterAltTwoToneIcon />}
className="primary-btn"
onClick={filterOpenClose}
>
Filter
</Button>
</Stack>
</Grid>
</Grid>
</Container>
</center>
{/* renders filter component */}
{filterFlag && Filter(country, setCountry, countryArray, setSearchResults)}
{/* renders pagination table component */}
<br></br>
{renderPagination && (
<ClientListing
clientNameInput={SearchInput}
country={SearchCountry}
searchResults={searchResults}
/>
)}
</div>
<br></br>
</>
)
}
graphql_operations.js
import {gql} from '#apollo/client'
export const CLIENT_AUTOCOMPLETE = gql`
query clientAutocomplete($input: String) {
autocompleteClient(input: $input){
data{
name
}
Error{
Message
}
}
}
`;
export const FIND_COUNTRY = gql`
query {
getCountries{
data{
country
countryCode
}
Error{
Message
}
}
}
`;
export const FIND_CLIENT_NAME = gql`
query getclients($input: SearchQuery) {
searchClients(input: $input){
clients{
id
name
dunsNumberCode
dunsNameDesc
ultimateDunsNumber
ultimateDunsDesc
nationalId
clientAddress{
countryName
countryCode
addressLine1Desc
addressLine2Desc
cityName
postalCode
}
clientIndustry{
industrySectorName
}
}
totalDocsReturned
}
}
`;
export const FIND_CLIENT_ENGAGEMENT = gql`
query getclients($input: SearchQuery) {
searchClients(input: $input){
clients{
id
name
dunsNumberCode
dunsNameDesc
ultimateDunsNumber
ultimateDunsDesc
nationalId
clientAddress{
countryName
countryCode
addressLine1Desc
addressLine2Desc
cityName
postalCode
}
clientIndustry{
industrySectorName
}
engagements{
data{
id
name
clientId
clientName
engagementStatusCode
companyName
companyCode
}
Error{
Message}
}
}
totalDocsReturned
Error{
Message
}
}
}
`;
In the filterOpenClose method, I have useLazyQuery for FIND_COUNTRY and in the beginnning of the component I have used useQuery. I'm getting the country details if and only if I keep both the snippet on.If I remove useLazyQuery for FIND_COUNTRY its not giving me the results. Its undefined. Because of redux, this problem occurs?

onRemove Upload Image antDesign + delete item from interface

I have a project, and this project contains several interfaces, and among these interfaces there is an interface for uploading an image, and the problem is in the deletion icon. When you click on it, a modal appears, but the element is deleted before the modal appears.
How can i solve the problem?
this file display a list of instructions that contains upload Image
import '../../../styles/input/index.scss';
import '../../../styles/dropzone/index.scss';
import { Button, Col, message, Modal, Row, Spin, Upload, UploadFile } from 'antd';
import { FunctionComponent, useCallback, useRef, useState } from 'react';
import { motion, useAnimation } from 'framer-motion';
import { defaultTranstion } from '../../../constants/framer';
import { Controller } from 'react-hook-form';
import FromElemnetWrapper from '../form-element-wrapper';
import { Delete, UploadCloud } from 'react-feather';
import { getBase64 } from '../../../utils/get-base64';
import _ from 'lodash';
import config from '../../../api/nuclearMedicineApi/config';
import { FormattedMessage } from 'react-intl';
import BasicModal from '../modal';
import { UploadOutlined } from '#ant-design/icons';
import axios from 'axios';
import { IFormError } from '../general-form-containner';
interface DropzoneProps {
name: string;
control: any;
rules?: any;
label: string;
disabled?: boolean;
multiple?: boolean;
accept?: string;
refType?: number;
defaultFileList?: any;
onRemove?: any;
customRequest?: (option: any) => void;
onProgress?: any;
}
const Dropzone: FunctionComponent<DropzoneProps> = ({
name,
control,
rules,
label,
disabled,
multiple,
accept,
refType,
defaultFileList,
onRemove,
customRequest,
onProgress
}) => {
const focusController = useAnimation();
const errorController = useAnimation();
const [previewVisible, setpreviewVisible] = useState(false);
const [previewImage, setpreviewImage] = useState('');
const handleCancel = () => setpreviewVisible(false);
const handlePreview = async (file: any) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setpreviewImage(file?.preview ?? file.url);
setpreviewVisible(true);
};
const [isModalOpen, setIsModalOpen] = useState(false);
const [errors, setErrors] = useState<IFormError[]>([]);
const [visibleModal, setVisibleModal] = useState(false);
const [removePromise, setRemovePromise] = useState();
const [deleteVisible, setdeleteVisible] = useState<boolean>(false);
const onDeleteHandle = () => {
setdeleteVisible(false);
};
const deletehandleCancel = () => {
setdeleteVisible(false);
};
let resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
// let resolvePromiseRef = useRef<HTMLInputElement | null>(null)
const handleRemove = useCallback(() =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}, [])
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false);
setVisibleModal(false)
}
}, [removePromise]);
return (
<>
<FromElemnetWrapper
focusController={focusController}
errorController={errorController}
label={label}
required={rules.required?.value}
>
<Controller
control={control}
name={name}
rules={rules}
render={({
field: { onChange, onBlur, value, name, ref },
fieldState: { invalid, error },
}) => {
if (invalid) {
errorController.start({ scale: 80 });
} else {
errorController.start(
{ scale: 0 },
{ ease: defaultTranstion.ease.reverse() },
);
}
return (
<div
onBlur={() => {
onBlur();
focusController.start({ scale: 0 });
}}
onFocus={() => {
focusController.start({ scale: 80 });
}}
className='relative'
>
<div className='upload-container'>
<form
className='dropzone needsclick'
id='demo-upload'
action='/upload'
>
{/* <> */}
<Upload
action={`${config.baseUrl}api/services/app/Attachment/Upload`}
headers={config.headers}
ref={ref}
multiple={multiple}
disabled={disabled}
data={{ RefType: refType }}
listType='picture'
fileList={value}
id={name}
accept={accept}
onPreview={handlePreview}
onRemove={handleRemove}
iconRender={
() => {
return <Spin style={{ marginBottom: '12px', paddingBottom: '12px' }}></Spin>
}
}
progress={{
strokeWidth: 3,
strokeColor: {
"0%": "#f0f",
"100%": "#ff0"
},
style: { top: 12 }
}}
beforeUpload={
(file) => {
console.log({ file });
return true
}
}
// onProgress= {(event: any) => (event.loaded / event.total) * 100}
// onChange={(e) =>
// onChange(e.fileList)
// }
// onChange={(response) => {
// console.log('response: ', response);
// if (response.file.status !== 'uploading') {
// console.log(response.file, response.fileList);
// }
// if (response.file.status === 'done') {
// message.success(`${response.file.name}
// file uploaded successfully`);
// } else if (response.file.status === 'error') {
// message.error(`${response.file.name}
// file upload failed.`);
// }
// else if (response.file.status === 'removed') {
// message.error(`${response.file.name}
// file upload removed.`);
// }
// }}
>
<div className='upload-button'>
<div className='wrapper'>
<motion.div
className='fas fa-angle-double-up'
whileHover={{
y: [
0, -2, 2,
0,
],
transition: {
duration: 1.5,
ease: 'easeInOut',
yoyo: Infinity,
},
}}
>
<UploadCloud
style={{
margin: '.2rem',
display:
'inline-block',
}}
color='white'
size={20}
/>
Upload
</motion.div>
</div>
</div>
</Upload>
{/*
<Modal
title="Are you sure?"
visible={visibleModal}
onOk={handleOkModalRemove}
onCancel={handleCancelModalRemove}
/> */}
<BasicModal
header={
<>
<FormattedMessage id={'confirmdeletion'} />
</>
}
headerType='error'
content={
<>
<Row>
<Col span={8} offset={4}>
<Button
type='primary'
className='savebtn'
onClick={onDeleteHandle}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'affirmation'} />
</Button>
</Col>
<Col span={8} offset={4}>
<Button
type='default'
className='savebtn'
onClick={deletehandleCancel}
style={{
cursor:
Object.keys(errors).length !==
0
? 'not-allowed'
: 'pointer',
}}
>
<FormattedMessage id={'cancel'} />
</Button>
</Col>
</Row>
</>
}
isOpen={visibleModal}
footer={false}
width='35vw'
handleCancel={handleCancelModalRemove}
handleOk={handleOkModalRemove}
/>
{/* {_.isEmpty(value) && (
<div className='dz-message needsclick'>
<FormattedMessage id='dropfileshere' />
</div>
)} */}
<BasicModal
isOpen={previewVisible}
header={<FormattedMessage id="Preview image" />}
footer={false}
handleCancel={handleCancel}
content={<img
alt='example'
style={{ width: '100%' }}
src={previewImage}
/>}
/>
{/* </> */}
</form>
</div>
{invalid && (
<p className='form-element-error'>
{error?.message}
</p>
)}
</div>
);
}}
/>
</FromElemnetWrapper>
</>
);
};
export default Dropzone;
Why it doesn't work?
https://ant.design/components/upload
onRemove - A callback function, will be executed when removing file button is clicked, remove event will be prevented when return value is false or a Promise which resolve(false) or reject
You have to return a promise which resolves to false or return false
How to solve it?
You have to return a promise so first of all, you need a ref or a state to store resolve function of that promise so you can call it in modal
const resolvePromiseRef = useRef<((value: boolean) => void) | undefined>();
Then you will need to assign the resolve function to the ref and return the promise
Now onRemove will wait for your promise to resolve
const handleRemove = () =>{
const promise = new Promise(resolve => {
resolvePromiseRef.current = resolve
});
setVisibleModal(true);
return promise;
}
Now your functions handlers
const handleOkModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(true)
}
}, [removePromise]);
const handleCancelModalRemove = useCallback(() => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(false)
}
}, [removePromise]);
You can also use the state, but I recommend ref because it doesn't rerender the component when changed.

MUI Autocomplete with axios call to select or add new option [ codesandbox included ]

I'm trying to make an Autocomplete input for categories from an API response and allow the user to be able to create one if he didn't find
a match.
Issues:
1- How to avoid Non-unique when I have same key which is name can I make on ID cause it's unique?
Warning: Encountered two children with the same key, `Flour & Bread Mixes`. Keys should be unique so that components maintain their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behaviour is unsupported and could change in a future version.
2- The dialog for entring new category doesn't open and I don't see any errors in the console
Code Sandbox
https://codesandbox.io/s/asynchronous-material-demo-forked-70eff?file=/demo.js
import React, {useState} from "react";
import TextField from '#mui/material/TextField';
import Autocomplete , { createFilterOptions } from '#mui/material/Autocomplete';
import CircularProgress from '#mui/material/CircularProgress';
import parse from 'autosuggest-highlight/parse';
import match from 'autosuggest-highlight/match';
import axios from "axios";
import Dialog from '#mui/material/Dialog';
import DialogTitle from '#mui/material/DialogTitle';
import DialogContent from '#mui/material/DialogContent';
import DialogContentText from '#mui/material/DialogContentText';
import DialogActions from '#mui/material/DialogActions';
import Button from '#mui/material/Button';
import { Input } from "#material-ui/core";
export default function Asynchronous() {
const filter = createFilterOptions();
const [open, setOpen] = React.useState(false);
const [options, setOptions] = React.useState([]);
const loading = open && options.length === 0;
const [categories, setCategories] = useState([]);
const [openDialog, toggleOpenDialog] = React.useState(false);
const handleClose = () => {
setDialogValue({
name: '',
slug: '',
image: '',
});
toggleOpenDialog(false);
};
const handleSubmit = (event) => {
event.preventDefault();
setCategories({
name: dialogValue.name,
slug: dialogValue.slug,
image: dialogValue.image
});
handleClose();
};
const [dialogValue, setDialogValue] = React.useState({
name: '',
slug: '',
image: '',
});
React.useEffect(() => {
let active = true;
if (!loading) {
return undefined;
}
(async () => {
var config = {
method: 'get',
url: 'https://null.free.mockoapp.net/getCategories',
};
axios(config)
.then(function (response) {
response.data = response.data.filter((category) => category.name)
if (active) {
setOptions([...response.data]);
}
})
.catch(function (error) {
console.log(error);
});
})();
return () => {
active = false;
};
}, [loading]);
React.useEffect(() => {
if (!open) {
setOptions([]);
}
}, [open]);
return (
<>
<Autocomplete
id="asynchronous-demo"
open={open}
limitTags={3}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
isOptionEqualToValue={(option, value) => option.name === value.name}
getOptionLabel={(option) => {
// Value selected with enter, right from the input
if (typeof option === 'string') {
return option;
}
// Add "xxx" option created dynamically
if (option.inputValue) {
return option.inputValue;
}
// Regular option
return option.name;
}}
options={options}
loading={loading}
multiple
renderInput={(params) => (
<>
<TextField
{...params}
label="Asynchronous"
InputProps={{
...params.InputProps,
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
/>
{console.log(options)}
</>
)}
renderOption={(props, option, { inputValue }) => {
const matches = match(option.name, inputValue);
const parts = parse(option.name, matches);
return (
<li {...props}>
<div>
{parts.map((part, index) => (
<span
key={index}
style={{
color: part.highlight ? "red" : 'inherit',
fontWeight: part.highlight ? 700 : 400,
}}
>
{part.text}
</span>
))}
</div>
</li>
);
}}
value={categories}
onChange={(event, newValue) => {
if (typeof newValue === 'string') {
// timeout to avoid instant validation of the dialog's form.
setTimeout(() => {
toggleOpenDialog(true);
setDialogValue({
name: newValue,
slug: '',
image: ''
});
});
} else if (newValue && newValue.inputValue) {
toggleOpenDialog(true);
setDialogValue({
name: newValue.inputValue,
slug: '',
image: ''
});
} else {
setCategories(newValue);
}
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
const { inputValue } = params;
const isExisting = options.some((option) => inputValue === option.name);
if (inputValue !== '' && !isExisting) {
filtered.push({
inputValue:inputValue,
name: `Add "${inputValue}"`,
});
}
return filtered;
}}
selectOnFocus
clearOnBlur
handleHomeEndKeys
/>
<Dialog open={openDialog} onClose={handleClose}>
<form onSubmit={handleSubmit}>
<DialogTitle>Add a new film</DialogTitle>
<DialogContent>
<DialogContentText>
Did you miss any film in our list? Please, add it!
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="name"
value={dialogValue.name}
onChange={(event) =>
setDialogValue({
...dialogValue,
name: event.target.value,
})
}
label="title"
type="text"
variant="standard"
/>
<TextField
margin="dense"
id="slug"
value={dialogValue.slug}
onChange={(event) =>
setDialogValue({
...dialogValue,
slug: event.target.value,
})
}
label="slug"
type="text"
variant="standard"
/>
<Input
margin="dense"
id="image"
value={dialogValue.image}
onChange={(event) =>
setDialogValue({
...dialogValue,
image: event.target.value,
})
}
label="image"
type="file"
variant="standard"
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button type="submit">Add</Button>
</DialogActions>
</form>
</Dialog>
</>
);
}
I found a lot of mistakes in your code so I made a new working fork on Codesandbox
https://codesandbox.io/s/asynchronous-material-demo-forked-oeg2p?file=/demo.js
Notes:
save the api response in a state instead of doing an API call
whenever the user opens the dropdown.
you could make an API request in handleSubmit function to create a new category in the back-end
handleFormSubmit will output the value of Autocomplete.
Let me know if you have any questions.
Read more about autocomplete at
https://mui.com/components/autocomplete/#asynchronous-requests

multi step form with React , Formik and Material UI, issue with checkboxes and autocomplete

I hope you could help me with this part, so I have no problem with the stepper and no problem with the forms too when it comes to getting data input ​​and validating fields with yup .
but I have a problem with the checkboxes and autocomplete. when I go back to a previous step, I lose the values ​​I have already entered and validation no longer works with checkbox and autocomplete field.
There is an example of a hook that I made for the checkboxes that I will use later in a form :
const CheckBoxField = (props) => {
const { label, ...rest } = props;
const [field, meta, helper] = useField(props);
const { setValue } = helper;
const [touched, error] = at(meta, "touched", "error");
const isError = error && touched && true;
function __renderHelperText() {
if (isError) {
return <FormHelperText>{error}</FormHelperText>;
}
}
function _onChange(event) {
setValue(event.target.checked);
}
const configFormControl = {
...field,
...rest,
onChange: _onChange,
};
return (
<FormControl
component="fieldset"
{...configFormControl}
error={Boolean(isError)}
>
<FormControlLabel
// value={field.checked}
checked={field.checked}
label={label}
onChange={_onChange}
control={
<BpCheckbox
{...configFormControl}
// checked={field.checked}
color="primary"
/>
}
color="primary"
/>
{__renderHelperText()}
</FormControl>
);
};
And there is the code for validation :
import * as yup from "yup";
import signUpFormModel from "../signUpFormModel";
const {
formField: {
terms //Boolean , used in checkboxes, should be true
},
} = signUpFormModel;
const signUpValidationSchema = [
yup.object().shape({
[terms.name]: yup.boolean().oneOf([true], `${terms.requiredErrMsg}`),
//other fields ...
}),
//other forms ...
];
export default signUpValidationSchema;
My Initial value :
import signUpFormModel from "../signUpFormModel";
const {
formField: {
terms,
},
} = signUpFormModel;
const formInitialValue = {
[terms.name]: false,
};
export default formInitialValue;
and some props helper (used as model for my forms)
import React, { Fragment } from "react";
import { Link } from "#mui/material";
const signUpFormModel = {
formId: "registration",
formField: {
terms: {
type: "checkbox",
name: "terms",
id: "terms",
label: () => (
<Fragment>
J'accepte les{" "}
<Link href="#" variant="body2">
termes et conditions générales d'utilisation
</Link>{" "}
et la{" "}
<Link href="#" variant="body2">
politique de confidentialité
</Link>
.
</Fragment>
),
requiredErrMsg: "Vous devez accepter les termes et conditions",
},
},
};
export default signUpFormModel;
Finaly the form itself :
import React from "react";
import { Grid } from "#mui/material";
import CheckBoxField from "../CheckBoxField";
const PrimarySignUpForm = (props) => {
const {
formField: {
terms,
},
} = props;
return (
<Grid container spacing={2}>
<Grid item xs={12}>
<CheckBoxField name={terms.name} label={terms.label()} />
</Grid>
</Grid>
);
};
export default PrimarySignUpForm;
And this is how I made the stepper :
import React, { Fragment, useState } from "react";
import SignUpFormLogs from "../forms/SignUpFormLogs";
import { Formik, Form } from "formik";
import formInitialValuefrom from "../formModel/formInitialValue";
import signUpFormModel from "../formModel/signUpFormModel";
import signUpValidationSchema from "../formModel/signUpValidationSchema";
import {
Button,
Link,
Step,
StepLabel,
Stepper,
Typography,
} from "#mui/material";
import { Box } from "#mui/system";
const steps = ["step1"];
const { formId, formField } = signUpFormModel;
function _renderSteps(step) {
switch (step) {
case "step1":
return <SignUpFormLogs formField={formField} />;
default:
return <div>Not Found</div>;
}
}
function _sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const SignUp = () => {
const [activeStep, setActiveStep] = useState(0);
const currentValidationSchema = signUpValidationSchema[activeStep]; //activeStep
const isLastStep = activeStep === steps.length - 1;
async function _submitForm(values, actions) {
await _sleep(1000);
console.log(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
setActiveStep(activeStep + 1);
}
function _handleSubmit(values, actions) {
if (isLastStep) {
_submitForm(values, actions);
} else {
setActiveStep(activeStep + 1);
actions.setTouched({});
actions.setSubmitting(false);
}
}
function _handleBack() {
setActiveStep(activeStep - 1);
}
return (
<Fragment>
<Stepper activeStep={activeStep} sx={{ pt: 3, pb: 5 }}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
{activeStep === steps.length ? (
<Typography variant="h6">Last step</Typography>
) : (
<Formik
initialValues={formInitialValuefrom}
validationSchema={currentValidationSchema}
onSubmit={_handleSubmit}
>
{({ isSubmitting }) => (
<Form id={formId}>
{_renderSteps(steps[activeStep])}
{activeStep !== 0 && (
<Button
onClick={_handleBack}
disabled={isSubmitting}
variant="outlined"
color="primary"
sx={{ mt: 4, mb: 2 }}
>
Back
</Button>
)}
<Button
type="submit"
disabled={isSubmitting}
variant="contained"
color="primary"
sx={{ mt: 4, mb: 2 }}
>
{isLastStep ? "Enregistrer" : "Suivant"}
</Button>
{isSubmitting && (
<Typography variant="h6">submitting...</Typography>
)}
</Form>
)}
</Formik>
)}
</Fragment>
);
};
export default SignUp;

set function does not trigger an update in React with Recoil

This seems like it should be easy but why isn't a button callback function with a setState call not triggering a refresh of the data item? Actually it's just the computeSMA button that isn't changing the sma when the button is selected. The other two callbacks to set inputs work. The fetchData updates the charts so i can't figure this out!! Must be too tired ...
import React, { useState, useEffect } from "react"
import { useRecoilState } from "recoil";
import { closingDataAtom, metaDataAtom, tickerAtom, timeSeriesAtom , smaAtom} from '../../utils/atoms'
import { Container } from '#material-ui/core'
import '../../utils/Home.css'
import { VictoryChart, VictoryBar, VictoryTheme, VictoryVoronoiContainer, VictoryLine, VictoryBrushContainer, VictoryZoomContainer } from 'victory';
import { Chart, Axis, Tooltip, Line, Point } from "bizcharts";
import {XYPlot, LineSeries} from 'react-vis';
const APIKEY = 'demo'
const Home = () => {
const [apikey, setApiKey] = useState(APIKEY)
const [ticker, setTicker] = useRecoilState(tickerAtom);
const [metadata, setMetaData] = useRecoilState(metaDataAtom)
const [closingdata, setClosingData] = useRecoilState(closingDataAtom)
const [dates, setDates] = useRecoilState(timeSeriesAtom)
const [sma, setSMA] = useRecoilState(smaAtom)
const TIME_RESOLUTION = 'Daily'
var requestUrl
if (TIME_RESOLUTION === 'Daily') {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey
} else {
requestUrl = "https://www.alphavantage.co/query?function=TIME_SERIES_WEEKLY_ADJUSTED&symbol=" + ticker + "&outputsize=full&apikey=" + apikey;
}
const fetchData = async () => {
fetch(requestUrl)
.then(response => response.json())
.then(data => {
var closing_price = []
var metadata = []
var dat = []
Object.keys(data['Time Series (Daily)']).forEach((dateKey) => {
closing_price.push(data['Time Series (Daily)'][dateKey]['5. adjusted close'])
dat.push({ 'date': new Date(dateKey) })
})
Object.keys(data['Meta Data']).forEach((metaKey) => {
metadata.push(data['Meta Data'][metaKey])
})
setDates(dat.reverse())
setClosingData(closing_price.reverse())
setMetaData(metadata)
})
.catch(e => {
});
};
const handleSMACompute = (e) => {
var sm = ['2', '3', '4']
setSMA(sm) <====== REACT IS NOT "REACTING"
}
const handleTickerInput = (e) => {
setTicker(e.target.value)
}
const handleAPIInput = (e) => {
setApiKey(e.target.value)
}
return (
<>
<Container className="container" maxWidth="sm">
<div>
<label>Ticker:</label> {ticker}
<input type="text" name="ticker" onChange={handleTickerInput} />
</div>
<div>
<label>APIKEY:</label> {apikey}
<input type="text" name="apikey" onChange={handleAPIInput} />
</div>
<button onClick={fetchData}>
Click it
</button>
<Container className="container" maxWidth="sm">
<ul>{metadata}</ul>
</Container>
<button OnClick={handleSMACompute}> Generate SMA </button>
<Container className="container" maxWidth="sm">
<ul>The value is {sma}</ul>
</Container><div>
</div>
<VictoryChart
theme={VictoryTheme.material}
domainPadding={10}
>
<VictoryBar
style={{ data: { fill: "#c43a31" } }}
data={closingdata}
/>
</VictoryChart>
<div>
<VictoryChart
theme={VictoryTheme.material}
>
<VictoryLine
style={{
data: { stroke: "#c43a31" },
parent: { border: "1px solid #ccc" }
}}
animate={{
duration: 20,
onLoad: { duration: 20 }
}}
containerComponent={<VictoryZoomContainer zoomDomain={{x: [5, 35], y: [0, 100]}}/>}
categories={{
y: dates
}}
data={closingdata}
/>
</VictoryChart>
</div>
</Container>
</>
);
}```
It seems to have been the button setup. I changed to this and it works....??ggrrrr
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{metadata}</li>
</Container>
<button onClick={computeSMA}>
Click it
</button>
<Container className="container" maxWidth="sm">
<li>{sma}</li>
</Container>
In your first code, you used OnClick as the event name. Should be onClick. It is a react syntax and it is case sensitive.

Resources