React Redux mapStateToProps showing old data before new data fetch - javascript

My component data is fetched based on the route entered - /reports/:id i.e. /reports/1
the "1" following the /reports/ is retrieved by match.params.id which I then make a dispatch call to the following url:
fetchDashData(`http://ee-etap.devops.fds.com/api/etap/v1/templates/template/report/${match.params.id}`)
When the user enter an invalid id, i.e. /reports/a - I want to redirect the user back to /reports which displays a landing page and error message, as such:
return <Redirect to={{
pathname: '/reports',
state: { templateId: match.params.id } }}
/>;
This all works fine until when the user try to visit a valid 'id', i.e. /reports/1 right after the erroneous one - /reports/a, in which the user is immediately redirected back to the /reports page because the fetch call is asynchronous and haven't finished loading the data for /reports/1.
I already have isLoading state defined.. but how can I prevent this from happening?
ReportsDashboard.jsx ( /reports/:id)
class ChartsDashboard extends React.Component {
componentDidMount() {
const { fetchDashData, data, isLoading, hasErrored, match } = this.props;
if ( match.params && match.params.id ) {
fetchDashData(`http://ee-etap.devops.fds.com/api/etap/v1/templates/template/report/${match.params.id}`);
}
}
render() {
const { data, hasErrored, isLoading, classes, match } = this.props;
if ( isLoading ) {
return (
<div style={{ margin: '0 auto', textAlign: 'center' }}>
<CircularProgress size={50} color="secondary" />
</div>
);
}
if ( data && data !== null ) {
const { TemplateReport } = data;
const {
errorBarChart, historyTriggers, historyLineChart, jobs, lastBuildDonutChart, features,
} = TemplateReport;
if (errorBarChart.length === 0) {
// error in data
return <Redirect to={{
pathname: '/reports',
state: { templateId: match.params.id } }}
/>;
}
const keys = [];
errorBarChart.forEach((errorItem) => {
Object.keys(errorItem).forEach((errorKey) => {
if (errorKey !== 'category') {
keys.push(errorKey);
}
});
});
if (match.params.id) {
return (
<div className="page-container">
<Grid container spacing={24}>
<Grid item xs={12} lg={4}>
<Paper className={classes.paper}>
<h4 className={classes.heading}>Error By Categories</h4>
<div style={{ height: '350px' }}>
<ResponsiveBar
data={errorBarChart}
keys={keys}
indexBy="category"
margin={{
top: 50,
right: 50,
bottom: 50,
left: 50,
}}
padding={0.1}
colors="paired"
colorBy="id"
axisBottom={{
orient: 'bottom',
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: 'CATEGORY',
legendPosition: 'middle',
legendOffset: 36,
}}
axisLeft={{
orient: 'left',
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: 'ERROR COUNT',
legendPosition: 'middle',
legendOffset: -40,
}}
labelSkipWidth={12}
labelSkipHeight={12}
labelTextColor="inherit:darker(1.6)"
animate
motionStiffness={90}
motionDamping={15}
/>
</div>
</Paper>
</Grid>
<Grid item xs={12} lg={4}>
<Paper className={classes.paper}>
<h4 className={classes.heading}>Pass Rate %</h4>
<div style={{ height: '350px' }}>
<ResponsivePie
colors="paired"
colorBy={this.pieColors}
margin={{
top: 40,
right: 40,
bottom: 40,
left: 40,
}}
data={lastBuildDonutChart}
animate
defs={[
linearGradientDef('gradientRed', [{ offset: 0, color: 'red' }, { offset: 100, color: '#ffcdd2', opacity: 0.3 }]),
linearGradientDef('gradientYellow', [{ offset: 0, color: 'yellow' }, { offset: 100, color: '#f7bf18a3', opacity: 0.3 }]),
linearGradientDef('gradientGreen', [{ offset: 0, color: '#38da3e' }, { offset: 100, color: '#38da3e', opacity: 0.3 }]),
]}
fill={[
{ match: { id: 'Fail' }, id: 'gradientRed' },
{ match: { id: 'Pass' }, id: 'gradientGreen' },
{ match: { id: 'Undefined' }, id: 'gradientYellow' },
]}
radialLabelsSkipAngle={10}
radialLabelsTextXOffset={6}
radialLabelsTextColor="#333333"
radialLabelsLinkOffset={0}
radialLabelsLinkDiagonalLength={8}
radialLabelsLinkHorizontalLength={7}
radialLabelsLinkStrokeWidth={1}
radialLabelsLinkColor="inherit"
innerRadius={0.5}
padAngle={0.7}
cornerRadius={3}
/>
</div>
</Paper>
</Grid>
<Grid item xs={12} lg={4}>
<Paper className={classes.paper}>
<h4 className={classes.heading}>Jobs Triggered</h4>
<JobsTable data={jobs} templateId={match.params.id} />
</Paper>
</Grid>
<Grid item xs={12} lg={12}>
<Paper className={classes.paper}>
<h4 className={classes.heading}>Scenarios Table</h4>
<Tooltip title="Scenario Report">
<a href={`/reports/${match.params.id}/scenarioHistory`} rel="noopener noreferrer">
<IconButton aria-label="Scenario Report">
<AssignmentIcon />
</IconButton>
</a>
</Tooltip>
<ScenariosTable data={features} />
</Paper>
</Grid>
<Grid item xs={12} lg={12}>
<Paper className={classes.paper}>
<h4 className={classes.heading}>Execution History</h4>
<div style={{ height: '400px' }}>
<ResponsiveLine
colors="paired"
colorBy="id"
margin={{
top: 20,
right: 20,
bottom: 60,
left: 80,
}}
data={historyLineChart}
enableArea={true}
animate
yScale={{ type: 'linear', stacked: true }}
/>
</div>
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
<h4 className={classes.heading}>Previous Builds</h4>
<PreviousBuildsTable data={historyTriggers} templateId={match.params.id}/>
</Paper>
</Grid>
</Grid>
</div>
);
}
}
// error in data
return <Redirect to={{
pathname: '/reports',
state: { templateId: match.params.id } }}
/>;
}
}
const mapStateToProps = state => ({
data: state.reports.data,
hasErrored: state.reports.hasErrored,
isLoading: state.reports.isLoading,
});
const mapDispatchToProps = dispatch => ({
fetchDashData: url => dispatch(chartDataFetch(url)),
});
export default compose(
withStyles(styles),
withRouter,
connect(
mapStateToProps,
mapDispatchToProps,
),
)(ChartsDashboard);
BrowseReport.jsx (/reports/)
class BrowseReports extends React.Component {
constructor(props) {
super(props);
this.state = {
searchVal: '',
errorMsg: '',
}
this.onSearchChange = this.onSearchChange.bind(this);
this.goToTemplateReport = this.goToTemplateReport.bind(this);
}
componentDidMount() {
if (this.props.location && this.props.location.state && this.props.location.state.templateId) {
this.state.errorMsg = `Template Name "${this.props.location.state.templateId}" does not exist, please try again`;
this.props.history.replace('/reports', null);
}
}
onSearchChange(val) {
this.setState({ searchVal: val });
}
goToTemplateReport(val) {
this.props.history.push(`/reports/${val}`);
}
render() {
const { classes, location } = this.props;
const { searchVal, errorMsg } = this.state;
return (
<div className="page-container" style={{ textAlign: 'center' }}>
<Grid container justify="center" spacing={24}>
<Grid item xs={12} lg={8}>
{/* dashData Error */}
<h4 className={classes.errorMsg}>
{/* ERROR MESSAGE HERE */}
{errorMsg}
</h4>
<SearchBar
value={this.state.searchVal}
placeholder='Search for Template Name'
onChange={(value) => this.onSearchChange(value)}
onRequestSearch={(value) => this.goToTemplateReport(value)}
style={{
margin: '0 auto',
}}
/>
</Grid>
<Grid item xs={12} lg={6}>
<Paper className={classes.paper}>
<CompletedJobsTable></CompletedJobsTable>
</Paper>
</Grid>
<Grid item xs={12} lg={6}>
<Paper className={classes.paper}>
<ActiveJobsTable></ActiveJobsTable>
</Paper>
</Grid>
</Grid>
</div>
)
}
}
export default compose(
withStyles(styles),
withRouter
)(BrowseReports);
actions.jsx
export const chartDataHasErrored = hasErrored => ({
type: CHARTS_DATA_HAS_ERRORED,
payload: { hasErrored },
});
export const chartDataIsLoading = isLoading => ({
type: CHARTS_DATA_IS_LOADING,
payload: { isLoading },
});
export const chartDataFetchSuccess = data => ({
type: CHARTS_DATA_FETCH_SUCCESS,
payload: { data },
});
export const chartDataFetch = url => (dispatch) => {
dispatch(chartDataIsLoading(true));
fetch(url, { mode: 'cors' })
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
})
.then(response => response.json())
.then((items) => {
dispatch(chartDataFetchSuccess(items));
})
.catch((error) => {
dispatch(chartDataHasErrored(error));
});
};
reducers.jsx
import { CHARTS_DATA_FETCH_SUCCESS, CHARTS_DATA_IS_LOADING, CHARTS_DATA_HAS_ERRORED } from '../../../store/actions';
const INITIAL_STATE = {
hasErrored: null,
isLoading: true,
data: {},
}
const reportsDashboardReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case CHARTS_DATA_HAS_ERRORED:
return {
...state,
hasErrored: action.payload.hasErrored,
isLoading: false,
};
case CHARTS_DATA_IS_LOADING:
return {
...state,
isLoading: action.payload.isLoading,
hasErrored: null,
};
case CHARTS_DATA_FETCH_SUCCESS:
return {
...state,
isLoading: false,
data: action.payload.data,
};
default:
return state;
}
};
export default reportsDashboardReducer;

