I have a SyncFusion grid that is using custom binding and I'm having two issues. Using React v18 with Redux.
When initially requesting the data to populate the grid, it is not showing the loading spinner, even though I have set it up via a side-effect and a Redux state property (isLoading) to do so. Via the console logs I can see that the side-effects are running as intended, but doesn't show spinner.
Once the initial data request comes back and populates the grid the spinner appears and doesn't stop. I believe it has something to do with the row-detail templates that are being added. If I remove the detail template the spinner does not appear. I have added in a hideSpnner to my external columnChooser button, after I click this, everything works normally.
It's not appearing when I want it to, then appearing and not going away.
Once I'm past this initial data request and force the hideSpinner() via the external column chooser button, subsequent data requests work fine when paging and sorting, spinner shows appropriately.
Not sure if there is a community of SyncFusion users here, but hopefully someone can help.
Here is my slice:
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import { DataStateChangeEventArgs } from "#syncfusion/ej2-react-grids";
import { ServiceRequest } from "./models/ServiceRequest.interface";
import { ServiceRequestResult } from "./models/ServiceRequestResult.interface";
import csmService from "./services/csmMyRequestService";
interface AsyncState {
isLoading: boolean;
isSuccess: boolean;
isError: boolean;
}
interface MyRequestState extends AsyncState {
result: ServiceRequest[];
count: number;
}
const initialState: MyRequestState = {
isLoading: false,
isSuccess: false,
isError: false,
result:[],
count: 0
}
export const getMyRequests = createAsyncThunk(
'csm/getMyRequests',
async (gridCriteria: DataStateChangeEventArgs) => {
try {
return await csmService.getMyRequests(gridCriteria);
} catch (error) {
console.log('Error: ', error);
}
});
export const csmMyRequestSlice = createSlice({
name: 'csmMyRequest',
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(getMyRequests.pending, (state) => {
state.isLoading = true;
})
.addCase(getMyRequests.fulfilled, (state, action) => {
state.result = action.payload?.myRequests || [];
state.count = action.payload?.count || 0;
state.isLoading = false;
state.isSuccess = true;
})
.addCase(getMyRequests.rejected, (state) => {
state.result = [];
state.count = 0;
state.isLoading = false;
state.isError = true;
})
},
});
export default csmMyRequestSlice.reducer;
Here is my component:
import { FC, useEffect, useRef, useState } from 'react';
import { Internationalization } from '#syncfusion/ej2-base';
import { ColumnDirective, ColumnsDirective, DataStateChangeEventArgs, Grid, GridComponent } from '#syncfusion/ej2-react-grids';
import { Inject, Page, Sort, Filter, FilterSettingsModel, Resize, ColumnChooser, DetailRow } from '#syncfusion/ej2-react-grids';
import { useAppDispatch, useAppSelector } from '../../../hooks/redux/hooks';
import styles from './MyRequests.component.module.scss';
import { getMyRequests } from '../csmMyRequestSlice';
import { IconButton, styled, Tooltip, tooltipClasses, TooltipProps } from '#mui/material';
import ViewColumnIcon from '#mui/icons-material/ViewColumn';
import { ServiceRequestResult } from '../models/ServiceRequestResult.interface';
let instance = new Internationalization();
const MyRequestsComponent: FC = () => {
const dispatch = useAppDispatch();
const { isLoading, result, count, isSuccess } = useAppSelector((state) => state.csmMyRequestReducer);
let initialMyRequests = { result: [], count: 0 };
const [myRequests, setMyRequests] = useState<ServiceRequestResult>(initialMyRequests);
const pageSettings = {
pageSize: 10,
pageSizes: ["10", "20", "30", "40", "50"]
};
const sortSettings = {
columns: []
};
const columnChooserSettings = {
hideColumns: [
"Contact",
"Request Subtype",
"Reference",
"Sys. Logged Date",
"Sys. Closed Date"
]
};
let myGridInstanceRef: Grid | null;
const format = (value: Date) => {
return instance.formatDate(value, { skeleton: 'yMd', type: 'date' });
};
const dataBound = () => {
}
const dataStateChange = (gridCriteria: DataStateChangeEventArgs) => {
if (myGridInstanceRef && gridCriteria.action) {
const requestType = gridCriteria.action.requestType;
switch (requestType) {
case 'paging':
case 'sorting':
dispatch(getMyRequests(gridCriteria));
break;
}
}
};
const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))({
[`& .${tooltipClasses.tooltip}`]: {
maxWidth: 500,
fontSize: 13,
color: 'white',
},
});
function gridDetailTemplate(props: any) {
return (
<CustomWidthTooltip title={props.Detail}><p className={`${styles['RequestDetailText']}`}>Detail: {' '}{props.Detail}</p></CustomWidthTooltip>
);
}
let template: any = gridDetailTemplate;
const columnChooserClick = (event: React.MouseEvent<HTMLElement>) => {
if (myGridInstanceRef) {
myGridInstanceRef.hideSpinner(); //Forced hide of spinner here
myGridInstanceRef.columnChooserModule.openColumnChooser();
}
};
useEffect(() => {
if (myGridInstanceRef) {
if (isLoading) {
console.log('is Loading show spinner'); //Goes through here but spinner doesn't display
myGridInstanceRef.showSpinner();
} else {
console.log('not Loading hide spinner'); //Who knows if it gets hidden as it never gets displayed
myGridInstanceRef.hideSpinner();
}
}
}, [isLoading])
useEffect(() => {
if (myGridInstanceRef && isSuccess) {
setMyRequests({ result: result, count: count });
}
}, [result, isSuccess])
useEffect(() => {
if (myGridInstanceRef) {
columnChooserSettings.hideColumns.forEach((field) => {
myGridInstanceRef!.hideColumns(field);
});
const gridCriteria = { skip: 0, take: 10 };
dispatch(getMyRequests(gridCriteria));
}
}, [])
return (
<div className={`${styles['RequestSection']}`}>
<legend className={`${styles['RequestLegend']}`}>My Requests:
<Tooltip title="Show/Hide Columns">
<IconButton
className={`${styles['ColumnChooser']}`}
onClick={columnChooserClick}
size="small"
>
<ViewColumnIcon />
</IconButton>
</Tooltip>
</legend>
<div className={`${styles['RequestGridContainer']}`}>
<GridComponent
ref={(g) => (myGridInstanceRef = g)}
dataSource={myRequests}
allowPaging={true} pageSettings={pageSettings}
allowSorting={true} allowMultiSorting={true} sortSettings={sortSettings}
allowResizing={true}
allowReordering={true}
showColumnChooser={true}
detailTemplate={template.bind(this)}
dataBound={dataBound.bind(this)}
dataStateChange={dataStateChange.bind(this)}
height='100%'
>
<ColumnsDirective>
<ColumnDirective field='ServiceRequestTag' headerText='Request #' />
<ColumnDirective field='Caller.Name' headerText='Caller' />
<ColumnDirective field='Source' />
<ColumnDirective field='Contact.ContactName' headerText='Contact' />
<ColumnDirective field='ServiceType.ServiceTypeName' headerText='Service Type' />
<ColumnDirective field='ServiceRequestType.ServiceRequestTypeName' headerText='Request Type' />
<ColumnDirective field='ServiceRequestSubtype.ServiceRequestSubtypeName' headerText='Request Subtype' />
<ColumnDirective field='Poi.Address' headerText='POI Address' />
<ColumnDirective field='Poi.CityTown' headerText='POI City/Town' />
<ColumnDirective field='ReferenceNumbers' headerText='Reference' />
<ColumnDirective field='OwnerName' headerText='Owner' />
<ColumnDirective field='Status.StatusName' headerText='Status' width='100' />
<ColumnDirective field='LoggedByName' headerText='Logged By' />
<ColumnDirective field='LoggedDate' headerText='Logged Date' type='datetime' format='dd MMM yyyy HH:mm' />
<ColumnDirective field='SystemLoggedDate' headerText='Sys. Logged Date' type='datetime' format='dd MMM yyyy HH:mm' />
<ColumnDirective field='ClosedByName' headerText='Closed By' />
<ColumnDirective field='ClosedDate' headerText='Closed Date' type='datetime' format='dd MMM yyyy HH:mm' />
<ColumnDirective field='SystemClosedDate' headerText='Sys. Closed Date' type='datetime' format='dd MMM yyyy HH:mm' />
<ColumnDirective field='DueDate' headerText='Due Date' type='datetime' format='dd MMM yyyy HH:mm' />
</ColumnsDirective>
<Inject services={[Page, Sort, Resize, ColumnChooser, DetailRow]} />
</GridComponent>
</div>
</div>
)
}
export default MyRequestsComponent;
I think the issue is with the myGridInstanceRef variable. It's not a true reference in the React ref sense. It is redeclared each render cycle so likely it has synchronization issues.
let myGridInstanceRef: Grid | null;
This should probably be declared as a React ref so it's a stable reference from render cycle to render cycle.
const myGridInstanceRef = React.useRef<Grid | null>();
Example:
const MyRequestsComponent: FC = () => {
...
const myGridInstanceRef = React.useRef<Grid | null>();
...
const dataStateChange = (gridCriteria: DataStateChangeEventArgs) => {
if (myGridInstanceRef.current && gridCriteria.action) {
const requestType = gridCriteria.action.requestType;
switch (requestType) {
case 'paging':
case 'sorting':
dispatch(getMyRequests(gridCriteria));
break;
}
}
};
...
const columnChooserClick = (event: React.MouseEvent<HTMLElement>) => {
if (myGridInstanceRef.current) {
myGridInstanceRef.current.hideSpinner();
myGridInstanceRef.current.columnChooserModule.openColumnChooser();
}
};
useEffect(() => {
if (myGridInstanceRef.current) {
if (isLoading) {
console.log('is Loading show spinner'); //Goes through here but spinner doesn't display
myGridInstanceRef.current.showSpinner();
} else {
console.log('not Loading hide spinner'); //Who knows if it gets hidden as it never gets displayed
myGridInstanceRef.current.hideSpinner();
}
}
}, [isLoading]);
useEffect(() => {
if (myGridInstanceRef.current && isSuccess) {
setMyRequests({ result: result, count: count });
}
}, [result, isSuccess]);
useEffect(() => {
if (myGridInstanceRef.current) {
columnChooserSettings.hideColumns.forEach((field) => {
myGridInstanceRef.current.hideColumns(field);
});
const gridCriteria = { skip: 0, take: 10 };
dispatch(getMyRequests(gridCriteria));
}
}, []);
return (
<div className={`${styles['RequestSection']}`}>
<legend className={`${styles['RequestLegend']}`}>My Requests:
<Tooltip title="Show/Hide Columns">
<IconButton
className={`${styles['ColumnChooser']}`}
onClick={columnChooserClick}
size="small"
>
<ViewColumnIcon />
</IconButton>
</Tooltip>
</legend>
<div className={`${styles['RequestGridContainer']}`}>
<GridComponent
ref={(g) => {
myGridInstanceRef.current = g;
}}
...
>
...
</GridComponent>
</div>
</div>
)
}
Found out that it was the actual grid causing the issue, while the grid control they provide is able to be used with React, it is not very React-ful when dealing with side-effects, they seem quite locked into their vanilla javascript event handlers and anything outside of that causes issues.
Related
I have a page in react 18 talking to a server which is passing information about how to build specific elements in a dynamic form. I am trying to figure out how to manage state in a case where there are multiple selects/multiselects in the page. Using one hook will not work separately for each dropdown field.
Code is updated with the latest updates. Only having issues with setting default values at this point. Hooks will not initially set values when given.
import { Calendar } from 'primereact/calendar';
import { Dropdown } from 'primereact/dropdown';
import { InputSwitch } from 'primereact/inputswitch';
import { InputText } from 'primereact/inputtext';
import { MultiSelect } from 'primereact/multiselect';
import React, { useEffect, useState, VFC } from 'react';
import { useLocation } from 'react-router-dom';
import { useEffectOnce } from 'usehooks-ts';
import { useAppDispatch, useAppSelector } from 'redux/store';
import Form from '../components/ReportViewForm/Form';
import { getReportParamsAsync, selectReportParams } from '../redux/slice';
export const ReportView: VFC = () => {
const location = useLocation();
const locState = location.state as any;
const dispatch = useAppDispatch();
const reportParams = useAppSelector(selectReportParams);
const fields: JSX.Element[] = [];
const depList: any[] = [];
//const defaultValList: any[] = [];
//dynamically setting state on all dropdown and multiselect fields
const handleDdlVal = (name: string, value: string) => {
depList.forEach((dep) => {
if (name === dep.dependencies[0]) {
dispatch(getReportParamsAsync(currentMenuItem + name + value));
}
});
setState((prev: any) => {
return { ...prev, [name]: value };
});
};
//dynamically setting state on all calendar fields
const handleCalVal = (name: string, value: Date) => {
setState((prev: any) => {
return { ...prev, [name]: value };
});
};
//dynamically setting state on all boolean fields
const handleBoolVal = (name: string, value: boolean) => {
setState((prev: any) => {
return { ...prev, [name]: value };
});
};
/* function getInitVals(values: any) {
const defaultList: any[] = [];
values.forEach((param: any) => {
defaultList.push({ name: param.name, value: param.defaultValues[0] });
});
} */
const [state, setState] = useState<any>({});
const [currentMenuItem, setCurrentMenuItem] = useState(locState.menuItem.id.toString());
useEffectOnce(() => {}), [];
useEffect(() => {
if (reportParams?.length === 0) {
dispatch(getReportParamsAsync(currentMenuItem));
}
//reload hack in order to get page to load correct fields when navigating to another report view
if (currentMenuItem != locState.menuItem.id) {
window.location.reload();
setCurrentMenuItem(locState.menuItem.id.toString());
}
}, [dispatch, reportParams, currentMenuItem, locState, state]);
//dependency list to check for dependent dropdowns, passed to reportddl
reportParams.forEach((parameter: any) => {
if (parameter.dependencies !== null && parameter.dependencies[0] !== 'apu_id') {
depList.push(parameter);
}
});
//filter dispatched data to build correct fields with data attached.
reportParams.forEach((parameter: any, i: number) => {
if (parameter.validValuesQueryBased === true) {
if (parameter.validValues !== null && parameter.multiValue) {
const dataList: any[] = [];
parameter.validValues.map((record: { value: any; label: any }) =>
dataList.push({ id: record.value, desc: record.label }),
);
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<MultiSelect
options={dataList}
name={parameter.name}
value={state[parameter.name]}
onChange={(e) => handleDdlVal(parameter.name, e.value)}
></MultiSelect>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
} else if (parameter.validValues !== null) {
const dataList: any[] = [];
parameter.validValues.map((record: { value: any; label: any }) =>
dataList.push({ id: record.value, desc: record.label }),
);
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<Dropdown
options={dataList}
optionValue='id'
optionLabel='desc'
name={parameter.name}
onChange={(e) => handleDdlVal(parameter.name, e.value)}
value={state[parameter.name]}
//required={parameter.parameterStateName}
placeholder={'Select a Value'}
></Dropdown>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
}
} else if (parameter.parameterTypeName === 'Boolean') {
fields.push(
<span key={i} className='col-12 mx-3 field-checkbox'>
<InputSwitch
checked={state[parameter.name]}
id={parameter.id}
name={parameter.name}
onChange={(e) => handleBoolVal(parameter.name, e.value)}
></InputSwitch>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
} else if (parameter.parameterTypeName === 'DateTime') {
//const date = new Date(parameter.defaultValues[0]);
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<Calendar
value={state[parameter.name]}
name={parameter.name}
onChange={(e) => {
const d: Date = e.value as Date;
handleCalVal(parameter.name, d);
}}
></Calendar>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
} else if (parameter.name === 'apu_id') {
return null;
} else {
fields.push(
<span key={i} className='p-float-label col-12 mx-3 field'>
<InputText name={parameter.name}></InputText>
<label className='mx-3'>{parameter.prompt.substring(0, parameter.prompt.indexOf(':'))}</label>
</span>,
);
}
});
const onSubmit = () => {
console.log(state);
};
return (
<Form onReset={null} onSubmit={onSubmit} initialValues={null} validation={null} key={null}>
{fields}
</Form>
);
};
enter code here
import React, { useState, useEffect, Fragment } from "react";
import Button from "../../../Resources/Forms/Button";
import Switch from "../../../Resources/Forms/Switch";
import { POST } from "../../../Utils/api";
import Radio from "../../../Resources/Forms/Radio";
import withAppData from "../../../HOC/withAppData";
const InventorySettings = (props) => {
const [state, setState] = useState({});
const [isSaving, setIsSaving] = useState();
const [isStockRequestChecked, setIsStockRequestChecked] = useState(false);
const getStatus = id => {
return props.context.isSettingsActivated(id) ? 1 : 0;
};
const setBusinessSettings = async () => {
const defaultSettings = [
{ state: "enableStockRequest", id: 53 },
{ state: "connectWarehousePickStock", id: 52 },
{ state: "approveRequestOtp", id: 51 },
{ state: "approveRequestManually", id: 50 }
];
for (const setting of defaultSettings) {
await setState({ [setting.state]: getStatus(setting.id) });
}
};
function chooseApprovalMethod(methodType) {
const currentValue = state[methodType];
setState({[methodType]
: currentValue === 1 ? 0: 1})
}
async function saveApprovalMethod() {
setIsSaving(true)
const approvalSettings = [{text:"approvalRequestManually", id: 51}, {text:"approveRequestOtp", id: 50}]
for(const el of approvalSettings) {
const currentValue = state[el.text];
const data = {
settingId: el.id,
status: currentValue
}
await POST(`Common/AddBusinessSetting`, data);
}
setIsSaving(false);
props.context.getBusinessSettings();
}
const updateBasicSettings = async (id, key) => {
setState({ [key]: !state[key] ? 1 : 0 });
const data = {
SettingId: id,
Status: state[key],
};
await POST(`Common/AddBusinessSetting`, data);
props.context.getBusinessSettings();
};
useEffect(() => {
setBusinessSettings();
}, []);
return (
<Fragment>
<div className="basic-settings-section">
<Switch
label={"Connect Warehouse Stock to pick stock"}
light={true}
checked={state && state.connectWarehousePickStock === 1}
onChange={() => updateBasicSettings(52, "connectWarehousePickStock")}
></Switch>
</div>
<div className="basic-settings-section">
<Switch
label={"Stock Request"}
light={true}
checked={isStockRequestChecked}
onChange={() => setIsStockRequestChecked(!isStockRequestChecked)}
></Switch>
{isStockRequestChecked && (
<div className="basic-settings-plan-generate">
<div
className="form__label"
style={{ padding: "2px", marginBottom: "20px" }}
>
<p>Please choose an approval method</p>
</div>
<Radio
label={"Manual Approval"}
name="approval"
value="50"
id="50"
checked={state && state.approveRequestManually === 1}
// onChange={() => (chooseApprovalMethod)}
/>
<Radio
label={"OTP Approval"}
name="approval"
value="51"
id="51"
checked={state && state.approveRequestOtp === 1}
// onChange={() => (chooseApprovalMethod)}
/>
<div className="password-settings-btn"
// onClick={props.context.showToast}
>
<Button
type={"outline"}
size={"medium"}
text={"Save"}
disabled={!state.approveRequestOtp && !state.approveRequestManually}
withMargin={false}
loading={isSaving}
onClick={saveApprovalMethod}
></Button>
</div>
</div>
)}
</div>
</Fragment>
);
}
export default withAppData(InventorySettings);
I added the chooseApprovalMethod function to the radio buttons but still I wasn't getting it well. So I had to call there state using state.text is equal to 1. Please help me out I don't think I know what I'm doing anymore.
Please above are my code, the radio buttons aren't checking or highlighting, so I want them to be checked when clicked on, and I want there ids to be saved when clicking on the save button.
So please guys help me out, as I don't understand it anymore.
I'm trying to show confirmation dialogue on saving in react admin framework but saving functionality started breaking.
Error
--> Converting circular structure to JSON
--> starting at object with constructor 'FiberNode'
| property 'stateNode' -> object with constructor 'HTMLButtonElement'
--- property '__reactInternalInstance$mtamow8fbfp' closes the circle
The dataProvider threw an error. It should return a rejected Promise instead.
I suspect its redirection issue but couldn't figure out.
It works if i don't use Confirmation Dialog and call handleSave in onSave prop of SaveButton
React admin version - 3.4.2
Is this the correct way to do it? please help
Confirm.tsx
/**
* Confirmation dialog
*
* #example
* <Confirm
* isOpen={true}
* title="Delete Item"
* content="Are you sure you want to delete this item?"
* confirm="Yes"
* confirmColor="primary"
* ConfirmIcon=ActionCheck
* CancelIcon=AlertError
* cancel="Cancel"
* onConfirm={() => { // do something }}
* onClose={() => { // do something }}
* />
*/
const Confirm: FC<ConfirmProps> = props => {
const {
isOpen = false,
loading,
title,
content,
confirm,
cancel,
confirmColor,
onClose,
onConfirm,
translateOptions = {}
} = props;
const classes = useStyles(props);
// const translate = useTranslate();
const handleConfirm = useCallback(
e => {
e.stopPropagation();
onConfirm(e);
},
[onConfirm]
);
const handleClick = useCallback(e => {
e.stopPropagation();
}, []);
return (
<Dialog
open={isOpen}
onClose={onClose}
onClick={handleClick}
aria-labelledby="alert-dialog-title"
>
<DialogTitle id="alert-dialog-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText className={classes.contentText}>
{content}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
disabled={loading}
onClick={onClose}
className={classnames("ra-confirm", {
[classes.confirmWarning]: confirmColor === "primary"
})}
>
<CancelIcon className={classes.iconPaddingStyle} />
{cancel}
</Button>
<Button
disabled={loading}
onClick={handleConfirm}
className={classnames("ra-confirm", {
[classes.confirmWarning]: confirmColor === "warning",
[classes.confirmPrimary]: confirmColor === "primary"
})}
autoFocus
>
<ActionCheck className={classes.iconPaddingStyle} />
{confirm}
</Button>
</DialogActions>
</Dialog>
);
};
export default Confirm;
SaveWithConfirmation.tsx*
import React, { useCallback, useState, Fragment } from "react";
import { useFormState } from "react-final-form";
import {
SaveButton,
Toolbar,
useCreate,
useRedirect,
useNotify,
Button
} from "react-admin";
import Confirm from "./Confirm";
const SaveWithConfirmButton = ({ resource, ...props }) => {
const [create, { loading }] = useCreate(resource);
const redirectTo = useRedirect();
const notify = useNotify();
const { basePath } = props;
// get values from the form
const formState = useFormState();
const [open, setOpen] = useState(false);
const handleDialogClick = e => {
setOpen(true);
};
const handleDialogClose = e => {
setOpen(false);
e.stopPropagation();
};
const handleSave = useCallback(
(values, redirect) => {
// call dataProvider.create() manually
// setOpen(true);
create(
{
payload: { data: { ...values } }
},
{
onSuccess: ({ data: newRecord }) => {
notify("ra.notification.created", "info", {
smart_count: 1
});
redirectTo(redirect, basePath, newRecord.id, newRecord);
},
onFailure: error => {
notify(
typeof error === "string"
? error
: error.message || "ra.notification.http_error",
"warning"
);
setOpen(false);
}
}
);
},
[create, notify, redirectTo, basePath, formState]
);
return (
<>
<SaveButton {...props} onSave={handleDialogClick} />
<Confirm
isOpen={open}
loading={loading}
title="Please confirm"
content="Are you sure you want to apply the changes ?"
onConfirm={handleSave}
onClose={handleDialogClose}
/>
</>
);
};
export default SaveWithConfirmButton;
Usage
const DateCreateToolbar = props => (
<Toolbar {...props}>
<SaveWithConfirmButton resource="dates" />
</Toolbar>
);
const DateCreate = props => {
return (
<Create {...props}>
<SimpleForm toolbar={<DateCreateToolbar />} redirect="list">
<DateTimeInput
validate={required()}
label="Start Date"
source="startDate"
/>
<DateTimeInput
validate={required()}
label="End Date"
source="endDate"
/>
</SimpleForm>
</Create>
);
};
export default DateCreate;
<Toolbar {...props}>
<SaveButton
label="Save"
redirect="edit"
submitOnEnter={false}
handleSubmitWithRedirect={
() => {
if(!window.confirm('Are you sure?'))
return false;
return props.handleSubmitWithRedirect();
}
}
/>
</Toolbar>
The payload data in the create, should be called with the form values:
...
create(
{
payload: { data: { ...formState.values } }
},
...
Alternative variant: there is a library called "react-confirm-alert".
Usage example:
import { confirmAlert } from 'react-confirm-alert';
/* In function you may ask confirmation like below */
confirmAlert({
title: 'Question',
message: 'Delete? id:' + id,
buttons: [
{
label: 'Yes',
onClick: () => {
deleteInfo(id)
.then(result => {
if (result.code == 0) {
notify("Deleted");
refreshInfo();
} else {
notify("Error occurred:" + result.msg);
}
}).catch((error) => {
notify('Error occurred:' + error, 'warning');
});
}
},
{
label: 'No',
onClick: () => { }
}
]
});
I'am using react autosuggest npm package to get the json data and display it. I want to display only 5 items. How to do it?
Form.js
import React from 'react'
import Autosuggest from 'react-autosuggest';
import cities from 'cities.json';
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
);
);
};
const getSuggestionValue = suggestion => suggestion.name;
const renderSuggestion = suggestion => (
<div>
{console.log('suggestion', suggestion)}
{suggestion.name}
</div>
);
class Form extends React.Component {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
render(){
const { value, suggestions } = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
return (
<div>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
/>
<br/>
</div>
)
}
}
export default Form;
I want to render only 5 items, otherwise, computer hangs while loading huge data. Is there any other autocomplete react npm package, since I want only cities and country list. i.e when city is inputted, automatically the city name must be suggested with its relevant country.Any solution or suggestion highly appreciated. Thanks in advance
i modified you're getSuggestions() method a little i guess this should work for you.
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
// Here I get data from cities.json
return inputLength === 0 ? [] : cities.filter(lang =>
lang.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,5);
};
Use the Slice method with start index and last Index
suggestions={suggestions.slice(0, 5)}
import {
React
,Avatar
,axiosbase
} from '../../import-files';
import Autosuggest from 'react-autosuggest';
import './autosuggest.css';
import { withStyles } from '#material-ui/core/styles';
import TextField from '#material-ui/core/TextField';
import Paper from '#material-ui/core/Paper';
import MenuItem from '#material-ui/core/MenuItem';
let suggestions = [ { label: 'Afghanistan' } ];
function renderInputComponent(inputProps) {
const { classes, inputRef = () => {}, ref, ...other } = inputProps;
return (
<TextField
className={classes.textField}
fullWidth
variant="outlined"
InputProps={{
inputRef: node => {
ref(node);
inputRef(node);
},
classes: {
input: classes.input,
},
}}
{...other}
/>
);
}
function renderSuggestion(suggestion, { query, isHighlighted }) {
return (
<MenuItem selected={isHighlighted} component="div">
<div>
<strong key={String(suggestion.id)} style={{ fontWeight: 300 }}>
<span className="sugg-option">
<span className="icon-wrap">
<Avatar src={suggestion.Poster}></Avatar>
</span>
<span className="name">
{suggestion.Title}
</span>
</span>
</strong>
</div>
</MenuItem>
);
}
function initSuggestions(value) {
suggestions = value;
}
function getSuggestionValue(suggestion) {
return suggestion.Title;
}
function onSuggestionSelected(event, { suggestion, suggestionValue, suggestionIndex, sectionIndex, method }) {
console.log('HandleSuggestion() '+suggestionValue);
}
const styles = theme => ({
root: {
height: 50,
flexGrow: 1,
},
container: {
position: 'relative',
},
suggestionsContainerOpen: {
position: 'absolute',
zIndex: 998,
marginTop: theme.spacing.unit,
left: 0,
right: 0,
overflowY: 'scroll',
maxHeight:'376%'
},
suggestion: {
display: 'block',
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
},
divider: {
height: theme.spacing.unit * 2,
},
});
class IntegrationAutosuggest extends React.Component {
state = {
single: '',
popper: '',
suggestions: [],
};
componentDidMount() {
initSuggestions(suggestions);
}
// Filter logic
getSuggestions = async (value) => {
const inputValue = value.trim().toLowerCase();
var _filter = JSON.stringify({
filter : inputValue,
});
return await axiosbase.post(`${apiCall}`, _filter);
};
handleSuggestionsFetchRequested = ({ value }) => {
this.getSuggestions(value)
.then(data => {
if (data.Error) {
this.setState({
suggestions: []
});
} else {
const responseData = [];
data.data.itemsList.map((item, i) => {
let File = {
id: item.idEnc,
Title: item.englishFullName +' '+item.arabicFullName,
englishFullName: item.englishFullName,
arabicFullName: item.arabicFullName,
Poster: item.photoPath,
}
responseData.push(File);
});
this.setState({
suggestions: responseData
});
}
})
};
handleSuggestionsClearRequested = () => {
this.setState({
suggestions: [],
});
};
handleChange = name => (event, { newValue }) => {
this.setState({
[name]: newValue,
});
if(event.type=='click'){
if(typeof this.props.handleOrderUserFirstNameChange === "function"){
this.props.handleOrderUserFirstNameChange(newValue);
}
this.state.suggestions.filter(f=>f.Title===newValue).map((item, i) => {
//id
//Title
// Poster
if(typeof this.props.handleUserIDChange === "function"){
this.props.handleUserIDChange(item.id);
}
});
}
};
render() {
const { classes } = this.props;
// console.log('Re-render!!');
// console.log(this.props);
// console.log(this.state.suggestions);
const autosuggestProps = {
renderInputComponent,
suggestions: this.state.suggestions,
onSuggestionsFetchRequested: this.handleSuggestionsFetchRequested,
onSuggestionsClearRequested: this.handleSuggestionsClearRequested,
onSuggestionSelected: this.props.onSelect,
getSuggestionValue,
renderSuggestion,
};
return (
<div className={classes.root}>
<Autosuggest
{...autosuggestProps}
inputProps={{
classes,
placeholder: this.props.placeHolder,
value: this.state.single,
onChange: this.handleChange('single'),
}}
theme={{
container: classes.container,
suggestionsContainerOpen: classes.suggestionsContainerOpen,
suggestionsList: classes.suggestionsList,
suggestion: classes.suggestion,
}}
renderSuggestionsContainer={options => (
<Paper {...options.containerProps} square>
{options.children}
</Paper>
)}
/>
<div className={classes.divider} />
</div>
);
}
}
export default withStyles(styles)(IntegrationAutosuggest);
I am working on implementing smart buttons for a PayPal widget and I was wondering how to go about doing this. My idea right now is to make a button and see if I can fit a script tag inside it that would lead me to make a payment. This is my code so far:
This is from the index.js file
<button>Donate Here Plz</button>
this is the reactjs file that was already written before I hopped on the project.
import ReactDOM from "react-dom";
import scriptLoader from "react-async-script-loader";
class PaypalButton extends React.Component {
constructor(props) {
super(props);
this.state = {
showButton: false,
price: 1.0,
priceError: true
};
window.React = React;
window.ReactDOM = ReactDOM;
}
componentDidMount() {
const { isScriptLoaded, isScriptLoadSucceed } = this.props;
if (isScriptLoaded && isScriptLoadSucceed) {
this.setState({ showButton: true });
}
}
handleInputChange = e => {
const re = /^\d*\.?\d{0,2}$/;
if (e.target.value === "" || re.test(e.target.value)) {
this.setState({ price: e.target.value });
}
if (this.state.price >= 1) {
this.state.priceError = false;
} else {
this.state.priceError = true;
}
console.log(this.state.priceError);
};
componentWillReceiveProps(nextProps) {
const { isScriptLoaded, isScriptLoadSucceed } = nextProps;
const isLoadedButWasntLoadedBefore =
!this.state.showButton && !this.props.isScriptLoaded && isScriptLoaded;
if (isLoadedButWasntLoadedBefore) {
if (isScriptLoadSucceed) {
this.setState({ showButton: true });
}
}
}
render() {
const paypal = window.PAYPAL;
const {
currency,
env,
commit,
client,
onSuccess,
onError,
onCancel
} = this.props;
const { showButton, price } = this.state;
const payment = () =>
paypal.rest.payment.create(env, client, {
transactions: [
{
amount: {
total: price,
currency
}
}
]
});
const onAuthorize = (data, actions) =>
actions.payment.execute().then(() => {
const payment = {
paid: true,
cancelled: false,
payerID: data.payerID,
paymentID: data.paymentID,
paymentToken: data.paymentToken,
returnUrl: data.returnUrl
};
onSuccess(payment);
});
const style = {
layout: "vertical", // horizontal | vertical
size: "medium", // medium | large | responsive
shape: "rect", // pill | rect
color: "gold" // gold | blue | silver | white | black
};
return (
<React.Fragment>
<form>
<h3 style={{ justifySelf: "center" }}>Donate Amount</h3>
<input
name="donate"
type="text"
placeholder="Minimum $1.00"
value={this.state.price}
onChange={this.handleInputChange}
className="donationInput"
/>
</form>
<br />
{showButton && (
<paypal.Button.react
style={style}
env={env}
client={client}
commit={commit}
payment={payment}
onAuthorize={onAuthorize}
onCancel={onCancel}
onError={onError}
/>
)}
</React.Fragment>
);
}
}
export default scriptLoader("https://www.paypalobjects.com/api/checkout.js")(
PaypalButton
);```
No error messages show up, but the button does not lead to anything.
It looks to me like you are trying to use the depreciated version of the checkout api. There is a new version V2 you can view here Paypal Checkout Buttons.
If you want there is an npm package for the new V2 buttons that can be viewed here NPM react-paypal-button-v2.
That said you can do something like the following which is taken from the npm packages github found here react-paypal-button-v2 github but without the typescript and in functional component form:
import React, { useState, useEffect} from 'react';
import ReactDOM from 'react-dom';
const PaypalButton = props => {
const [sdkReady, setSdkReady] = useState(false);
const addPaypalSdk = () => {
const clientID =
'Your-Paypal-Client-ID';
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = `https://www.paypal.com/sdk/js?client-id=${clientID}`;
script.async = true;
script.onload = () => {
setSdkReady(true);
};
script.onerror = () => {
throw new Error('Paypal SDK could not be loaded.');
};
document.body.appendChild(script);
};
useEffect(() => {
if (window !== undefined && window.paypal === undefined) {
addPaypalSdk();
} else if (
window !== undefined &&
window.paypal !== undefined &&
props.onButtonReady
) {
props.onButtonReady();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//amount goes in the value field we will use props of the button for this
const createOrder = (data, actions) => {
return actions.order.create({
purchase_units: [
{
amount: {
currency_code: 'USD',
value: props.amount,
}
}
]
});
};
const onApprove = (data, actions) => {
return actions.order
.capture()
.then(details => {
if (props.onSuccess) {
return props.onSuccess(data);
}
})
.catch(err => {
console.log(err)
});
};
if (!sdkReady && window.paypal === undefined) {
return (
<div>Loading...</div>
);
}
const Button = window.paypal.Buttons.driver('react', {
React,
ReactDOM
});
//you can set your style to whatever read the documentation for different styles I have put some examples in the style tag
return (
<Button
{...props}
createOrder={
amount && !createOrder
? (data, actions) => createOrder(data, actions)
: (data, actions) => createOrder(data, actions)
}
onApprove={
onSuccess
? (data, actions) => onApprove(data, actions)
: (data, actions) => onApprove(data, actions)
}
style={{
layout: 'vertical',
color: 'blue',
shape: 'rect',
label: 'paypal'
}}
/>
);
};
export default PaypalButton;
Then you can use this in your component like so:
const onSuccess = payment => {
console.log(payment)
}
const onCancel = data => {
console.log(data)
};
const onError = err => {
console.log(err);
};
<PaypalButton
amount="1.00"
onError={onError}
onSuccess={onSuccess}
onCancel={onCancel}
/>
Note this is not tested I just pulled it from the npm packages github and removed the typescript for a little easier reading but it should give you an idea of what to do and how to add your donation logic to the button. I would highly suggest reading through paypals documentation. It is painful to go through but necessary. If you don't feel like messing with creating your own button you can just add the npm package and be on your way pretty easy.