I have a project and in this project I have several interfaces and among these interfaces there is an interface to create an index and I have three fields, the first is the name and the second is the type and I have 20 types, and I want when the type is 11 or 14 to show me a third field which is “RefId”:
import React from 'react';
import FormElement from '../../../../common/form-element';
import { IFormProps } from '../../../../../interfaces/form-props';
import { Col, Row } from 'antd';
import Index from '../../../../../api/nuclearMedicineApi/services/Index';
import { useQuery } from 'react-query';
import { useIntl } from 'react-intl';
interface IndexFormProps extends IFormProps { }
const IndexForm: React.FC<IndexFormProps> = ({
control,
disabled,
type,
data
}) => {
// console.log('data: ', data);
// Get All types
const getAllIndexTypesQuery = useQuery('getAllIndexTypes', () =>
Index.indexGetAllTypes(),
);
const getIndexGetAllLite = useQuery('getIndexGetAllLite', () =>
Index.indexGetAllLite({ Type: data?.type?.value}),
);
const sharedProps = {
control,
disabled,
};
const { formatMessage } = useIntl();
return (
<div className='permissions p-8'>
<Row>
<Col span={24}>
<FormElement
{...sharedProps}
label={'indexName'}
type='input'
name={'name'}
required
/>
</Col>
<Col span={24}>
<FormElement
{...sharedProps}
label={'indexType'}
type='select'
name={'type'}
options={
getAllIndexTypesQuery.data?.map((op) => ({
...op,
label: formatMessage({ id: op?.label }),
}))
}
required
/>
</Col>
{
// (data?.type?.value === 13 || data?.type?.value === 14 || data?.type?.label === 'SubTopography' || data?.type?.label === 'SubCity') ?
(type=== 'Create' && data?.type?.value === 13 || data?.type?.value === 14) ?
<Col span={24}>
<FormElement
{...sharedProps}
label={'refId'}
type='select'
name={'refId'}
options={
getIndexGetAllLite.data?.items?.map((op) => ({
...op,
label: formatMessage({ id: op.label }),
}))
}
required
/>
</Col>: ''
}
{type !== 'Create' && (
<>
<Col span={24} className={"m-8"}>
<FormElement
{...sharedProps}
label={'isActive'}
type='checkbox'
name='isActive'
disabled
/>
</Col>
</>
)}
</Row>
</div>
);
};
export default IndexForm;
you could hande this with an onChange event and a state like "hasExtraField" to render extra field.
const Page = () => {
const [hasExtraField, setHasExtraField] = useState(false);
const handleOnChange = (id) => {
if(id === 14 || id === 11) {
setHasExtraField(true);
}
}
return (
<>
{...yourOtherFields}
{hasExtraField && <input name="extraField" />}
</>
)
}
Related
I have been working on refactoring code for a crypto trading platform - changing the class based components to functional components.
The main aim is to display every info of selected coin pair of binance.com using websocket.
If coin pair is changed by selecting option, we have to close old socket and create new socket connection, at first the plan was to implement this in TradeCoinSelection.tsx, but there is a part that is getting coin pair list and calling function of 'parent' (index.tsx) with changed info.
What is happening is the socket thread is not killed, even though there is close() function, the close() function is not finding the old socket thread because ws is resetting in re-rendering.
This means the front end seems to be alternating between new and old pair data as the old pair socket thread is not closed properly.
Any help is appreciated.
Functional component code - TradeCoinSection.tsx
import { useState, useEffect } from 'react';
import Select, { Option } from 'rc-select';
import { Row, Col, Card } from 'reactstrap';
import TradeApi from '#api/TradeApi';
import { IExchangeListAPIResponse } from '#api/types/exchange';
import { connect } from 'react-redux';
import { AppDispatch, RootState } from '#redux/store';
import { fetchExchangeCoinListAction } from '#redux/actions/Exchange';
import { ICoinSymbolResponse } from '#api/types/trade';
import ReconnectingWebSocket from 'reconnecting-websocket';
function TradeCoinSection (props: any) {
const [disabled] = useState<boolean>(false);
const [symbolLoader, setSymbolLoader] = useState<boolean>(false);
const [symbolList, setSymbolList] = useState<Array<ICoinSymbolResponse>>(props.exchangeCoinList);
const [binanceTickerData, setBinanceTickerData] = useState<Record<string, string>>({
h: "",
c: "",
b: "",
p: "",
l: "",
v: "",
q: "",
});
const [didLoadSymbolList] = useState<boolean>(false);
let ws : ReconnectingWebSocket = new ReconnectingWebSocket('wss://stream.binance.com:9443/ws/bnbusdt#ticker');
ws.close();
const closeAndOpenTickerStream = (symbol: string) => {
if(symbol != "") {
ws.close()
ws = new ReconnectingWebSocket('wss://stream.binance.com:9443/ws/'+symbol.toLowerCase()+'#ticker');
ws.addEventListener('open', () => {
// console.log('connected');
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
ws.onmessage = (evt: any) => {
// listen to data sent from the websocket server
const message = JSON.parse(evt.data);
setBinanceTickerData(message);
};
ws.addEventListener('close', () => {
// console.log('disconnected');
});
}
};
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const onSymbolChangeHandler = (value: string, option: any) => {
let baseAssest = '';
let quoteAssest = '';
if (value) {
const splitArray = option.key.split('-');
if (splitArray.length > 1) {
baseAssest = splitArray[0];
quoteAssest = splitArray[1];
}
}
console.log("1,", value);
props.symbolChangeHandler(value, quoteAssest, baseAssest);
closeAndOpenTickerStream(value);
};
const fetchCoinPairList = async () => {
try {
setSymbolLoader(true);
// setErrorList([]);
console.log("extra-1")
if(props.selectedExchangeAccountSite != '') {
const response = await TradeApi.getCoinPair(props.selectedExchangeAccountSite);
// const response = await TradeApi.getCoinPair();
setSymbolList(response.data);
props.fetchExchangeCoinListAction(response.data);
let symbolObj = response.data[0];
if (props.selectedSymbol) {
response.data.find((obj) => {
if (obj.symbol === props.selectedSymbol) {
symbolObj = obj;
}
});
}
onSymbolChangeHandler(symbolObj.symbol, {
key: symbolObj.symbol_pair,
value: symbolObj.symbol,
});
}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
} catch (err: any) {
} finally {
setSymbolLoader(false);
console.log(symbolLoader)
}
};
useEffect(() => {
if (props.exchangeCoinList && props.exchangeCoinList.length < 1) {
fetchCoinPairList();
}
return () => {
ws.close();
}
}, []);
useEffect(() => {
if(props.selectedExchangeAccountSite !== '' && !didLoadSymbolList) {
fetchCoinPairList();
}
if (
props.exchangeCoinList &&
props.exchangeCoinList.length > 0 &&
!props.selectedSymbol_state
) {
setSymbolList(props.exchangeCoinList);
let symbolObj = props.exchangeCoinList[0];
if (props.selectedSymbol) {
props.exchangeCoinList.find((obj: any) => {
if (obj.symbol === props.selectedSymbol) {
symbolObj = obj;
}
});
}
onSymbolChangeHandler(symbolObj.symbol, {
key: symbolObj.symbol_pair,
value: symbolObj.symbol,
});
}
}, [props.selectedExchangeAccount]);
const temp_disabled = disabled;
return (
<Card className="shadow-xl p-3">
<Row className="less-gutters align-items-start">
<Col xl={2} lg={12} md="12">
<Row className="less-gutters flex-column flex-sm-row flex-xl-column">
{!props.dropdownDisabled && (
<Col sm={6} md={6} lg={6} xl={12}>
<Select
placeholder="Select account"
style={{ width: '100%' }}
value={props.selectedExchangeAccount}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
onChange={(exchangeId: number, options: any) =>
props.exchangeAccountChangeHandler(exchangeId, options.label, options.site)
}
className="mb-2"
>
{props.exchangeList.length > 0
? props.exchangeList.map((obj: IExchangeListAPIResponse) => (
<Option key={obj.id} value={obj.id} label={obj.account_name} site={obj.exchange}>
<b>{obj.account_name}</b>
</Option>
))
: null}
</Select>
</Col>
)}
<Col sm={6} md={6} lg={6} xl={12}>
<Select
showSearch={true}
disabled={
props.dropdownDisabled ? props.dropdownDisabled : temp_disabled
}
placeholder="Select Symbol"
style={{ width: '100%' }}
value={props.selectedSymbol}
onChange={onSymbolChangeHandler}
className="currency-dropdown"
>
{symbolList.length > 0
? symbolList.map((obj: ICoinSymbolResponse) => (
<Option value={obj.symbol} key={obj.symbol_pair}>
<b>{obj.symbol_pair}</b>
</Option>
))
: null}
</Select>
</Col>
</Row>
</Col>
<Col xl={10} lg={12} md="12">
<div className="d-flex w-100 market-data-container mt-md-3 mt-lg-3 mt-xl-0">
<div className="w-100 d-flex align-items-start mt-3 mt-md-0 sm:flex-col">
<div className="last-price-block col-md-4 col-lg-3 pl-0 pl-md-0 pl-lg-3 pr-3">
<div className="market-data-last-price">
{binanceTickerData.h &&
parseFloat(binanceTickerData.c).toFixed(8)}
</div>
<div className="market-data-worth">
{binanceTickerData.b &&
`${parseFloat(binanceTickerData.b).toFixed(8)}`}
</div>
</div>
<div className="market-data d-flex flex-wrap w-100 justify-content-start pl-0 pl-md-3 mt-3 mt-md-0">
<div className="market-data-block w-33 mb-md-3 mb-lg-2">
<div className="market-data-block-title">24h Change</div>
<div className="market-data-block-value">
{binanceTickerData.p &&
parseFloat(binanceTickerData.p).toFixed(2)}{' '}
{binanceTickerData.p}%
</div>
</div>
<div className="market-data-block w-33 mb-md-3 mb-lg-2">
<div className="market-data-block-title">24h High</div>
<div className="market-data-block-value">
{binanceTickerData.h &&
parseFloat(binanceTickerData.h).toFixed(8)}
</div>
</div>
<div className="market-data-block w-33 mb-md-3 mb-lg-2">
<div className="market-data-block-title">24h Low</div>
<div className="market-data-block-value">
{binanceTickerData.l &&
parseFloat(binanceTickerData.l).toFixed(8)}
</div>
</div>
<div className="market-data-block w-33 mb-md-3 mb-lg-0">
<div className="market-data-block-title">
24h Volume({props.quoteAssest})
</div>
<div className="market-data-block-value">
{binanceTickerData.v &&
parseFloat(binanceTickerData.v).toFixed(2)}
</div>
</div>
<div className="market-data-block w-33 mb-md-0 mb-lg-0">
<div className="market-data-block-title">
24h Volume({props.baseAssest})
</div>
<div className="market-data-block-value">
{binanceTickerData.q &&
parseFloat(binanceTickerData.q).toFixed(2)}
</div>
</div>
</div>
</div>
</div>
</Col>
</Row>
</Card>
);
}
const mapStateToProps = (state: RootState, props: any) => ({
exchangeCoinList: state.exchange.exchangeCoinList,
ownProps: props
});
const mapDispatchToProps = (dispatch: AppDispatch) => ({
fetchExchangeCoinListAction: (data: Array<ICoinSymbolResponse>) =>
dispatch(fetchExchangeCoinListAction(data)),
});
// export default TradeCoinSection;
export default connect(mapStateToProps, mapDispatchToProps)(TradeCoinSection);
Parent (Index.tsx)
import { useState, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'reactstrap';
import { BarChart2 } from 'react-feather';
import { connect } from 'react-redux';
// Import Api and constants;
import TradeApi from '#api/TradeApi';
import AppConfig from '#config/config';
import { getCatchError } from '#helpers/ErrorHandler';
import { RootState } from '#redux/store';
import TradeCoinSection from '#pages/Admin/Trade/Trade/partials/TradeCoinSection';
import TradeChartSection from '#pages/Admin/Trade/Trade/partials/TradeChartSection';
import OrderHistorySection from '#pages/Admin/Trade/OrderHistory/partials/OrderHistorySection';
import OpenOrderSection from '#pages/Admin/Trade/OpenOrder/partials/OpenOrderSection';
import PageTitle from '#components/ui/PageTitle/PageTitle';
import TradeAddSection from '#pages/Admin/Trade/Trade/partials/TradeAddSection';
import EmptyDataComponent from '#components/EmptyDataComponent';
import LoaderComponent from '#components/LoaderComponent';
import ExchangeModal from '../../Settings/partials/ExchangeModal';
function TradePage (props: any) {
const [ symbolPriceObj, setSymbolPriceObj ] = useState({
symbol: '',
price: '',
status: '',
filter_data: null,
symbol_info: null,
} as any);
const [symbolDetailLoader, setSymbolDetailLoader] = useState<boolean>(false);
const [isAddExchangeModalOpen, setIsAddExchangeModalOpen] = useState<boolean>(false);
const [selectedExchangeAccount, setSelectedExchangeAccount] = useState<number>(0);
const [selectedExchangeAccountLabel, setSelectedExchangeAccountLabel] = useState<string>('');
const [selectedExchangeAccountSite, setSelectedExchangeAccountSite] = useState<string>('');
const [selectedSymbol, setSelectedSymbol] = useState<string>('');
const [baseAssest, setBaseAssest] = useState<string>('');
const [quoteAssest, setQuoteAssest] = useState<string>('');
const [errorList, setErrorList] = useState<Array<string>>([]);
const [lastRecordInsertedAt, setLastRecordInsertedAt] = useState<number>(0);
const postOrderHandler = () => {
setLastRecordInsertedAt(Date.now());
}
const addExchangeModalHandler = (isAdded?: boolean) => {
setIsAddExchangeModalOpen(!isAddExchangeModalOpen);
if (isAdded) {
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
const exchangeAccountChangeHandler = (exchangeId: number, exchangeLabel: string, exchangeSite: string) => {
setSelectedExchangeAccount(exchangeId);
setSelectedExchangeAccountLabel(exchangeLabel);
setSelectedExchangeAccountSite(exchangeSite);
}
const symbolChangeHandler = (symbol: string, quoteAssest: string, baseAssest: string) => {
setSelectedSymbol(symbol);
setQuoteAssest(quoteAssest);
setBaseAssest(baseAssest);
}
const fetchSymbolDetailHandler = async () => {
try {
setSymbolDetailLoader(true);
setErrorList([]);
const response = await TradeApi.getCoinPairPrice(selectedExchangeAccount, selectedSymbol);
const coinObj = {
filter_data: response.data.filter_data,
symbol_info: response.data.symbol_info,
price: response.data.price,
symbol: '',
status: '',
};
setSymbolPriceObj(coinObj);
} catch (error: any) {
console.log(symbolDetailLoader, errorList);
setSymbolDetailLoader(false);
setErrorList(getCatchError(error));
} finally {
setSymbolDetailLoader(false);
}
}
useEffect(() => {
if(selectedSymbol != '')
fetchSymbolDetailHandler();
}, [selectedSymbol])
useEffect(() => {
document.title = `${AppConfig.REACT_APP_NAME} | Trade`;
if (props.exchangeList && props.exchangeList.length > 0) {
const exchangeList = props.exchangeList;
setSelectedExchangeAccount(exchangeList[0].id);
setSelectedExchangeAccountLabel(exchangeList[0].account_name);
setSelectedExchangeAccountSite(exchangeList[0].exchange);
postOrderHandler();
}
}, []);
useEffect(() => {
if (props.exchangeList.length > 0) {
const exchangeList = props.exchangeList;
setSelectedExchangeAccount(exchangeList[0].id);
setSelectedExchangeAccountLabel(exchangeList[0].account_name);
setSelectedExchangeAccountSite(exchangeList[0].exchange);
postOrderHandler();
}
}, [props]);
useEffect(() => {
postOrderHandler();
}, [selectedExchangeAccount])
return (
<>
<PageTitle
titleHeading="Trade"
titleDescription="Buy and sell all of your currencies"
pageTitleIconBox={true}
pageTitleIcon={<BarChart2 className="display-2 text-primary" />}
/>
{props.exchangeLoader && <LoaderComponent isSmallLoader={true} />}
{!props.exchangeLoader &&
props.exchangeList &&
props.exchangeList.length > 0 && (
<>
<Row className="less-gutters buysell">
<Col xl={3} lg={4} md={5} className="mb-4 mb-md-0 pl-2 pr-2">
<TradeAddSection
selectedSymbol={selectedSymbol}
selectedExchangeAccount={selectedExchangeAccount}
selectedExchangeSite={selectedExchangeAccountSite}
symbolPriceObj={symbolPriceObj}
quoteAssest={quoteAssest}
baseAssest={baseAssest}
selectedAccountLabel={selectedExchangeAccountLabel}
postOrderHandler={postOrderHandler}
/>
</Col>
<Col xl={9} lg={8} md={7}>
<TradeCoinSection
selectedSymbol={selectedSymbol}
exchangeList={props.exchangeList}
selectedExchangeAccount={selectedExchangeAccount}
selectedExchangeAccountSite={selectedExchangeAccountSite}
exchangeAccountChangeHandler={exchangeAccountChangeHandler}
symbolChangeHandler={symbolChangeHandler}
quoteAssest={quoteAssest}
baseAssest={baseAssest}
/>
{selectedSymbol && (
<TradeChartSection selectedSymbol={selectedSymbol} />
)}
</Col>
</Row>
{selectedExchangeAccount && (
<OpenOrderSection
selectedExchangeAccount={selectedExchangeAccount}
lastRecordInsertedAt={lastRecordInsertedAt}
selectedSymbol={selectedSymbol}
isTradePage={true}
/>
)}
{selectedExchangeAccount && (
<OrderHistorySection
selectedExchangeAccount={selectedExchangeAccount}
lastRecordInsertedAt={lastRecordInsertedAt}
selectedSymbol={selectedSymbol}
isTradePage={true}
/>
)}
</>
)}
{!props.exchangeLoader &&
props.exchangeList &&
props.exchangeList.length < 1 && (
<Row className="less-gutters buysell">
<Col xl={12} lg={12} md={12}>
<EmptyDataComponent
message="No exchange connected. Please connect an exchange first"
buttonText="Connect Exchange"
buttonClickHandler={addExchangeModalHandler}
showExchangeLinkSection={true}
/>
<ExchangeModal
isOpen={isAddExchangeModalOpen}
toggler={addExchangeModalHandler}
/>
</Col>
</Row>
)}
</>
);
}
const mapStateToProps = (state: RootState, props: any) => ({
exchangeList: state.exchange.exchangeList,
exchangeLoader: state.exchange.exchangeListStatusObj.loading,
ownProps: props
});
export default withRouter(connect(mapStateToProps, {})(TradePage));
In my simple crud application, when I try to add a new author with an invalid name format and try to submit form display an error and after that when I have press backspace twice to erase last letter in textbox.
Here is my AuthorForm.tsx
import React, {useEffect, useState} from 'react';
import {Row, Col, Form, Button} from 'react-bootstrap';
import {IAuthor} from "../../assets/types/LibraryTypes";
import {XCircle} from "react-feather";
import {useForm} from "react-hook-form";
interface IFormInputs {
authorName: string
}
type CreateFormProps = {
onClose: () => void,
onAuthorAdded: (author:IAuthor)=>void,
onAuthorToUpdate: IAuthor | null,
updateAuthorIndex : number | null,
onAuthorUpdated : (updatedAuthor:IAuthor, index:number)=>void,
}
const AuthorForm:React.FC<CreateFormProps> = (props) =>{
const [authorName, setAuthorName] = useState<string|null>(null);
const { register, errors, handleSubmit } = useForm<IFormInputs>({mode:'onTouched'});
useEffect(()=>{
if(!props.onAuthorToUpdate){
setAuthorName(null);
return;
}
setAuthorName(props.onAuthorToUpdate.name);
},[props.onAuthorToUpdate]);
const handleOnNameChange = (event:React.ChangeEvent<HTMLInputElement>)=>{
event.preventDefault();
setAuthorName(event.target.value);
}
const handleOnCreate = () =>{
if(!authorName){
return;
}
if(props.onAuthorToUpdate && props.updateAuthorIndex !== null){
props.onAuthorUpdated({...props.onAuthorToUpdate,name:authorName},props.updateAuthorIndex);
setAuthorName(null);
return;
}
const newAuthor: IAuthor = {name:authorName};
props.onAuthorAdded(newAuthor);
setAuthorName(null);
};
return(
<Col className='p-0' sm={10}>
<Row className=' pb-1 mb-3 mx-1'>
<Col xs={10}>
<span className='add-book-title pt-2'>
{!props.onAuthorToUpdate && 'Create Author'}
{props.onAuthorToUpdate && 'Update Author'}
</span>
</Col>
<Col className='closeBtn text-right p-0' xs={2}>
<XCircle color='#363636' className='mt-2 mr-3' onClick={props.onClose}/>
</Col>
</Row>
<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
<Form.Group>
<Form.Row>
<Form.Label column="sm" xs={6} className='label'>
Name of the Author
</Form.Label>
<Col xs={6} className='warning text-right mt-2 pr-2'>
{errors.authorName?.type === "required" && (
<p>This field is required</p>
)}
{errors.authorName?.type === "maxLength" && (
<p>Author Name name cannot exceed 50 characters</p>
)}
{errors.authorName?.type === "pattern" && (
<p>Invalid Author Name</p>
)}
</Col>
<Col sm={12}>
<Form.Control size={"sm"}
name="authorName"
ref={register({
required: true,
maxLength: 50,
pattern: /^[a-zA-Z\s]+$/
})}
onChange={
(event:React.ChangeEvent<HTMLInputElement>)=>
handleOnNameChange(event)
}
value={authorName?authorName:''}
/>
</Col>
</Form.Row>
</Form.Group>
<Col className='text-right mb-3 p-0' xs={12}>
<Button type={"submit"} variant={"primary"} size={"sm"} className={"px-3 pt-1"}>
{!props.onAuthorToUpdate && 'Create'}
{props.onAuthorToUpdate && 'Update'}
</Button>
</Col>
</Form>
</Col>
)
};
export default AuthorForm;
And this is AuthorList.tsx
import React, {useEffect, useState} from 'react';
import {Container} from 'react-bootstrap';
import AuthorAddedList from "./AuthorAddedList";
import AuthorForm from "./AuthorForm";
import AuthorWelcome from "./AuthorWelcome";
import CreateAuthor from "./CreateAuthor";
import {IAuthor} from "../../assets/types/LibraryTypes";
const AuthorList:React.FC = () =>{
const initAuthors: IAuthor[] = [];
const [authors, setAuthors] = useState<IAuthor[]>(initAuthors);
const [isFormVisible, setIsFormVisible] = useState<boolean>(false);
const [authorToUpdate, setAuthorToUpdate] = useState<IAuthor | null>(null);
const [updateAuthorIndex, setUpdateAuthorIndex] = useState<number| null>(null)
useEffect(()=>{
if(!authorToUpdate){
return;
}
setIsFormVisible(true);
},[authorToUpdate]);
const handleOnCreateClick = () => {
setIsFormVisible(true);
setAuthorToUpdate(null);
};
const handleOnFormClosed = () => {
setIsFormVisible(false);
}
const handleAuthorAdded = (newAuthor: IAuthor) => {
const allAuthors: IAuthor[] = authors.slice();
allAuthors.push(newAuthor)
setAuthors(allAuthors);
};
const handleAuthorDeleted = (index: number) => {
const allAuthors: IAuthor[] = authors.slice();
allAuthors.splice(index, 1);
setAuthors(allAuthors);
}
const handleOnUpdateRequest = (index: number) => {
setAuthorToUpdate(authors[index]);
setUpdateAuthorIndex(index);
setIsFormVisible(true);
}
const handleOnAuthorUpdated = (updatedAuthor: IAuthor, index:number) =>{
const allAuthors : IAuthor [] = authors.slice();
allAuthors.splice(index,1, updatedAuthor);
setAuthors(allAuthors)
}
return (
<Container fluid={true} className={"authors"}>
<AuthorWelcome/>
<AuthorAddedList authors={authors} onDeleted={handleAuthorDeleted} onUpdateRequested={handleOnUpdateRequest} />
<CreateAuthor onClickCreate={handleOnCreateClick}/>
{isFormVisible &&
<AuthorForm onClose={handleOnFormClosed} onAuthorAdded={handleAuthorAdded} onAuthorToUpdate={authorToUpdate} onAuthorUpdated={handleOnAuthorUpdated} updateAuthorIndex={updateAuthorIndex}/>}
</Container>
)
}
export default AuthorList;
Here is the sandbox link to my full code Click Here
to demonstrate the error,
go to the sandbox link
click add author button in webapp
enter an invalid name like 'john97'
then submit the form
then try to clear the name using backspace.
now you can see to erase last letter 'j' you have to press backspace twice
please help me to solve this issue
thank you
I think using Controller component of react-hook-form is better at handling controlled components, you also don't have to set onChange event and make your code much cleaner.
Using this in AuthorForm.tsx seems to make your weird bug fixed.
type FormData = {
authorName: string;
}
//some codes...
const { register, handleSubmit, control, errors, setValue, reset } = useForm<FormData>();
//some codes...
const handleOnCreate = (data: FormData) => {
if (!data?.authorName) {
return;
}
if (props.onAuthorToUpdate && props.updateAuthorIndex !== null) {
props.onAuthorUpdated(
{ ...props.onAuthorToUpdate, name: data.authorName },
props.updateAuthorIndex
);
reset({ authorName: "" }); // or setValue("authorName", "");
return;
}
const newAuthor: IAuthor = { name: data.authorName };
props.onAuthorAdded(newAuthor);
reset({ authorName: "" }); // or setValue("authorName", "");
};
//some codes...
<Controller
control={control}
name={"authorName"}
as={<Form.Control size={"sm"} />}
defaultValue=""
rules={{
required: true,
maxLength: 50,
pattern: /^[A-Za-z ]+$/i
}}
/>
Here is the sandbox.
the problem is in the AuthorForm.tsx file, in this line:
<Form className='mx-4' onSubmit={handleSubmit(handleOnCreate)}>
the onSubmit shouldn't accept an invoked function handleSubmit(handleOnCreate),
it should be changed to onSubmit={() => handleSubmit(handleOnCreate)}
I have an issue, I need to show and hide DropDown/Selects field based on Checkbox's state. Means when checkbox state is checked I need to show Dropdown and when I uncheck it should hide. I have attached a code of this component below. As you can see I have 3 Dropdowns created already, but I need to hide/show them by checking/unchecking one single checkbox
import React, { useState, useEffect } from "react";
import { Button } from "../../components/Button";
import { Checkbox } from "../../components/Checkbox";
import { FormBuilder } from "../../components/FormBuilder";
import { Grid, GridCell, GridRow } from "../../components/Grid";
import { LinearProgress } from "../../components/LinearProgress";
import { Select } from "../../components/Select";
import { TextField } from "../../components/TextField";
import { Name } from "../../models/Name";
import { Option } from "../../models/Option";
import { DynamicForm } from "../../models/DynamicForm";
import "./index.css";
import { Organization } from "../../models/Organization";
import { request } from "../../api";
import { serialize } from "class-transformer";
import { CodingTemplate } from "../../models/CodingTemplate";
import { useHistory } from "react-router-dom";
import { useRef } from "react";
const NUMBER_OF_STEPS = 3;
const screen = {
first: 1,
second: 2,
third: 3,
};
const FormDetails = ({ dynamicForm }: { dynamicForm: DynamicForm }) => {
const check1 = useRef();
return (
<div>
<h4 style={{ margin: 16 }}>Данные формы</h4>
<Grid>
<GridRow>
<GridCell span={5}>
<Checkbox
label="CHECKBOX"
onChange={(value) => (dynamicForm.needExtraApprove = value)}
defaultChecked={dynamicForm.needExtraApprove}
/>
</GridCell>
</GridRow>
<GridRow>
<GridCell span={4}>
<Select<Option>
required
//defaultValue={dynamicForm.departmentHash}
label="Select to show/hide"
type={Option}
//api="/state_schedule/departments-with-journals/"
defaultOptions={defaultOptions}
//onChange={(value: Option) => dynamicForm.departmentHash = value}
/>
}
</GridCell>
<GridCell span={4}>
<Select<Option>
required
//defaultValue={dynamicForm.departmentHash}
label="Select to show/hide"
type={Option}
//api="/state_schedule/departments-with-journals/"
defaultOptions={defaultOptions}
//onChange={(value: Option) => dynamicForm.departmentHash = value}
/>
</GridCell>
<GridCell span={4}>
<Select<Option>
required
//defaultValue={dynamicForm.departmentHash}
label="Select to show/hide"
type={Option}
//api="/state_schedule/departments-with-journals/"
defaultOptions={defaultOptions}
//onChange={(value: Option) => dynamicForm.departmentHash = value}
/>
</GridCell>
</GridRow>
</Grid>
</div>
);
};
const FormFields = ({ dynamicForm }: { dynamicForm: DynamicForm }) => (
<div>
<h4 style={{ margin: 16 }}>ASD</h4>
<FormBuilder dynamicForm={dynamicForm} />
</div>
);
export default () => {
const [step, setStep] = useState(1);
const [dynamicForm, setDynamicForm] = useState<DynamicForm>(
new DynamicForm()
);
let progress = parseFloat(((step - 1) * (1 / NUMBER_OF_STEPS)).toFixed(2));
const onBackButtonPress = () => {
if (step > 0) {
setStep((prev) => prev - 1);
}
};
const onNextButtonPress = () => {
event.preventDefault();
if (step < screen.third) {
setStep((prev) => prev + 1);
} else {
console.log("submit!");
//submit
}
// if (step < screen.third) {
// setStep((prev) => prev + 1);
// } else {
// console.log("submit!");
// //submit
// }
};
const history = useHistory();
const onSubmitTest = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
try {
console.log(dynamicForm);
await request("/service-desk/forms/", {
method: "POST",
body: serialize(dynamicForm),
});
history.push({
pathname: "/service-desk/application-form",
});
} catch (error) {
console.log(error);
alert("ERROR");
}
};
let content = <SubDivision dynamicForm={dynamicForm} />;
let nextBtnLabel = "NEXT";
if (step === screen.second) {
content = <FormDetails dynamicForm={dynamicForm} />;
} else if (step === screen.third) {
content = <FormFields dynamicForm={dynamicForm} />;
}
return (
<form onSubmit={onSubmitTest} noValidate={step === screen.third}>
<LinearProgress progress={progress} determinate />
{content}
<div className="request-btn-container">
<Button
label="BACK"
disabled={step == 1}
onClick={onBackButtonPress}
/>
{step === 3 ? (
<Button label="SAVE" raised type="submit" />
) : (
<Button
label="NEXT"
disabled={step == 3}
onClick={onNextButtonPress}
/>
)}
</div>
</form>
);
};
. Thank you for any tip or advice!
Let me know if this helps.
const FormDetails = ({ dynamicForm }: { dynamicForm: DynamicForm }) => {
const check1 = useRef();
return (
<div>
<h4 style={{ margin: 16 }}>Данные формы</h4>
<Grid>
<GridRow>
<GridCell span={5}>
<Checkbox
label="CHECKBOX"
onChange={(value) => (dynamicForm.needExtraApprove = value)}
defaultChecked={dynamicForm.needExtraApprove}
/>
</GridCell>
</GridRow>
{dynamicForm.needExtraApprove ? (
<GridRow>
<GridCell span={4}>
// put your select here
</GridCell>
<GridCell span={4}>
// put your select here
</GridCell>
<GridCell span={4}>
// put your select here
</GridCell>
</GridRow>
): null}
</Grid>
</div>
);
};
Hello everyone :D I need your advise/tip. Right now I have a APIDataTable component. It has its rows, columns and etc. This component is responsible to show/build data table on frontend with search bar in it above the table. I have an search bar, which is not functional right now. I want it to search data from data table. What should I start from? How can i make it perform search in Table. Thank you for any advise and tip <3
import React, { useEffect, useState } from "react";
import { plainToClassFromExist } from "class-transformer";
import { Pagination } from "../../models/Pagination";
import {
DataTable,
DataTableHead,
DataTableHeadCell,
DataTableBody,
DataTableRow,
DataTableCell,
} from "../DataTable";
import { request } from "../../api";
import "./index.css";
import { MenuSurface } from "../MenuSurface";
import { IconButton } from "../IconButton";
import { Checkbox } from "../Checkbox";
import { Dialog } from "../Dialog";
import { GridCell, GridRow } from "../Grid";
import { Button } from "../Button";
export class Column<T> {
label: string;
width?: number;
filter?: JSX.Element;
render: (row: T) => JSX.Element | string;
constructor(column: Partial<Column<T>>) {
Object.assign(this, column);
}
}
type APIDataTableProps<T> = {
apiPath?: string;
params?: string;
columns?: Column<T>[];
type: Function;
onRowClick?: (row: T) => void;
};
export const APIDataTable = <T extends object>({
apiPath,
params,
columns,
type,
onRowClick,
}: APIDataTableProps<T>) => {
const [data, setData] = useState<Pagination<T>>(null);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(15);
const [isLoading, setIsLoading] = useState(false);
const [isDialogOpen, setDialogOpen] = useState(false);
const [isMenuSurFaceOpen, setMenuSurfaceOpen] = useState(false);
const [hiddenColumns, setHiddenColumns] = useState<number[]>(
JSON.parse(localStorage.getItem(`hiddenColumns-${apiPath + params}`)) || []
);
const fetchData = async () => {
const urlSearchParams = new URLSearchParams(params);
urlSearchParams.set("page", page.toString());
urlSearchParams.set("page_size", pageSize.toString());
const url = `${apiPath}?${urlSearchParams}`;
const response = await request(url);
const data = plainToClassFromExist(new Pagination<T>(type), response, {
excludeExtraneousValues: true,
});
setData(data);
setIsLoading(false);
};
useEffect(() => {
if (!!apiPath) {
setIsLoading(true);
fetchData();
}
}, [page, pageSize]);
const headCells = columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => (
<DataTableHeadCell key={column.label} width={column.width}>
{column.label}
</DataTableHeadCell>
));
const rows = data?.results?.map((row, index) => (
<DataTableRow
key={"row-" + index}
onClick={() => !!onRowClick && onRowClick(row)}
>
{columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => {
return (
<DataTableCell key={column.label} width={column.width}>
<div className="data-table-cell-text">{column.render(row)}</div>
</DataTableCell>
);
})}
</DataTableRow>
));
let uncheckedCheckboxes = hiddenColumns;
const onCheckboxChange = (index: number, value: boolean) => {
if (!value) {
uncheckedCheckboxes.push(index);
//setHiddenColumns(uncheckedCheckboxes);
} else {
const array = [...uncheckedCheckboxes];
array.splice(array.indexOf(index), 1);
uncheckedCheckboxes = array;
}
};
const [isOpen, setIsOpen] = useState(false);
return (
<div className="data-table-container">
<div className="search-test">
<div className="mdc-menu-surface--anchor">
<label
className="mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--with-leading-icon mdc-text-field--with-trailing-icon"
htmlFor="input"
id="search-menu-surface"
>
<IconButton density={-1} icon="search" />
<input
className="mdc-text-field__input "
type="text"
placeholder="Поиск"
id="searchinput"
/>
<IconButton
density={-1}
icon="arrow_drop_down"
onClick={() => {
setMenuSurfaceOpen(true);
}}
/>
</label>
<MenuSurface
isOpen={isMenuSurFaceOpen}
onClose={() => setMenuSurfaceOpen(false)}
fullwidth
>
<div className="data-table-filters-container">
{columns.map(
(column) =>
!!column.filter && (
<div className="data-table-filter">
<div className="data-table-filter-label mdc-typography--subtitle1">
{column.label}
</div>
<div className="data-table-column-filter">
{column.filter}
</div>
</div>
// <GridRow>
// <GridCell span={3}>{column.label}</GridCell>
// <GridCell span={3}>{column.filter}</GridCell>
// </GridRow>
)
)}
{/* <GridCell span={2}> */}
{/* <Button label="Поиск" raised /> */}
{/* <Button
label="Отмена"
raised
onClick={() => {
setIsOpen(false);
}}
/> */}
{/* </GridCell> */}
</div>
</MenuSurface>
</div>
<IconButton
onClick={() => {
setDialogOpen(true);
}}
density={-1}
icon="settings"
/>
<Dialog
isOpen={isDialogOpen}
onOkClick={() => {
localStorage.setItem(
`hiddenColumns-${apiPath + params}`,
JSON.stringify(uncheckedCheckboxes)
);
setDialogOpen(false);
setHiddenColumns(uncheckedCheckboxes);
}}
onCloseClick={() => setDialogOpen(false)}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
{columns.map((column, index) => (
<Checkbox
label={column.label}
onChange={(value) => onCheckboxChange(index, value)}
defaultChecked={!uncheckedCheckboxes.includes(index)}
/>
))}
</div>
</Dialog>
</div>
<DataTable
pagination={true}
count={data?.count}
rowsNumber={data?.results.length}
page={page}
next={data?.next}
previous={data?.previous}
isLoading={isLoading}
onNextClick={() => setPage(page + 1)}
onPreviosClick={() => setPage(page - 1)}
>
<DataTableHead>{headCells}</DataTableHead>
<DataTableBody>{rows}</DataTableBody>
</DataTable>
</div>
);
};
I'm guessing that you want to search bar to effectively filter out rows that don't match. in this case what you want to do is add a filter to the search text (naturally you'll add a state for the search value, but it looks like you'll have that handled.
You'll add your filter here const rows = data?.results?.filter(...).map
You filter function will look something like this
const rows = data?.results.filter((row) => {
// In my own code if I have other filters I just make them return false
// if they don't match
if (
searchText &&
!(
// exact match example
row.field === searchText ||
// case-insensitive example
row.otherField?.toLowerCase().includes(searchText)
// can continue with '||' and matching any other field you want to search by
)
)
return false;
return true;
}).map(...)
I am new to react, I am working on a project. I need little help regarding the filtering of the data. There are two filters one-: State and Location. The location is dependent on State, which is working fine, but after that, I want to filter my card component i.e VideoCategory card basis on above-selected value. There are three levels of filtering and for the third level, the filtering is not working.
Please, anyone, help me out, as I am trying to fix this issue from last two days.
import React, { useState, useEffect } from "react";
import Menu from "../common/Menu";
import { Form, Container, Row, Col } from "react-bootstrap";
import {
getVideosBasedOnCategories,
getAllStates,
} from "./helper/userApiCalls";
import VideoCard from "../common/VideoCard";
const CategoryVideos = ({ match }) => {
const [videoCategory, setVideoCategory] = useState([]);
const [selectState, setSelectState] = useState([]);
const [selectLocation, setSelectLocation] = useState([]);
let location="";
const preload = (categoryId) => {
getVideosBasedOnCategories(categoryId).then((data) => {
if (data.error || !data) {
console.log(data.error);
return <h1>No Data to Show Now</h1>;
} else {
setVideoCategory(...videoCategory, data);
}
});
};
//Intial Loading
useEffect(() => {
preload(match.params.categoryId);
getAllStateForSelect();
}, []);
//getting data for first Select Component from API
const getAllStateForSelect = () => {
getAllStates().then((data) => {
console.log(data);
if (data.error) {
return console.log(data.error);
} else {
setSelectState(...selectState, data);
}
});
};
const handleChange = (event) => {
console.log(event.target.value);
setSelectLocation(event.target.value);
};
const onLocationChange=(event)=>{
location = event
console.log(location)
}
//Storing Location into Option for Second Select
const onSplit = (x) => {
var arr = [];
for (let i = 0; i < x.citynames.length; i++) {
arr.push(
<option key={i} value={x.citynames[i]}>
{x.citynames[i]}
</option>
);
}
return arr;
};
return (
<div>
<Menu />
<Container style={{ marginTop: "200px" }}>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select State</Form.Label>
<Form.Control
as="select"
onChange={handleChange.bind(selectState[0])}
custom
>
{selectState.map((data, index) => (
<option name="setmap" key={index} value={data._id}>
{data.statename}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select Location</Form.Label>
<Form.Control
as="select"
onChange={(e) => onLocationChange(e.target.value)}
custom
>
{selectState
.filter((selectState) => selectState._id.includes(selectLocation))
.map((e) => onSplit(e))}
</Form.Control>
</Form.Group>
</Form>
<Row>
{videoCategory.filter(videocard=>videocard.location.includes(location.toString()))
.map((videocard) => {
return (
<Col lg={4} xs={12} md={12} className="py-3 px-3">
<VideoCard videoCategory={videocard}
/>
</Col>
);
})}
</Row>
</Container>
</div>
);
};
export default CategoryVideos;
VideoCategory File
import React, { Fragment,useEffect,useState } from "react";
import { Card, Button, Container, Row, Col } from "react-bootstrap";
import {getStateById} from '../user/helper/userApiCalls'
const VideoCard = ({ videoCategory }) => {
const [state,setState] = useState("");
const [city,setCity] = useState("");
//const { name, link, description } = videoCategory;
const getStateByFromVideoId = (stateId)=>{
getStateById(stateId).then((data)=>{
console.log(data)
if(data.error){
return console.log(data.error)
}
else{
setState(data.statename)
}
})
}
useEffect(() => {
getStateByFromVideoId(videoCategory.state);
}, []);
return (
<Container fluid>
<iframe
src={videoCategory.link}
width="300px"
height="300px"
id="myId"
className="myClassname"
display="initial"
position="relative"
allowfullscreen="allowfullscreen"
></iframe>
<Card style={{ width: "300px", }}>
<Card.Body>
<Card.Title>{videoCategory.name}</Card.Title>
<Card.Text>{videoCategory.description}</Card.Text>
<Card.Text>{state}</Card.Text>
<Card.Text>{videoCategory.location}</Card.Text>
<Button variant="primary">Go somewhere</Button>
</Card.Body>
</Card>
</Container>
);
};
export default VideoCard;
**UPDATE**
The API structure for State, which will reflect in card, the location is hardcoded, it doesn't have any id.
{
"_id": "5eb59d177693b6f99404f4c6",
"link": "https://www.youtube.com/embed/NEIwl93Yr8o",
"description": "LifeStyle",
"name": "Testing",
"state": "5eb564ec7693b6f99404f4c5",
"category": "5ead7555fb5c440f458e625b",
"location": "Kondapur",
"createdAt": "2020-05-08T17:55:35.731Z",
"updatedAt": "2020-05-08T18:28:43.635Z",
"__v": 0
},
** The Filter of State and location(citynames) is :**
{
"citynames": [
"ABC",
"EFG"
],
"_id": "5eb2ad8b554215be68773cf1",
"statename": "Madras",
"createdAt": "2020-05-06T12:28:59.149Z",
"updatedAt": "2020-05-06T12:28:59.149Z",
"__v": 0
},
Here's i change some code, but there's also some notes i need to confirm first
import React, { useState, useEffect } from "react";
import Menu from "../common/Menu";
import { Form, Container, Row, Col } from "react-bootstrap";
import {
getVideosBasedOnCategories,
getAllStates,
} from "./helper/userApiCalls";
import VideoCard from "../common/VideoCard";
const CategoryVideos = ({ match }) => {
const [videoCategory, setVideoCategory] = useState([]);
const [apiStates, setApiStates] = useState([])
const [selectedState, setSelectedState] = useState(null);
// Change default value to '' i think
const [selectedLocation, setSelectedLocation] = useState(null);
const preload = (categoryId) => {
getVideosBasedOnCategories(categoryId).then((data) => {
if (data.error || !data) {
console.log(data.error);
return <h1>No Data to Show Now</h1>;
} else {
setVideoCategory(...videoCategory, data);
}
});
};
//Intial Loading
useEffect(() => {
preload(match.params.categoryId);
getAllStateForSelect();
}, []);
//getting data for first Select Component from API
const getAllStateForSelect = () => {
getAllStates().then((data) => {
console.log(data);
if (data.error) {
return console.log(data.error);
} else {
setApiStates(...apiStates, data);
// remove this line
// setSelectedLocation(data[0])
}
});
};
const onStateChange = event => {
setSelectedState(event.target.value)
}
const onLocationChange= event => {
setSelectedLocation(event.target.value);
}
//Storing Location into Option for Second Select
const onSplit = (x) => {
var arr = [];
for (let i = 0; i < x.citynames.length; i++) {
arr.push(
<option key={i} value={x.citynames[i]}>
{x.citynames[i]}
</option>
);
}
return arr;
};
return (
<div>
<Menu />
<Container style={{ marginTop: "200px" }}>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select State</Form.Label>
<Form.Control
as="select"
onChange={onStateChange}
custom
>
{apiStates.map((data, index) => (
<option name="setmap" key={index} value={data._id}>
{data.statename}
</option>
))}
</Form.Control>
</Form.Group>
</Form>
<Form>
<Form.Group controlId="exampleForm.SelectCustom">
<Form.Label>Select Location</Form.Label>
<Form.Control
as="select"
onChange={onLocationChange}
custom
>
{apiStates
.filter(apiState => apiState._id === selectedState)
.map(value => onSplit(value))}
</Form.Control>
</Form.Group>
</Form>
<Row>
// I'm curious about the location, is it array of string ?
{videoCategory.filter(videocard => videocard.location === selectedLocation))
.map((videocard) => {
return (
<Col lg={4} xs={12} md={12} className="py-3 px-3">
<VideoCard videoCategory={videocard} />
</Col>
);
})}
</Row>
</Container>
</div>
);
};
export default CategoryVideos;
<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>