You need to save templateId in the global state (set it when data loaded). In component data need to be shown only if templateId from the path equal to the templateId from the global state.

Related

how to reset dialog in react after close

The code below I have provided , one example opens the dialog. And the dialog has add functionality which is the addEmail where in you can add multiple fields which is inside the dialog.
I wanna know is that when I close the dialog using the onClick={handleClose} it should reset the dialog, fields that I added should not show after I close since I did not save it.
So when I click cancel it should reset the state.
Thanks for any idea.
for example here I added fields when I close this and open again these field should not show cause it should reset when I close.
#interface.ts
export type EditPropertiesProps = {
open: boolean;
handleClose: () => void;
selectedRow: IRegional
};
#Code snippet - main page --- this calls and opens the dialog
const handleClose = () => {
console.log('here')
setOpen(false);
};
<EditProperties open={open} handleClose={handleClose} selectedRow={selectedRow} />
#EditProperties ts code
export const RegionalListData: IRegionalList[] = [
{
id: 4,
name: "Associate Director of Construction Ops",
column: "associateDirectorofConstructionOps",
emails: [
{
emailAddress: "associateDir#gmail.com",
firstName: "Associate",
lastName: "Director",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
},
{
id: 5,
name: "CAM Manager",
column: "camManager",
emails: [
{
emailAddress: "associateDir#gmail.com",
firstName: "Associate",
lastName: "Director",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
},
{
id: 6,
name: "CAO-Chief Administrative Officer",
column: "caoChiefAdministrativeOfficer",
emails: [
{
emailAddress: "associateDir#gmail.com",
firstName: "Associate",
lastName: "Director",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
},
];
type InitialReqPaylod = {
accountId: number;
regionalRoleUserDto: IRegional;
};
type IData = {
regionName: string;
marketName: string;
subRegionName: string;
};
type IEmail = {
emailAddress: string;
firstName: string;
id: number;
lastName: string;
};
const EditProperties: FC<EditPropertiesProps> = ({
open,
handleClose,
selectedRow,
}) => {
const dispatch = useAppDispatch();
const [isEmailOpen, setOpenEmail] = useState(false);
const [fetching, setFetching] = useState(false);
const [RegionalList, setRegionalList] = useState<IRegionalList[]>(
RegionalListData
);
const [data, setData] = useState<IData>({
regionName: "",
marketName: "",
subRegionName: "",
});
const [regionalId, setRegionalId] = useState<number | null>(null);
const [emailCurrentIndex, setEmailCurrentIndex] = useState<number | null>(
null
);
const [selectedEmailId, setSelectedEmailId] = useState<number | null>(null);
const { isSuccess } = useAppSelector((state) => state.yardUser);
const { isSaveSuccess } = useAppSelector((state) => state.region);
const email = useAppSelector((state) => state.yardUser);
const [emailOptions, setEmailOptions] = useState<IEmail[]>([]);
const emailList = email.data ? email.data.data : [];
useEffect(() => {
if (selectedRow) {
setData({
regionName: selectedRow["regionName"],
marketName: selectedRow["marketName"],
subRegionName: selectedRow["subRegionName"],
});
let regional = [...RegionalList];
for (const k in selectedRow) {
regional.map((prop: IRegionalList) => {
if (prop.column === k) {
prop.emails = selectedRow[k] ? selectedRow[k] : [];
}
});
}
setRegionalList(regional);
}
}, [selectedRow]);
const [maxWidth, setMaxWidth] = React.useState<DialogProps["maxWidth"]>("md");
const fetchEmailResult = React.useMemo(
() =>
throttle(
(event: any, callback: (results: IEmail[]) => void) => {
const payload: IYardUserRequestPayload | InitialReqPaylod = {
accountId: 1,
searchString: event.target.value,
};
fetch(
`https://jsonplaceholder.typicode.com/users?email=${event.target.value}`
)
.then((res) => res.json())
.then((res) => res.data ? callback(res.data.slice(0, 10)) : callback([]))
},
200
),
[]
);
const emailOnChange = (event: any, regionalId: number, index: number, emailId: number) => {
setRegionalId(regionalId);
setEmailCurrentIndex(index);
setSelectedEmailId(emailId);
fetchEmailResult(event,(results: IEmail[]) => {
console.log('results' , results)
if (results.length) setEmailOptions(results);
});
};
useEffect(() => {
if (isSaveSuccess) {
handleClose();
}
}, [isSaveSuccess]);
useEffect(() => {
if (isSuccess) {
setFetching(false);
}
}, [isSuccess]);
const addEmail = (id: number) => {
setRegionalList((list) =>
list.map((item) => {
if (item.id === id) {
return {
...item,
emails: [
...item.emails,
{
emailAddress: "",
firstName: "",
lastName: "",
id: Math.floor(Math.random() * 999),
fetching: false,
},
],
};
}
return item;
})
);
};
const deleteEmail = (email: IEmail, regionId: number) => {
const regionalListCopy = [...RegionalList].map((prop: IRegionalList) => {
if (prop.id === regionId) {
return {
...prop,
emails: prop.emails.filter((prop) => prop.id !== email.id),
};
}
return { ...prop };
});
setRegionalList(regionalListCopy);
};
const setOnChangeOption = (email) => {
setSelectedEmailId(null);
setRegionalList((list) =>
list.map((item) => {
if (item.id === regionalId) {
return {
...item,
emails: [
...item.emails.map((prop) => {
return {
...prop,
...email,
};
}),
],
};
}
return item;
})
);
};
const EmailItem = ({ email, mIndex, prop }) => (
<>
<div style={{ display: "block" }} key={email.id}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginTop: 15
}}
>
<Autocomplete
options={emailOptions}
getOptionLabel={(option: IEmail) => option.emailAddress}
onInputChange={($event) => emailOnChange($event, prop.id, mIndex, email.id)}
onChange={($event, value) => setOnChangeOption(value)}
fullWidth
open={email.id === selectedEmailId}
renderInput={(params) => (
<TextField size="small" {...params} variant="standard" />
)}
renderOption={(props, option) => {
return (
<Box component="li" {...props}>
{option.emailAddress}
</Box>
);
}}
/>
<DeleteIcon
style={{ color: "red", cursor: "pointer" }}
onClick={() => deleteEmail(email, prop.id)}
/>
</div>
<div
style={{
fontSize: ".8em",
display: "flex",
justifyContent: "space-between",
}}
>
<span style={{ paddingTop: 5 }}>
Email : {email.emailAddress}
Full Name: {email.firstName} {email.lastName}
</span>
{/* <span style={{ paddingRight : 40 }}>{fetching ? "Fetching...." : null}</span> */}
</div>
</div>
</>
);
return (
<Dialog
maxWidth={maxWidth}
open={open}
onClose={handleClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Edit</DialogTitle>
<DialogContent>
<Card sx={{ minWidth: 275 }} style={{ padding: 20 }}>
<div>
<span>Sub-Region (Sub-Division)</span>
<Divider style={{ marginTop: 10 }} />
<FormControl sx={{ mt: 2, minWidth: 720 }}>
<TextField
label="Region (Division)"
variant="filled"
value={data.regionName}
/>
</FormControl>
</div>
<div style={{ marginTop: 10 }}>
<span>Sub-Region (Sub-Division)</span>
<Divider style={{ marginTop: 10 }} />
<FormControl sx={{ mt: 2, minWidth: 720 }}>
<TextField
label="Sub-Region (Sub-Division)"
variant="filled"
value={data.subRegionName}
/>
</FormControl>
</div>
<div style={{ marginTop: 10 }}>
<span>Market</span>
<Divider style={{ marginTop: 10 }} />
<FormControl sx={{ mt: 2, minWidth: 720 }}>
<TextField
label="Market"
variant="filled"
value={data.marketName}
/>
</FormControl>
</div>
</Card>
{RegionalList.map((prop: IRegionalList, index: number) => (
<Card
sx={{ minWidth: 275 }}
style={{ overflow: "visible", padding: 20, marginTop: 20 }}
key={prop.id}
>
<div style={{ display: "flex", alignItems: "center" }}>
{prop.name}*{" "}
<AddIcon
style={{ marginLeft: 5, cursor: "pointer" }}
onClick={() => addEmail(prop.id)}
/>
</div>
<Divider style={{ marginTop: 10 }} />
{prop.emails.map((email: IEmail, mIndex: number) => (
<EmailItem
key={email.id}
prop={prop}
email={email}
mIndex={mIndex}
/>
))}
</Card>
))}
</DialogContent>
<DialogActions
style={{ marginTop: "20px", marginRight: "20px", marginBottom: "20px" }}
>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="contained" onClick={() => saveChanges()} autoFocus>
Save Changes
</Button>
</DialogActions>
</Dialog>
);
};
export default EditProperties;
You need just reset all used states as values of form when clicking handleClose, my suggestion would be to use just one object state for form values.
Example:
const onClose = () => {
handleClose();
setRegionalList(RegionalListData);
}
return (
<Dialog
maxWidth={maxWidth}
open={open}
onClose={onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>

Reactjs Parent not getting child data change values. Rerender issue

I need to update Parent table when data is changed from child edit form. Not getting the updated data in parent component.
I'm passing the data using all the passible ways as per my understanding. I'm not much experienced in react. So it would be really very helpful if someone can help me to understand where i'm making mistake.
PARENT Component is class component-
import Radium from 'radium';
import Dock from 'react-dock';
import {Divider} from '#material-ui/core';
import "react-tabulator/lib/styles.css";
import "react-tabulator/css/bootstrap/tabulator_bootstrap.min.css";
import { React15Tabulator, reactFormatter, MultiValueFormatter } from "react-tabulator";
import {Badge, Row, Button, Col,Card} from 'react-bootstrap';
import EditServer from '../EditServer';
import Confirm from '../../../ui-components/Confirm';
// import './index.scss';
import jsondata from '../../../constants/data/json/ApplicationServer.json';
const styles = {
root: {
fontSize: '16px',
color: '#999',
height: '100vh',
},
main: {
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
paddingTop: '30vh',
},
dockContent: {
// width: '400px',
height: '100%',
display: 'flex',
alignItems: 'left',
padding:'1rem',
overflowX:"hidden",
// justifyContent: 'center',
flexDirection: 'column',
},
remove: {
position: 'absolute',
zIndex: 1,
right: '10px',
top: '10px',
cursor: 'pointer'
}
}
const positions = ['left', 'top', 'right', 'bottom'];
const dimModes = ['transparent', 'none', 'opaque'];
// const dimStyle = ['all']
function SimpleBage (props: any) {
const rowData = props.cell._cell.row.data;
const cellValue = props.cell._cell.value || 'STANDALONE | DOWN | PRIMARY | BACKUP';
if(cellValue === 'STANDALONE'){
return <h5><Badge pill variant="warning" className="btn-size"
onClick={() => alert(rowData.serverStatus)}>
{cellValue}</Badge></h5>;
}
else if(cellValue === 'DOWN'){
return <h5><Badge pill variant="dark" className="btn-size">
{cellValue}</Badge></h5>;
}
else if(cellValue === 'PRIMARY'){
return <h5><Badge pill variant="success" className="btn-size">
{cellValue}</Badge></h5>;
}
else if(cellValue === 'BACKUP'){
return <h5><Badge pill variant="primary" className="btn-size">
{cellValue}</Badge></h5>;
}
else if(cellValue === 'STANDBY'){
return <h5><Badge pill variant="secondary" className="btn-size">
{cellValue}</Badge></h5>;
}
}
const editableColumns = [
{
title: "#",
field: "index",
width: 80,
dir:"asc",
// headerFilter: "input"
},
{
title: "Server Name",
field: "serverName",
hozAlign: "left",
dir:"asc",
},
{
title: "Server Status",
field: "serverStatus",
// width: 100,
hozAlign: 'left',
//formatter: MultiValueFormatter,
formatter: reactFormatter(<SimpleBage />),
},
{
title: "Server Group",
field: "serverGroup",
hozAlign: "left",
},
]
let data = [...jsondata];
const serverListArray =localStorage.getItem("configuration");
class ApplicationServer extends Component {
constructor(props) {
super(props);
this.state = {
data: data,
rowSelection:"",
stopBtnVisible:false,
startBtnVisble:true,
selectedServerName: "",
selectedServerGroup:"",
selectedServerStatus:"",
selectedServerStatusChange:this.state,
positionIdx: 2,
dimModeIdx: "transparent",
isVisible: false,
fluid: true,
customAnimation: true,
slow: true,
size: 0.30,
isServerStarted:this.state,
serverName:null,
rowIndex:null,
};
this.handleVisibleChange = this.handleVisibleChange.bind(this);
this.handleChange = this.handleChange.bind(this);
this.onClose= this.onClose.bind(this);
}
ref = null;
onClose = isVisible =>{
this.setState({ isVisible: false });
}
clearData = () => {
this.setState({ data: [] });
};
handleChange = (handler) =>{
// console.log("update handler data", serverGroup)
// const updatedServerGroup = event.target.value;
// console.log("handle form submit in table", this.state.selectedServerGroup, this.props.serverGroup);
console.log("handle form submit in table", handler);
this.setState({
...handler
//selectedServerGroup:handler.target.serverGroup
})
//console.log("handle form submit in table", this.state);
console.log("handle form submit in table", handler);
}
render(){
const options = {
height: 215,
movableRows: true,
history: true,
movableColumns: true,
resizableRows: true ,
reactiveData:true,
};
const duration = this.state.slow ? 1000 : 100;
const dur = duration / 1000;
const transitions = ['left', 'top', 'width', 'height']
.map(p => `${p} ${dur}s cubic-bezier(0, 1.5, 0.5, 1)`)
.join(',');
console.log("Render data:", serverListArray);
if(Array.isArray(serverListArray) && serverListArray.length>0){
data=[...serverListArray];
//jsondata= serverListArray;
}
return(
<div style={[styles.root]}>
<div style={{
position: 'relative',
height: 'calc(100vh - 50px)',
}}>
<Row className="table-control-bar">
<Col sm={8}><h5>Server Controls</h5></Col>
<Col sm={4} className="text-right">
<Button variant={this.state.failoverButtonVariant}
className="mr-2"
id="serverFailover"
onClick={this.handleServerFailover}
selectedserverName={this.selectedserverName}
disabled={this.state.failoverButtonDisabled}>Failover</Button>
<Button variant={this.state.editButtonVariant}
className="mr-2"
id="serverEdit"
selectedServerName={this.selectedServerName}
disabled={this.state.editButtonDisabled}
onClick={this.handleVisibleChange}>Edit</Button>
{this.state.stopBtnVisible === false &&
(
<Button
onClick={this.handleServerStart}
className="mr-2"
id="serverStart"
selectedserverName={this.selectedserverName}
visible={this.state.startBtnVisible}
variant={this.state.startButtonVariant}
disabled={this.state.startButtonDisabled}>Start
</Button>
)}
{this.state.startBtnVisible === false &&
(<Button onClick={this.handleServerStop}
className="mr-2"
id="serverStop"
selectedserverName={this.selectedserverName}
variant="primary">Stop</Button>)}
<Button variant={this.state.restartButtonVariant}
id="serverRestart"
onClick={this.handleServerRestart}
selectedserverName={this.selectedserverName}
disabled={this.state.restartButtonDisabled}>Restart</Button>
</Col>
</Row>
<div>
<React15Tabulator
columns={editableColumns}
data={data}
selectable={1}
rowClick={this.rowClick}
options={options}
data-custom-attr="test-custom-attribute"
className={"custom-css-class" + this.state.rowSelection}
tooltips={true}
layout={"fitData"}
/>
</div>
</div>
<div>
<Dock position={positions[this.state.positionIdx]}
size={this.state.size}
dimMode={dimModes[this.state.dimModeIdx]}
isVisible={this.state.isVisible}
onVisibleChange={this.handleVisibleChange}
fluid={this.state.fluid}
dimStyle={{ background: 'rgba(0, 0, 100, 0.2)'}, {pointerEvents:'all !important'}}
dockStyle={this.state.customAnimation ? { transition: transitions } : null, {top:'6%'} }
// ,{top:'13%'}
dockHiddenStyle={this.state.customAnimation ? {
transition: [transitions, `opacity 0.01s linear ${dur}s`].join(',')
} : null}
duration={duration}>
{({ position, isResizing }) =>
<div style={[styles.dockContent]} className="modal-container">
<h4>Edit Server Group</h4>
<Divider />
<EditServer
handleInputChange={this.handleChange}
// handleChange = {e => console.log(e)}
serverName={this.state.selectedServerName}
serverGroup= {this.state.selectedServerGroup}
onClose={this.onClose} />
<span onClick={() => this.setState({ isVisible: false })}
style={styles.remove} title="close">✖</span>
</div>
}
</Dock>
</div>
</div>
)
}
handleSizeChange = size => {
this.setState({ size });
}
}
export default ApplicationServer = Radium(ApplicationServer);
CHILD Component is functional component-
import {func, string} from 'prop-types';
import {makeStyles } from '#material-ui/core';
import {Row, Col, Button, Form} from 'react-bootstrap';
import toast from '../../../ui-components/Toaster';
import Input from '../../../ui-components/Input';
const useStyles = makeStyles((theme) => ({
root: {
'& > *': {
margin: theme.spacing(2),
},
},
formControl: {
margin: theme.spacing(1),
minWidth: 120,
maxWidth: 300,
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: 2,
},
noLabel: {
marginTop: theme.spacing(3),
},
}));
function getStyles(name, personName, theme) {
return {
fontWeight:
personName.indexOf(name) === -1
? theme.typography.fontWeightRegular
: theme.typography.fontWeightMedium,
};
}
export default function EditServer(props) {
const {onClose} = props;
let serverGroup = props.serverGroup;
const initialFormState = {
id: null,
serverName: props.serverName,
serverGroup: props.serverGroup,
saveDisabled: true,
cancelDisabled:true,
showDockModal:false,
edited:false,
}
// console.log("inside child", initialFormState);
const [handler, setHandler] = useState();
//const ref = useRef(null);
const handleInputChange = (event) => {
//event.preventDefault();
console.log("edit server props input change", handler);
let { name, value } = event.target;
//handler.serverGroup = event.target.value;
setHandler({...handler, [name]: value, edited:true});
console.log("inside edit form change:", handler, value, name);
}
const handleFormSubmit = (event) => {
//console.log('inside handleFormSubmit', event, props);
event.preventDefault();
onClose();
if (handler.edited){
//let newServerGroup= handler.serverGroup;
//console.log("form submit", handler);
setHandler({
...handler,
edited:false,
});
console.log("inside edit form change:", handler, event)
// props.serverGroup=newServerGroup;
// return handleInputChange(handler.serverGroup.value);
return toast.success(initialFormState.serverGroup + " Server Group Changed to SIMULATION !");
}
}
return (
<React.Fragment>
<Form className="scrollY"
onSubmit={handleFormSubmit}
>
<Row>
<Col sm={4} className="text-align-right">
<label htmlFor="serverName">Server Name<span className="red">*</span></label>
</Col>
<Col sm={8}>
<Input
type="text"
required
id="severName"
name="severName"
// label="Handler Name"
fullWidth
autoComplete="given-name"
value={initialFormState.serverName}
onChange={handleInputChange}
disabled="true"
/>
</Col>
</Row>
<Row>
<Col sm={4} className="text-align-right">
<label htmlFor="serverGroup" required>Server Group<span className="red">*</span></label>
</Col>
<Col sm={8}>
<Input
type="string"
required
id="serverGroup"
name="serverGroup"
// label="Handler Speed"
fullWidth
autoComplete="family-name"
defaultValue={initialFormState.serverGroup}
onChange={handleInputChange}
/>
</Col>
</Row>
<Row>
<Col sm={4}>
{/* <Button variant="secondary" disabled className="btn-custom" onClick={handleClose}>Cancel</Button>*/}
</Col>
<Col sm={8}>
<Button type="submit"
variant="primary"
className="btn-custom"
>
Submit
</Button>
</Col>
</Row>
</Form>
</React.Fragment>
);
}
EditServer.propTypes = {
onSuccess: func,
};
I'm using react tabulator.
Appreciate the help. Thank you.

ReactJS TypeError: this.props. is not a function

I'm building an application, where there is a form presented with different steps. In all the steps but one, I manage to provide the necessary functions as props to make some operations such as 'handleNext', 'handleBack' or 'handleChange'.
Nevertheless, in the last step, represented in the class SuccessForm, when I try to execute the 'handleDownload' function, I get the following error:
TypeError: this.props.handleDownload is not a function
Here it is the SuccessForm.js class:
export class SuccessForm extends Component {
constructor() {
super();
}
download = e => {
e.preventDefault();
this.props.handleDownload();
}
render() {
return (
<React.Fragment>
<Grid container>
<Grid item xs={12} sm={2}>
<DirectionsWalkIcon fontSize="large" style={{
fill: "orange", width: 65,
height: 65
}} />
</Grid>
<Grid>
<Grid item xs={12} sm={6}>
<Typography variant="h5" gutterBottom>
Route created
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1">
Your new track was succesfully created and saved
</Typography>
</Grid>
</Grid>
<Tooltip title="Download" arrow>
<IconButton
variant="contained"
color="primary"
style={{
marginLeft: 'auto',
// marginRight: '2vh'
}}
onClick={this.download}
>
<GetAppIcon fontSize="large" style={{ fill: "orange" }} />
</IconButton>
</Tooltip>
</Grid>
</React.Fragment>
)
}
}
The entire NewRouteForm.js:
import React, { Component } from 'react'
import { makeStyles, MuiThemeProvider } from '#material-ui/core/styles';
import Paper from '#material-ui/core/Paper';
import Stepper from '#material-ui/core/Stepper';
import Step from '#material-ui/core/Step';
import StepLabel from '#material-ui/core/StepLabel';
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/Typography';
import DataForm from '../stepper/dataform/DataForm';
import ReviewForm from '../stepper/reviewform/ReviewForm';
import MapForm from '../stepper/mapform/MapForm';
import NavBar from '../../graphic interface/NavBar';
import DirectionsWalkIcon from '#material-ui/icons/DirectionsWalk';
import Avatar from '#material-ui/core/Avatar';
import CheckCircleOutlineOutlinedIcon from '#material- ui/icons/CheckCircleOutlineOutlined';
import FilterHdrIcon from '#material-ui/icons/FilterHdr';
import Grid from '#material-ui/core/Grid';
import SuccessForm from '../stepper/success/SuccessForm';
import { withStyles } from '#material-ui/styles';
export class NewRouteForm extends Component {
state = {
activeStep: 0,
name: '',
description: '',
date: new Date(),
photos: [],
videos: [],
points: []
};
handleNext = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep + 1 });
};
handleBack = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep - 1 });
};
handleChange = input => e => {
this.setState({ [input]: e.target.value });
}
handleDateChange = date => {
this.setState({ date: date });
}
handleMediaChange = (selectorFiles: FileList, code) => { // this is not an error, is TypeScript
switch (code) {
case 0: // photos
this.setState({ photos: selectorFiles });
break;
case 1: // videos
this.setState({ videos: selectorFiles });
break;
default:
alert('Invalid media code!!');
console.log(code)
break;
}
}
handleMapPoints = points => {
this.setState({ points: points })
}
// ###########################
// Download and Upload methods
// ###########################
handleDownload = () => {
// download route
console.log("DOWNLOAD")
alert("DOWNLOAD");
}
upload = () => {
// upload route
}
render() {
const { activeStep } = this.state;
const { name, description, date, photos, videos, points } = this.state;
const values = { activeStep, name, description, date, photos, videos, points };
const { classes } = this.props;
return (
<MuiThemeProvider>
<React.Fragment>
<NavBar />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<FilterHdrIcon fontSize="large" />
</Avatar>
<Typography component="h1" variant="h4" align="center">
Create your own route
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<SuccessForm />
) : (
<React.Fragment>
{getStepContent(activeStep,
values,
this.handleNext,
this.handleBack,
this.handleChange,
this.handleDateChange,
this.handleMediaChange,
this.handleMapPoints,
this.handleDownload
)}
</React.Fragment>
)}
</React.Fragment>
</Paper>
</main>
</React.Fragment>
</MuiThemeProvider>
)
}
}
const steps = ['Basic data', 'Map', 'Review your route'];
function getStepContent(step,
values,
handleNext,
handleBack,
handleChange,
handleDateChange,
handleMediaChange,
handleMapPoints,
handleDownload) {
switch (step) {
case 0:
return <DataForm
handleNext={handleNext}
handleChange={handleChange}
handleDateChange={handleDateChange}
handleMediaChange={handleMediaChange}
values={values}
/>;
case 1:
return <MapForm
handleNext={handleNext}
handleBack={handleBack}
handleMapPoints={handleMapPoints}
values={values}
/>;
case 2:
return <ReviewForm
handleNext={handleNext}
handleBack={handleBack}
values={values}
/>;
case 3:
return <SuccessForm
handleDownload={handleDownload}
/>;
default:
throw new Error('Unknown step');
}
}
const useStyles = theme => ({
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
width: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
},
},
stepper: {
padding: theme.spacing(3, 0, 5),
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
},
avatar: {
marginLeft: 'auto',
marginRight: 'auto',
backgroundColor: theme.palette.warning.main,
},
icon: {
width: 65,
height: 65,
},
grid: {
marginLeft: theme.spacing(2),
}
});
export default withStyles(useStyles)(NewRouteForm);
Try calling super(props) in the constructor:
constructor(props) {
super(props);
}
and passing function with this instance (this.handleDownload) as it is a class property:
<SuccessForm handleDownload={this.handleDownload} />
Update:
You have a bug on the last step when you not passing a property:
activeStep === steps.length ? <SuccessForm handleDownload={this.handleDownload}/>
Assuming that you have a class in your parent Component, what you're missing is the this keyword in the function reference...
case 3:
return <SuccessForm
handleDownload={this.handleDownload}
/>;

Search functionality is not working for the frontend

I am getting data from the backend using postman. But when i am using frontend for the same, it is not working. I am getting errors like
1.TypeError: Cannot read property 'map' of null
2.Unhandled Rejection (TypeError): Cannot read property 'map' of null.
I think, I am getting this error because cards are not able to render when I am searching.The backend data is coming as a array.
const styles = theme => ({
appBar: {
position: 'relative',
},
icon: {
marginRight: theme.spacing.unit * 2,
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
[theme.breakpoints.up(1100 + theme.spacing.unit * 3 * 2)]: {
width: 1100,
marginLeft: 'auto',
marginRight: 'auto',
},
},
cardGrid: {
padding: `${theme.spacing.unit * 8}px 0`,
},
card: {
height: '100%',
display: 'flex',
flexDirection: 'column',
},
cardContent: {
flexGrow: 1,
},
});
class Products extends Component {
constructor(props) {
super(props);
this.state = {
products: [],
searchString: ''
};
this.onSearchInputChange = this.onSearchInputChange.bind(this);
this.getProducts = this.getProducts.bind(this);
}
componentDidMount() {
this.getProducts();
}
// delete = id => {
// axios.post('http://localhost:9022/products/delete/' + id)
// .then(res => {
// let updatedProducts = [...this.state.products].filter(i => i.id !== id);
// this.setState({ products: updatedProducts });
// });
// }
delete = id => {
axios.post('http://localhost:9022/products/delete/' + id)
.then(res => {
this.setState((prevState, prevProps) => {
let updatedProducts = [...prevState.products].filter(i => i.id !== id);
return ({
products: updatedProducts
});
});
});
}
getProducts() {
axios.get('http://localhost:9022/products/getAll')
.then(res => {
this.setState({ products: res.data }, () => {
console.log(this.state.products);
});
});
}
onSearchInputChange = (event) => {
let newSearchString = '';
if (event.target.value) {
newSearchString = event.target.value;
}
axios.get('http://localhost:9022/products/getproducts' + newSearchString)
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
this.getProducts();
}
// onSearchInputChange(event) {
// let newSearchString = '';
// if (event.target.value) {
// newSearchString = event.target.value;
// }
// // call getProducts once React has finished updating the state using the callback (second argument)
// this.setState({ searchString: newSearchString }, () => {
// this.getProducts();
// });
// }
render() {
const { classes } = this.props;
return (
<React.Fragment>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<CssBaseline />
<main>
<div className={classNames(classes.layout, classes.cardGrid)}>
<Grid container spacing={40}>
{this.state.products.map(currentProduct => (
<Grid item key={currentProduct.id} sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{currentProduct.title}
</Typography>
<Typography>
{currentProduct.price}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" component={Link} to={"/products/" + currentProduct.id}>
Edit
</Button>
<Button size="small" color="primary" onClick={() => this.delete(currentProduct.id)}>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</main>
</React.Fragment>
)
}
}
Products.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Products);
It has been observed that, you have added wrong URL of getproducts, slash is missing in the URL. Please find details below:
If search-string is r, then you are using this URL of getproducts: http://localhost:9022/products/getproductsr
which is wrong and it should be http://localhost:9022/products/getproducts/r
Hence you have to change your code of retrieving products as follows:
axios.get('http://localhost:9022/products/getproducts/' + newSearchString)
.then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
Also it will be good to provide a check for undefined/null for this.state.products and then render the components because it is possible that products might be null if one provide wrong URL and undefined as axios request is async. Hence by adding 'this.state.products && ' in existing render code will be good to avoid such issues. I have updated your render function, please find it below:
render() {
const { classes } = this.props;
return (
<React.Fragment>
<TextField style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange} />
<CssBaseline />
<main>
<div className={classNames(classes.layout, classes.cardGrid)}>
<Grid container spacing={40}>
{this.state.products && this.state.products.map(currentProduct => (
<Grid item key={currentProduct.id} sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{currentProduct.title}
</Typography>
<Typography>
{currentProduct.price}
</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary" component={Link} to={"/products/" + currentProduct.id}>
Edit
</Button>
<Button size="small" color="primary" onClick={() => this.delete(currentProduct.id)}>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</div>
</main>
</React.Fragment>
)
}
Hope it will help..
You may try this one:
You just missed the slash in the api end Point as below : Use this
axios.get('http://localhost:9022/products/getproducts/' + newSearchString)
instead of :
axios.get('http://localhost:9022/products/getproducts' + newSearchString)

react-form-validator not registering children components?

I am using 'react-form-validator-core' package and trying to create a custom form validator that implements 'mui-downshift', a Material UI implementation of PayPal's downshift. This question is mostly about 'react-form-validator-core' package itself. The problem is that the form itself does not register the validator component I've created. Here is my full code of the custom component and the form itself. I've exhausted my debugging skills, but what I noticed is that there's something wrong with the this.context in the form...
Validator component:
import React from 'react';
import PropTypes from 'prop-types';
import MuiDownshift from 'mui-downshift';
import { ValidatorComponent } from 'react-form-validator-core';
class AutocompleteValidator extends ValidatorComponent {
constructor(props) {
debugger;
super(props);
this.originalItems = props.items.map(({key, name}) => ({ text: name, value: key }));
this.handleStateChange = this.handleStateChange.bind(this);
this.errorText = this.errorText.bind(this);
}
componentWillMount() {
if (!this.filteredItems) {
this.setState({filteredItems: this.originalItems});
}
if (!!this.props.value) {
const selectedItem = this.originalItems.filter(
item => item.value.toLowerCase().includes(this.props.value.toLowerCase())
)[0];
this.setState({ selectedItem })
} else {
this.setState({ selectedItem: null})
}
}
componentWillReceiveProps(nextProps) {
// If no filteredItems in sate, get the whole list:
if (!nextProps.value) {
this.setState({ isValid: false })
}
}
handleStateChange(changes) {
// If searching
if (changes.hasOwnProperty('inputValue')) {
const filteredItems = this.originalItems.filter(
item => item.text.toLowerCase().includes(changes.inputValue.toLowerCase())
);
this.setState({ filteredItems })
}
// If something is selected
if (changes.hasOwnProperty('selectedItem')) {
!!changes.selectedItem ? this.setState({isValid: true}) : this.setState({isValid: false})
// If we get undefined, change to '' as a fallback to default state
changes.selectedItem = changes.selectedItem ? changes.selectedItem : '';
this.props.onStateChange(changes);
}
}
errorText() {
const { isValid } = this.state;
if (isValid) {
return null;
}
return (
<div style={{ color: 'red' }}>
{this.getErrorMessage()}
</div>
);
}
render() {
return (
<div>
<MuiDownshift
{...this.props}
items={this.state.filteredItems}
onStateChange={this.handleStateChange}
ref={(r) => { this.input = r; }}
defaultSelectedItem={this.state.selectedItem}
/>
{this.errorText()}
</div>
);
}
}
AutocompleteValidator.childContextTypes = {
form: PropTypes.object
};
export default AutocompleteValidator;
A component where it's used:
render() {
return (
<ValidatorForm
ref='form'
onSubmit={() => {
this.context.router.history.push(this.props.config.urls['step5']);
}}
onError={errors => console.log(errors)}
>
<Row>
<Col md={12}>
<AutocompleteValidator
validators={['required']}
errorMessages={['Cette information doit être renseignée']}
isRequired={true}
name='bankId'
items={this.props.config.list.bank}
onStateChange={(changes) => {
this.props.loansUpdate('bankId', changes.selectedItem.value);
}}
value={!!this.props.loans.bankId ? this.props.loans.bankId : false}
/>
</Col>
</Row>
<Row>
<Col md={12} style={{ marginTop: '15px' }}>
<Checkbox
label={<Translate value='step4.insuranceProvidedByBank' />}
labelStyle={{ 'top': '0px' }}
name='insuranceProvidedByBank'
value={this.props.loans.insuranceProvidedByBank}
checked={this.props.loans.insuranceProvidedByBank}
onCheck={(event, value) => {
this.props.loansUpdate('insuranceProvidedByBank', value);
}}
/>
</Col>
</Row>
<Row>
<Col sm={6} md={5}>
<Button
fullWidth
style={{ marginTop: '50px', marginBottom: '20px' }}
type='submit'
icon={<i className='fa fa-angle-right' style={{ marginLeft: '10px', display: 'inline-block' }} />}
><Translate value='iContinue' /></Button>
</Col>
</Row>
</ValidatorForm >
)
};
};
you get this problem because you override default componentWillMount and componentWillReceiveProps methods from ValidatorComponent. So, to solve this case you should do something like this
componentWillMount() {
// should call parent method before
super.componentWillMount();
// your code
}

Categories