React Maximum update depth exceeded - can't spot the loop - reactjs

I'm a very new dev working on legacy code, so I'll apologize now. But I'm getting this error when I try to "add a rule" to an array of rules. I did try googling this, and understand I am likely looping a setState, but I can't spot it. Below is the code I believe is relevant, I am not using useEffect, and I don't think I'm calling the function directly. Any help would be appreciated, thanks.
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import {
Grid,
MenuItem,
TextField,
Typography,
IconButton,
InputAdornment,
Select,
} from '#material-ui/core';
import ArrowIcon from '#material-ui/icons/ArrowDropDown'
import AddIcon from '#material-ui/icons/Add';
import DeleteIcon from '#material-ui/icons/Clear';
import DragHandle from '#material-ui/icons/DragHandle';
import {
DropTarget,
DragSource,
} from 'react-dnd';
import registry from "../registry";
import {Can} from '../utils';
let schema = registry.actions.RUN.configDef;
const styles = theme => ({
row: {
'&:nth-of-type(even)': {
backgroundColor: theme.palette.background.default,
},
borderTop: '1px solid',
borderColor: theme.palette.grey[400]
},
header: {
fontWeight: 'normal',
textAlign: 'center'
},
border: {
border: '1px solid',
borderColor: theme.palette.grey[400],
borderRadius: '4px',
padding: '8px',
overflow: 'hidden'
},
add: {
color: theme.palette.primary.main
},
clear: {
color: theme.palette.error.main
},
textField: {
margin: '8px'
},
rightJustifiedText: {
display: 'flex',
justifyContent: 'flex-end',
alignItems: 'center',
margin: '8px'
},
container: {
width: '100%'
},
iconContainer: {
padding: '0px 8px 0px 8px'
},
flexContainer: {
display: 'flex',
alignItems: 'center'
}
});
function CustomArrowIcon(props) {
return (<ArrowIcon {...props} style={{position: 'absolute', left: 0}}/>)
}
function RowContent({id, onRemove, children, connectDropTarget, connectDragSource, isDragging, classes, ability, accountNumber}) {
const opacity = isDragging ? 0.5 : 1;
return (<Grid className={classes.row} item xs={12} key={id}>
{connectDropTarget(
<span className={classes.flexContainer} style={{opacity}}>
{connectDragSource(
<span className={classes.iconContainer}>
<Can I={'update'} a={'serviceActionProfile'} where={'accountNumber'} equals={accountNumber} ability={ability}>
{can => (
<IconButton disabled={!can}>
<DragHandle/>
</IconButton>
)}
</Can>
</span>
)}
{children}
<span className={classes.iconContainer}>
<Can I={'update'} a={'serviceActionProfile'} where={'accountNumber'} equals={accountNumber} ability={ability}>
{can => (
<IconButton disabled={!can} onClick={onRemove}>
<DeleteIcon className={can ? classes.clear : undefined}/>
</IconButton>
)}
</Can>
</span>
</span>
)}
</Grid>);
}
const DraggableRow = DropTarget('row', {
hover: (props, monitor) => {
const monitorItem = monitor.getItem();
const dragIndex = monitorItem.index;
const hoverIndex = props.index;
if (dragIndex === hoverIndex) return null;
let offsetY = monitor.getClientOffset().y;
// When a short item is swapped with a tall item the short item will continue to hover the tall item
// This would normally cause the two to be swapped again, and again, and again...
// Dont re-swap the items until the mouse moves in the other direction
if (dragIndex < hoverIndex && offsetY <= monitorItem.offsetY) {
monitorItem.offsetY = offsetY;
return
}
if (dragIndex > hoverIndex && offsetY >= monitorItem.offsetY) {
monitorItem.offsetY = offsetY;
return
}
props.moveRow(dragIndex, hoverIndex);
monitorItem.index = hoverIndex;
monitorItem.offsetY = offsetY;
},
}, (connect) => {
return {
connectDropTarget: connect.dropTarget()
};
})(DragSource('row', {
beginDrag: (props, monitor) => {
return { index: props.index, offsetY: monitor.getClientOffset().y };
},
}, (connect, monitor) => {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
};
})(withStyles(styles)(RowContent)));
class Modify extends React.Component {
constructor(props) {
super(props);
let now = Date.now();
this.state = {
rules: (props.configValues.rules || []).slice().sort((a,b) => a.index-b.index).map((rule, index) => {
return {
...rule,
toIsValid: validateToFrom('', rule.to.path.trim(), props.serviceActionProfile.jsonataVersion),
fromIsValid: validateToFrom(rule.from.type, rule.from.value.trim(), props.serviceActionProfile.jsonataVersion),
id: ++now,
index
};
})
};
}
componentDidMount() {
this._notifyParent();
}
onChangeAction = (index, event) => {
let action = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].action = action;
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeToPrefix = (index, event) => {
let prefix = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].to.prefix = prefix;
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeToPath = (index, event) => {
let path = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].to.path = path;
newRules[index].toIsValid = validateToFrom('', path.trim(), this.props.serviceActionProfile.jsonataVersion);
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeFromType = (index, event) => {
let type = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].from.type = type;
newRules[index].fromIsValid = validateToFrom(type, newRules[index].from.value.trim(), this.props.serviceActionProfile.jsonataVersion);
return {
rules: newRules
};
}, this._notifyParent);
};
onChangeFromValue = (index, event) => {
let value = event.target.value;
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules[index].from.value = value;
newRules[index].fromIsValid = validateToFrom(newRules[index].from.type, value.trim(), this.props.serviceActionProfile.jsonataVersion);
return {
rules: newRules
};
}, this._notifyParent);
};
onRemoveRule = (index) => {
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules.splice(index, 1);
newRules.forEach((map, index) => {
map.index = index;
});
return {
rules: newRules
}
}, this._notifyParent);
};
addRule = () => {
this.setState(oldState => {
let newRules = oldState.rules.slice();
newRules.push({
id: Date.now(),
index: newRules.length,
action: 'Set',
to: {
prefix: 'local.',
path: ''
},
from: {
type: 'local.',
value: ''
}
});
return {
rules: newRules
};
}, this._notifyParent);
};
_notifyParent() {
let config = {
rules: this.state.rules.map(rule => {
let clone = {
...rule
};
delete clone.id;
delete clone.toIsValid;
delete clone.fromIsValid;
return clone;
})
};
const profile = {
...this.props.serviceActionProfile, // Add any additional properties that might be on the profile and used for validation like the jsonataVersion.
name: "PLACEHOLDER",
action: 'RUN',
documents: {},
configValues: config, // rules
};
const validation = validate(profile);
this.props.onChange({}, config, validation.validOverall);
}
moveRow = (from, to) => {
this.setState(oldState => {
let newRules = oldState.rules.slice();
let moving = newRules.splice(from, 1)[0];
newRules.splice(to, 0, moving);
newRules.forEach((map, index) => {
map.index = index;
});
return {
rules: newRules
}
}, this._notifyParent);
};
render() {
const { classes, ability, accountNumber } = this.props;
const textFieldMargin = 'dense';
return (
<div className={classes.border}>
<Can I={'update'} a={'serviceActionProfile'} where={'accountNumber'} equals={accountNumber} ability={ability}>
{can => (
<React.Fragment>
<Grid container
spacing={16}
direction={"column"}>
<Grid item xs={12} container alignItems={'center'}>
<Grid item xs={1}>
<span className={classes.iconContainer}>
<IconButton className={classes.add} disabled={!can} onClick={this.addRule}>
<AddIcon/>
</IconButton>
</span>
</Grid>
<Grid item xs={10}>
<Typography component={'div'} className={classes.header}>
Rules
</Typography>
</Grid>
</Grid>
{this.state.rules.slice().sort((a,b) => a.index-b.index).map(({action, to, toIsValid, from, fromIsValid, id}, index) => {
return (<DraggableRow key={id}
index={index}
onRemove={this.onRemoveRule.bind(this, index)}
moveRow={can ? this.moveRow : ()=>{}}
ability={ability}
accountNumber={accountNumber}>
<div className={classes.container}>
<span className={classes.flexContainer}>
<TextField select
className={classes.textField}
variant={'outlined'}
margin={textFieldMargin}
style={{width: '150px'}}
value={action}
fullWidth
disabled={!can}
onChange={this.onChangeAction.bind(this, index)}>
{schema.properties.rules.items.properties.action.enum.map(value => {
return (<MenuItem key={value} value={value}>{value}</MenuItem>)
})}
</TextField>
<TextField variant={'outlined'}
label={'Field'}
className={classes.textField}
margin={textFieldMargin}
value={to.path}
fullWidth
disabled={!can}
error={!toIsValid}
onChange={this.onChangeToPath.bind(this, index)}
inputProps={{style: {paddingLeft: '4px'}}}
InputProps={{
style: {width: 'calc(100% - 14px)'},
startAdornment: (
<InputAdornment>
<Select value={to.prefix}
disabled={!can}
onChange={this.onChangeToPrefix.bind(this, index)}
IconComponent={CustomArrowIcon}
SelectDisplayProps={{
style: {paddingLeft: '24px', paddingRight: '0px'}
}}>
{schema.properties.rules.items.properties.to.properties.prefix.enum.map(value => {
return (<MenuItem key={value} value={value}>{value}</MenuItem>)
})}
</Select>
</InputAdornment>
)
}}/>
</span>
{action === 'Set' ? (
<span className={classes.flexContainer}>
<Typography className={classes.rightJustifiedText} component='span' style={{width: '150px'}}>to</Typography>
<TextField variant={'outlined'}
label={'Value'}
className={classes.textField}
margin={textFieldMargin}
value={from.value}
fullWidth
disabled={!can}
error={!fromIsValid}
onChange={this.onChangeFromValue.bind(this, index)}
inputProps={{style: {paddingLeft: '4px'}}}
InputProps={{
style: {width: 'calc(100% - 14px)'},
startAdornment: (
<InputAdornment>
<Select value={from.type}
onChange={this.onChangeFromType.bind(this, index)}
IconComponent={CustomArrowIcon}
disabled={!can}
SelectDisplayProps={{
style: {paddingLeft: '24px', paddingRight: '0px'}
}}>
{schema.properties.rules.items.properties.from.properties.type.enum.map(value => {
return (<MenuItem key={value} value={value}>{value}</MenuItem>)
})}
</Select>
</InputAdornment>
)
}}/>
</span>
) : null}
</div>
</DraggableRow>)
})}
</Grid>
</React.Fragment>
)}
</Can>
</div>
);
}
}
Modify.defaultProps = {
requiredDocuments: {},
documents: {},
configValues: {
rules: []
},
serviceActionProfile: {}
};
Modify.propTypes = {
requiredDocuments: PropTypes.object,
documents: PropTypes.object,
configValues: PropTypes.object,
onChange: PropTypes.func.isRequired,
accountNumber: PropTypes.number.isRequired,
ability: PropTypes.object.isRequired,
serviceActionProfile: PropTypes.object
};
export default withStyles(styles)(Modify);

Related

Read more functionality: Too many re-renders. React limits the number of renders to prevent an infinite loop

I am trying to do a Read More functionality, where clicking read more will show the long text for both the header and message. I came across this solution and works, but this only works for only one long text. I want to only have one read more option for both header and message, so I modified it a bit but I am having the above error.
After reading so many similar questions, I still can't seem to fix my code. I understand that this has something to do with the setting of state, but I am at a lost on how to fix this.
Here is a self-contained sample: https://stackblitz.com/edit/react-7qwbcb?file=src/App.js
App.js
import React, { useEffect, useState, useCallback } from 'react';
import Box from '#material-ui/core/Box';
import { notificationsApi } from './fakeAPI';
import ReadMore from './ReadMore';
const Notifications = () => {
const [notifs, setNotifs] = useState(null);
const Lists = useCallback(async () => {
try {
const notif = await notificationsApi.getNotifications();
setNotifs(notif);
} catch (e) {
console.log(e);
}
// return () => {
// notifs;
// };
}, [notifs]);
useEffect(() => {
Lists();
}, [Lists]);
return (
<Box>
{notifs &&
notifs.map((notif) => {
return (
<Box key={notif.id}>
<ReadMore
headerLimit={112}
messageLimit={298}
isRead={notif.isRead}
headerNotif={notif.header}
messageNotif={notif.message}
/>
</Box>
);
})}
</Box>
);
};
export default Notifications;
ReadMore.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Box, Typography } from '#material-ui/core';
const ReadMore = (props) => {
const { headerNotif, messageNotif, isRead, headerLimit, messageLimit } =
props;
const [header, setHeader] = useState(null);
const [message, setMessage] = useState(null);
const [limit, setLimit] = useState({
headLimit: headerLimit,
messLimit: messageLimit,
});
const [initialHeaderLimit] = useState(headerLimit);
const [initialMessageLimit] = useState(messageLimit);
const [showOption, setShowOption] = useState(false);
const [viewMoreClicked, setViewMoreClicked] = useState(false);
const getReadMoreContent = () => {
if (headerNotif.length > limit.headLimit) {
setHeader(<span>{headerNotif.substr(0, limit.headLimit)}...</span>);
setShowOption(true);
} else if (headerNotif.length < limit.headLimit) {
setHeader(<span>{headerNotif}</span>);
}
if (messageNotif.length > limit.messLimit) {
setMessage(
<span>
{messageNotif.substr(0, limit.messLimit)}...
{!viewMoreClicked && (
<Box sx={{ pt: 2 }}>
<span
style={{
color: '#008C44',
cursor: 'pointer',
display: 'block',
fontSize: 14,
}}
onClick={() => showLongText()}
>
<u> View More</u>
</span>
</Box>
)}
</span>
);
setShowOption(true);
} else if (messageNotif.length < limit.messLimit) {
setMessage(<span>{messageNotif}</span>);
}
return (
<span>
<Box sx={{ pb: 1 }}>
<Typography
variant="subtitle2"
style={{
fontSize: 16,
color: isRead ? '#333333' : '#008C44',
}}
>
{header}
</Typography>
</Box>
<Box sx={{ pb: 1 }}>
<Typography
variant="body2"
style={{
fontSize: 12,
color: '#666666',
}}
>
{message}
</Typography>
</Box>
<span
style={{
color: '#008C44',
cursor: 'pointer',
display: 'block',
fontSize: 14,
}}
onClick={() => showShortText()}
>
{showOption && viewMoreClicked && <u> View Less</u>}
</span>
</span>
);
};
const showLongText = () => {
setViewMoreClicked(true);
setLimit({
headLimit: headerNotif.length,
messLimit: messageNotif.length,
});
getReadMoreContent();
};
const showShortText = () => {
setViewMoreClicked(false);
setLimit({
headLimit: initialHeaderLimit,
messLimit: initialMessageLimit,
});
getReadMoreContent();
};
return <>{getReadMoreContent()}</>;
};
ReadMore.propTypes = {
headLimit: PropTypes.number,
messLimit: PropTypes.number,
};
export default ReadMore;
I ended up redoing the ReadMore.jsx to limit the use of states and did conditional returns:
import React, { useState } from 'react'
import { Box, Typography } from '#material-ui/core'
const Content = (
header,
message,
isRead,
showLongText,
showShortText,
showViewMore,
showViewLess
) => {
return (
<span>
<Box sx={{ pb: 1 }}>
<Typography
variant="subtitle2"
style={{
fontSize: 16,
color: isRead ? '#333333' : '#008C44',
}}
>
{header}
</Typography>
</Box>
<Box sx={{ pb: 1 }}>
<Typography
variant="body2"
style={{
fontSize: 12,
color: '#666666',
}}
>
{message}
</Typography>
</Box>
{showViewMore && (
<Box sx={{ pt: 2 }}>
<span
style={{
color: '#008C44',
cursor: 'pointer',
display: 'block',
fontSize: 14,
}}
onClick={() => showLongText()}
>
<u> View More</u>
</span>
</Box>
)}
{showViewLess && (
<Box sx={{ pt: 2 }}>
<span
style={{
color: '#008C44',
cursor: 'pointer',
display: 'block',
fontSize: 14,
}}
onClick={() => showShortText()}
>
<u> View Less</u>
</span>
</Box>
)}
</span>
)
}
const ReadMore = (props) => {
const { headerNotif, messageNotif, isRead, headerLimit, messageLimit } =
props
const [limit, setLimit] = useState({
headLimit: headerLimit,
messLimit: messageLimit,
})
const getReadMoreContent = () => {
if (headerNotif.length > limit.headLimit) {
if (messageNotif.length > limit.messLimit) {
return Content(
headerNotif.substr(0, limit.headLimit) + '...',
messageNotif.substr(0, limit.messLimit) + '...',
isRead,
showLongText,
showShortText,
true,
false
)
} else if (messageNotif.length < limit.messLimit) {
return Content(
headerNotif.substr(0, limit.headLimit) + '...',
messageNotif,
isRead,
showLongText,
showShortText,
true,
false
)
}
} else if (headerNotif.length < limit.headLimit) {
if (messageNotif.length > limit.messLimit) {
return Content(
headerNotif,
messageNotif.substr(0, limit.messLimit) + '...',
isRead,
showLongText,
showShortText,
true,
false
)
} else if (messageNotif.length < limit.messLimit) {
return Content(
headerNotif,
messageNotif,
isRead,
showLongText,
showShortText,
false,
false
)
}
}
return Content(
headerNotif,
messageNotif,
isRead,
showLongText,
showShortText,
false,
true
)
}
const showLongText = () => {
setLimit({
headLimit: headerNotif.length,
messLimit: messageNotif.length,
})
getReadMoreContent()
}
const showShortText = () => {
setLimit({
headLimit: headerLimit,
messLimit: messageLimit,
})
getReadMoreContent()
}
return <>{getReadMoreContent()}</>
}
export default ReadMore

How to change react-select option automatically after 5 seconds

I have created a dropdown menu in react using the react-select package, The dropdown menu is working as as expected, now I want to change the option value automatically after some seconds. After some seconds it should select another option from the option array, which option I want from the option array option.
import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import Select, { components } from "react-select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCircle, faMinusCircle } from "#fortawesome/fontawesome-free-solid";
import "./presence.css";
function Presence() {
const [presence, setPresence] = useState({
value: "unavailable",
label: "Offline",
icon: <FontAwesomeIcon icon={faCircle} color="gray" />,
});
console.log(presence);
const client = useSelector((state) => state.client.client);
const handleChange = (selectedOption) => {
setPresence(selectedOption.label);
console.log(`Option selected:`, selectedOption);
};
const { Option, SingleValue } = components;
const CustomSelectOption = (props) => (
<Option {...props}>
<div style={{ display: "inline-block", marginRight: "5%" }}>
{props.data.icon}{" "}
</div>
<div style={{ display: "inline-block" }}> {props.data.label}</div>
</Option>
);
const ValueOption = (props) => (
<SingleValue {...props}>
<span style={{ marginRight: "8%" }}> {props.data.icon}</span>
<span>{props.data.label}</span>
</SingleValue>
);
const options = [
{
value: "chat",
label: "Available",
icon: <FontAwesomeIcon icon={faCircle} color="#5cd068" />,
},
{
value: "xa",
label: "Appear Away",
icon: <FontAwesomeIcon icon={faCircle} color="orange" />,
},
{
value: "away",
label: "Be Right Back",
icon: <FontAwesomeIcon icon={faCircle} color="orange" />,
},
{
value: "dnd",
label: "Do not Disturb",
icon: <FontAwesomeIcon icon={faMinusCircle} color="red" />,
},
{
value: "unavailable",
label: "Offline",
icon: <FontAwesomeIcon icon={faCircle} color="gray" />,
},
];
const style = {
control: (base) => ({
...base,
border: 0,
boxShadow: "none",
}),
placeholder: (base) => ({
...base,
fontSize: "1em",
fontWeight: 600,
}),
};
const DropdownIndicator = (props) => {
return (
components.DropdownIndicator && (
<components.DropdownIndicator {...props}>
<FontAwesomeIcon
icon={props.selectProps.menuIsOpen ? "caret-up" : "caret-down"}
/>
</components.DropdownIndicator>
)
);
};
return (
<Select
styles={style}
name="presence"
clearable={false}
placeholder={"Choose"}
onChange={handleChange}
options={options}
classNamePrefix="select"
components={{
Option: CustomSelectOption,
SingleValue: ValueOption,
DropdownIndicator: DropdownIndicator,
IndicatorSeparator: () => null,
}}
/>
);
}
export default Presence;
If you want to execute some code after the first render only, use useEffect(() => {}, []) (notice the empty array), to execute once after 5 seconds, call the setTimeout in the callback like this:
useEffect(() => {
setTimeout(() => {
handlerChange(options.find(o => o.value === 'chat'));
}, 5000);
}, []);
You have no value defined on your <Select>
You have no getOptionValue or getOptionLabel defined (though it's probably using the defaults)
If presence were your value, all it would require is the selectedOption.value
Since presence appears to be defaulted to unavailable, then your initial value would never be away
You are rebuilding a lot of stuff on every rerender of Presence, which will force rerender your <Select> every time.
You are defaulting your presence to "unavailable", so it would never start as "away"
Maybe you're saying you will change the value by loading some record on component load. In that situation, you would put your window.setTimeout in that method. I put some notes on that in the comments below.
Refactored Presence.js:
import React, { useState, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import Select, { components } from "react-select";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faCircle, faMinusCircle } from "#fortawesome/fontawesome-free-solid";
import "./presence.css";
const { Option, SingleValue, DropdownIndicator } = components;
const CustomSelectOption = (props) => {
const {
data: { icon: Icon, label }
} = props;
return (
<Option {...props}>
<div style={{ display: "inline-block", marginRight: "5%" }}>
<Icon />{" "}
</div>
<div style={{ display: "inline-block" }}> {label}</div>
</Option>
);
};
const ValueOption = (props) => {
const {
data: { icon: Icon, label }
} = props;
return (
<SingleValue {...props}>
<span style={{ marginRight: "8%" }}>
{" "}
<Icon />
</span>
<span>{label}</span>
</SingleValue>
);
};
const Indicator = (props) => {
const icon = props.selectProps.menuIsOpen ? "caret-up" : "caret-down";
return (
<DropdownIndicator {...props}>
<FontAwesomeIcon
icon={icon}
/>
</DropdownIndicator>
);
};
const Separator = () => null;
const options = [
{
value: "chat",
label: "Available",
icon: <FontAwesomeIcon icon={faCircle} color="#5cd068" />
},
{
value: "xa",
label: "Appear Away",
icon: <FontAwesomeIcon icon={faCircle} color="orange" />
},
{
value: "away",
label: "Be Right Back",
icon: <FontAwesomeIcon icon={faCircle} color="orange" />
},
{
value: "dnd",
label: "Do not Disturb",
icon: <FontAwesomeIcon icon={faMinusCircle} color="red" />
},
{
value: "unavailable",
label: "Offline",
icon: <FontAwesomeIcon icon={faCircle} color="gray" />
}
];
const style = {
control: (base) => ({
...base,
border: 0,
boxShadow: "none"
}),
placeholder: (base) => ({
...base,
fontSize: "1em",
fontWeight: 600
})
};
const getLabel = (option) => option.label;
const getValue = (option) => option.value;
function Presence() {
const [presence, setPresence] = useState("unavailable");
console.log(presence);
const client = useSelector((state) => state.client.client);
/**
* Let's say you include some other process to 'set' `presence`,
* like loading some record or something. The you could 'init'ialize
* your component by then adding your timeout. You can't really do
* this alone in a `useEffect`, as you defaulted the value, which
* would trigger your effect.
*
window.setTimeout(() => {
// make sure to check that current value again, in case
// someone changed it in that 5 seconds
setPresence(prev => prev === 'away' ? 'chat' : prev);
}, 5000);
*/
const handleChange = (selectedOption) => {
setPresence(selectedOption?.value);
console.log(`Option selected:`, selectedOption);
};
return (
<Select
styles={style}
name="presence"
isClearable={false}
placeholder={"Choose"}
onChange={handleChange}
options={options}
classNamePrefix="select"
components={{
Option: CustomSelectOption,
SingleValue: ValueOption,
DropdownIndicator: Indicator,
IndicatorSeparator: Separator
}}
getOptionLabel={getLabel}
getOptionValue={getValue}
value={presence}
/>
);
}
export default Presence;

google.maps.places.AutoComplete suddenly stops working and then start working on refreshing the page and then again stops working on next refresh

I just started working google maps and I just want to add two points on the map and draw a direction on it and
also want to add radius so I can go for the uber algorithms.
When I first refresh the page after changing some code the google place doesn't show any of the suggestions and then on again refreshing it starts showing suggestions as I type in the field.
Here is the code for the auto complete in form.
import {
addPointA,
addPointB,
addSpecialRoute
} from "../../../redux/routesSpecial/actions";
import {
TextField,
Grid,
Checkbox,
Button,
FormControlLabel
} from "#material-ui/core";
import { makeStyles } from "#material-ui/core/styles";
import Map from "views/Maps/Maps";
import { connect } from "react-redux";
const style = makeStyles(theme => ({
container: {
flexDirection: "row",
display: "flex",
flexWrap: "wrap"
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
width: 500
},
dense: {
marginTop: 19
},
menu: {
width: 200
},
button: {
height: 40,
width: 100,
marginTop: 50,
left: 50,
backgroundColor: "black"
},
text: {
color: "white"
}
}));
class addSpecialRoutes extends PureComponent {
static propTypes = {};
constructor(props) {
super(props);
this.state = {
pointA: "",
pointB: "",
address: "",
send: false
};
this.fillInAddressA = this.fillInAddressA.bind(this);
this.fillInAddressB = this.fillInAddressB.bind(this);
}
componentDidMount() {
this.initAutocompleteA();
this.initAutocompleteB();
}
initAutocompleteA() {
const input = document.getElementById("pointAPlaces");
const google = window.google;
this.autocompleteA = new google.maps.places.Autocomplete(input, {
types: [{ region: "locality" }]
});
this.autocompleteA.addListener("place_changed", this.fillInAddressA);
}
geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
const geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
console.log(geolocation);
});
}
}
fillInAddressA() {
const componentForm = {
street_number: "short_name",
route: "long_name",
locality: "long_name",
administrative_area_level_1: "short_name",
country: "long_name",
postal_code: "short_name"
};
// Get the place details from the autocomplete object.
this.placeA = this.autocompleteA.getPlace();
/*this.setState({placeResult: this.place.address_components})*/
this.setState({
address: this.placeA
});
this.getAddressA(this.placeA.formatted_address);
for (let component in this.componentForm) {
this.refs.component.value = "";
this.refs.component.disabled = false;
}
// Get each component of the address from the place details
// and fill the corresponding field on the form.
if (this.placeA.address_components) {
this.placeA.address_components.forEach((component, index) => {
const addressType = this.placeA.address_components[index].types[0];
if (componentForm[addressType]) {
const val = this.placeA.address_components[index][
componentForm[addressType]
];
console.log(val);
}
});
}
}
getAddressA(val) {
var OK = window.google.maps.GeocoderStatus.OK;
var geocoder = new window.google.maps.Geocoder();
geocoder.geocode({ address: val }, (results, status) => {
if (status !== OK) {
console.log(status);
} else {
console.log(results);
var latLng = {
lat: results[0].geometry.location.lat(),
lng: results[0].geometry.location.lng()
};
console.log(results, latLng);
this.setState({
pointA: { formatted_address: val, position: latLng }
});
}
});
}
initAutocompleteB() {
console.log(this.refs);
const input = document.getElementById("autoCompletePlacesPointB");
console.log(input);
const google = window.google;
this.autocomplete = new google.maps.places.Autocomplete(input, {
types: ["geocode"]
});
this.autocomplete.addListener("place_changed", this.fillInAddressB);
}
fillInAddressB() {
const componentForm = {
street_number: "short_name",
route: "long_name",
locality: "long_name",
administrative_area_level_1: "short_name",
country: "long_name",
postal_code: "short_name"
};
// Get the place details from the autocomplete object.
this.place = this.autocomplete.getPlace();
/*this.setState({placeResult: this.place.address_components})*/
this.setState({
address: this.place
});
this.getAddressB(this.place.formatted_address);
for (let component in this.componentForm) {
this.refs.component.value = "";
this.refs.component.disabled = false;
}
// Get each component of the address from the place details
// and fill the corresponding field on the form.
if (this.place.address_components) {
this.place.address_components.forEach((component, index) => {
const addressType = this.place.address_components[index].types[0];
if (componentForm[addressType]) {
const val = this.place.address_components[index][
componentForm[addressType]
];
console.log(val);
}
});
}
}
getAddressB(val) {
var OK = window.google.maps.GeocoderStatus.OK;
var geocoder = new window.google.maps.Geocoder();
geocoder.geocode({ address: val }, (results, status) => {
if (status !== OK) {
console.log(status);
} else {
console.log(results);
var latLng = {
lat: results[0].geometry.location.lat(),
lng: results[0].geometry.location.lng()
};
this.setState({
pointB: { formatted_address: val, position: latLng },
send: true
});
console.log(results, latLng);
}
});
}
addCirclePointA(e) {
console.log(e);
this.setState({
pointA: { ...this.state.pointA, circle: e }
});
}
addCirclePointB(e) {
this.setState({
pointB: { ...this.state.pointB, circle: e }
});
}
render() {
console.log(this.state.pointA, this.state.pointB);
const classes = style;
return (
<Grid container style={{ height: "500px" }}>
<Grid item xs={3}>
<form className={classes.container}>
{/* <TextField
style={{ width: "300px" }}
id="pointAPlaces"
label="Point A"
type="search"
margin="normal"
/> */}
<input id="pointAPlaces" />
<br />
<TextField
onChange={e => this.addCirclePointA(e.target.valueAsNumber)}
label="Radius"
type="number"
className={classes.textField}
InputLabelProps={{
shrink: true
}}
margin="normal"
/>
<br />
<TextField
style={{ width: "300px" }}
id="autoCompletePlacesPointB"
label="Point B"
type="search"
margin="normal"
/>
<br />
<TextField
onChange={e => this.addCirclePointB(e.target.value)}
label="Radius"
type="number"
className={classes.textField}
InputLabelProps={{
shrink: true
}}
margin="normal"
/>
<br />
<TextField
className={classes.formControl}
label="Fare"
id="formatted-numberformat-input"
// InputProps={{
// inputComponent: NumberFormatCustom
// }}
/>
<br />
<FormControlLabel
style={{ marginTop: "20px" }}
control={<Checkbox checked={true} color="primary" />}
label="Same charges apply for revert"
/>
</form>
<div>
<Button
style={{
width: "100px",
height: "40px",
backgroundColor: "black",
margin: "20px"
}}
>
<p style={{ color: "white" }}>Add</p>
</Button>
</div>
</Grid>
<Grid item xs={9}>
<Map
polyline={
this.state.send
? { pointA: this.state.pointA, pointB: this.state.pointB }
: null
}
/>
</Grid>
</Grid>
);
}
}
const mapStateToProps = state => {
return {
countryCode: state.App.countryCode,
pointA: state.SpecialRoutes.pointA,
pointB: state.SpecialRoutes.pointB
};
};
export default connect(
mapStateToProps,
{ addPointA, addPointB, addSpecialRoute }
)(addSpecialRoutes);
basically the problem was with react-google-maps, that is depreciated and i skip it and start using react-google-maps/api

Material UI TextField Not Showing Value

I have a form in react where I'm using a Material UI TextField. I want a value present in the TextField and I can see that it is being set when I use the Inspect option of Google Chrome. In addition to that, I see that the type="hidden". I have changed this to type="text" and to no avail, the value is still not presented. Curious if anyone here has any understanding as to why this would happen. Below is the primary code that is causing the problem:
<TextField
name="Property"
select
fullWidth
margin="normal"
className={clsx(selectInputStyle.margin, selectInputStyle.textField, selectInputStyle.root)}
value={viewProperties[index].name}
onChange={this.handleSelectChange}
>
{propertyKeys.map((key, index) => (
<MenuItem value={key} key={index}>
{key}
</MenuItem>
))}
</TextField>
Here is the full code file just for a complete context of what is going on.
import React, { Component } from 'react';
import { Container, Form, Button, Row, Col, Nav, NavItem, NavLink, Input, FormGroup } from 'reactstrap';
import { connect } from 'react-redux';
import { reduxForm, FieldArray, arrayRemoveAll } from 'redux-form/immutable';
import * as Immutable from 'immutable';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import BlockUi from 'react-block-ui';
import MaterialButton from '#material-ui/core/Button';
import DeleteIcon from '#material-ui/icons/Delete';
import { makeStyles } from '#material-ui/core/styles';
import AppBar from '#material-ui/core/AppBar';
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
import MenuItem from '#material-ui/core/MenuItem';
import Select from '#material-ui/core/Select';
import InputMaterial from '#material-ui/core/Input';
import FormControl from '#material-ui/core/FormControl';
import TextField from '#material-ui/core/TextField';
import OutlinedInput from '#material-ui/core/OutlinedInput';
import clsx from 'clsx';
import { AvText, AvSelect } from '../forms/components';
import { showNotification, loadPayerProperties, updatePayerProperties } from '../actions';
class PayerPropertiesEditor extends Component {
constructor(props) {
super(props);
this.uploadRef = React.createRef();
this.state = {
errors: [],
refeshProperties: false,
blocking: false
};
this.showButton = false;
this.divPadding = { padding: '20px' };
this.doSubmit = this.doSubmit.bind(this);
this.handleInvalidSubmit = this.handleInvalidSubmit.bind(this);
this.renderProperties = this.renderProperties.bind(this);
this.handleSelectChange = this.handleSelectChange.bind(this);
this.useStyles = makeStyles(theme => ({
button: {
margin: theme.spacing(1)
},
leftIcon: {
marginRight: theme.spacing(1)
},
rightIcon: {
marginLeft: theme.spacing(1)
},
iconSmall: {
fontSize: 20
},
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
input: {
margin: theme.spacing(1)
}
}));
}
componentDidMount() {
this.setState({ view: 'payer' });
}
componentDidUpdate(prevProps) {
const { loadPayerProperties } = this.props;
if (this.state.refeshProperties) {
this.props.arrayRemoveAll('payerPropertiesEditorForm', 'properties');
loadPayerProperties();
this.setState({ refeshProperties: false });
this.setState({ blocking: false });
this.showButton = false;
}
if (!prevProps.properties && this.props.properties) {
this.props.change('properties', Immutable.fromJS(this.props.properties));
}
}
doSubmit(values) {
const { updatePayerProperties } = this.props;
return new Promise(resolve => {
this.setState({
blocking: true
});
updatePayerProperties(values.toJS(), () => {
this.setState({ refeshProperties: true });
});
resolve();
});
}
handleInvalidSubmit() {
this.props.showNotification({
level: 'error',
message: 'Errors were found.'
});
}
handleSelectChange(event) {
console.log(event);
}
renderProperties({ fields }) {
const inputUseStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
},
container: {
display: 'flex',
flexWrap: 'wrap'
},
margin: {
margin: theme.spacing(1)
},
textField: {
flexBasis: 200
},
input: {
margin: theme.spacing(1)
}
}));
const selectInputStyle = inputUseStyles().input;
const useStyles = this.useStyles();
const { formProperties, unsetPropertiesKeys } = this.props;
const viewProperties = formProperties[`${this.state.view}`];
const propertyKeys = unsetPropertiesKeys[`${this.state.view}`];
return (
<div maxWidth="sm" className={selectInputStyle.root}>
{fields.map((property, index) => (
<Row
key={index}
style={{
display: viewProperties[index].action === 'remove' ? 'none' : ''
}}
>
<Col xs={5}>
<TextField
select
fullWidth
margin="normal"
className={clsx(selectInputStyle.margin, selectInputStyle.textField, selectInputStyle.root)}
value={viewProperties[index].name}
onChange={this.handleSelectChange}
>
{propertyKeys.map((key, index) => (
<MenuItem value={key} key={index}>
{key}
</MenuItem>
))}
</TextField>
</Col>
<Col xs={5}>
<AvText
name={`${property}.value`}
onChange={() => {
if (viewProperties[index].action !== 'add') {
this.props.change(`${property}.action`, 'update');
this.showButton = true;
}
}}
/>
</Col>
<Col xs={1}>
<MaterialButton
variant="contained"
className="{classes.button}"
onClick={() => {
fields.remove(index);
if (viewProperties[index].action !== 'add') {
fields.insert(
index,
Immutable.fromJS(Object.assign({}, viewProperties[index], { action: 'remove' }))
);
this.showButton = true;
}
}}
>
Delete
<DeleteIcon className={useStyles.rightIcon} />
</MaterialButton>
</Col>
</Row>
))}
<Row>
<Col xs={12}>
<Button
color="primary"
onClick={() => {
fields.push(
Immutable.fromJS({
owner: this.props.ownerKeys[this.state.view],
action: 'add'
})
);
this.showButton = true;
}}
>
Add Property
</Button>
</Col>
</Row>
<br />
{this.showButton === true && (
<Row>
<Col xs={12}>
<Button color="primary" type="submit">
Submit
</Button>
</Col>
</Row>
)}
</div>
);
}
render() {
const { handleSubmit, properties, payerName, chsId, parentChsId } = this.props;
const formStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap'
},
formControl: {
margin: theme.spacing(1),
minWidth: 120
},
selectEmpty: {
marginTop: theme.spacing(2)
}
}));
const navItems = ['payer', 'clearinghouse', 'parent'].map(key => (
<Tab
textColor="primary"
key={key}
label={
key === 'payer'
? 'PAYER: ' + payerName
: key === 'clearinghouse'
? 'CLEARING HOUSE: ' + chsId
: 'PARENT: ' + parentChsId
}
onClick={() => this.setState({ view: key })}
/>
));
const overrides = this.state.view === 'payer' ? ['clearinghouse'] : this.state.view === 'parent' ? ['parent'] : [];
const readonly = properties
? overrides
.filter(key => key !== this.state.view)
.map(key => properties[key])
.reduce((acc, val) => acc.concat(val), [])
.map((property, idx) => {
return (
<Row key={idx}>
<Col xs={5}>
<FormGroup>
<Input value={property.name} disabled />
</FormGroup>
</Col>
<Col xs={5}>
<FormGroup>
<Input value={property.value} disabled />
</FormGroup>
</Col>
</Row>
);
})
: [];
return (
<BlockUi tag="div" blocking={this.state.blocking} className="my-2">
<Container maxWidth="sm">
<AppBar position="static" color="default">
<Tabs variant="fullWidth" textColor="primary">
{navItems}
</Tabs>
</AppBar>
<FormControl
fullWidth
className="mt-4"
onSubmit={handleSubmit(this.doSubmit)}
ref={form => (this.formRef = form)}
>
{readonly}
<FieldArray
name={`properties.${this.state.view}`}
component={this.renderProperties}
rerenderOnEveryChange
/>
</FormControl>
</Container>
</BlockUi>
);
}
}
const mapStateToProps = state => {
const {
payerPropertiesStore: {
payer: { payerId, payerName, chsId, parentChsId },
properties,
propertyKeys
},
form: {
payerPropertiesEditorForm: {
values: { properties: formProperties }
}
}
} = state.toJS();
const unsetPropertiesKeys = {};
for (const view of ['payer', 'clearinghouse', 'parent']) {
unsetPropertiesKeys[view] = propertyKeys.filter(key => !_.find(formProperties[view], { name: key }));
}
const ownerKeys = { payer: payerId, clearinghouse: chsId, parent: parentChsId };
return { formProperties, properties, ownerKeys, unsetPropertiesKeys, payerId, payerName, chsId, parentChsId };
};
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
showNotification,
loadPayerProperties,
updatePayerProperties,
arrayRemoveAll
},
dispatch
);
export default reduxForm({
form: 'payerPropertiesEditorForm',
enableReinitialize: true,
initialValues: Immutable.fromJS({ properties: {} })
})(
connect(
mapStateToProps,
mapDispatchToProps
)(PayerPropertiesEditor)
);
In the TextField you are setting value from viewProperties like,
value={viewProperties[index].name}
You are getting data in viewProperties from formProperties based on this.state.view
const viewProperties = formProperties[`${this.state.view}`];
As you are setting view state in componentDidMount, on intial render view is not set and don't have value so you are not getting any value from formProperties.
You need to have a default state,
this.state = {
errors: [],
refeshProperties: false,
blocking: false,
view: 'payer' // default set
};

How to update other component when change in single component

I am creating timer component and implement with every task. So when I start my timer for a single task then other task timer will be disabled or hidden. I am trying to disable other timer component on start timer but it gives me only value for current component. So how can I update all components when I start a single timer?
DeveloperTasks.js
import { Mutation, Query } from "react-apollo";
import gql from "graphql-tag";
import React, { Component, Fragment } from "react";
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import TaskTimer from "./TaskTimer";
import Note from "./Note";
import getCDTime from "../util/commonfunc";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import Paper from "#material-ui/core/Paper";
import List from "#material-ui/core/List";
import ListItem from "#material-ui/core/ListItem";
import ListItemText from "#material-ui/core/ListItemText";
import CircularProgress from "#material-ui/core/CircularProgress";
import Avatar from "#material-ui/core/Avatar";
import FormControl from "#material-ui/core/FormControl";
import InputLabel from "#material-ui/core/InputLabel";
import Select from "#material-ui/core/Select";
import MenuItem from "#material-ui/core/MenuItem";
import TextField from "#material-ui/core/TextField";
import Dialog from "#material-ui/core/Dialog";
import MuiDialogTitle from "#material-ui/core/DialogTitle";
import MuiDialogContent from "#material-ui/core/DialogContent";
import MuiDialogActions from "#material-ui/core/DialogActions";
import Popover from "#material-ui/core/Popover";
import DeleteIcon from "#material-ui/icons/Delete";
import AssignmentIcon from "#material-ui/icons/Assignment";
import NotesIcon from "#material-ui/icons/EventNote";
import AssignmentInd from "#material-ui/icons/AssignmentInd";
import CheckCircleOutline from "#material-ui/icons/CheckCircleOutline";
import CheckCircle from "#material-ui/icons/CheckCircle";
import CloseIcon from "#material-ui/icons/Close";
import Typography from "#material-ui/core/Typography";
import EditIcon from "#material-ui/icons/Edit";
import DateFnsUtils from "#date-io/date-fns";
import {
MuiPickersUtilsProvider,
TimePicker,
DatePicker
} from "material-ui-pickers";
import UserList from "../components/UserList";
import emails from "../components/UserList";
import TodoInlineForm from "../components/TodoInlineForm";
const ms = require("pretty-ms");
//Kanban Quearies
export const tasksQuery = gql`
query Developertasklist($contact_id_c: String) {
Developertasklist(contact_id_c: $contact_id_c) {
id
name
due_date
dtask_start_time
time_tracking_flag
dtask_total_time
status
}
}
`;
//Delete Task Mutation
export const DELETE_TODO = gql`
mutation todo_operations($id: String, $deleted: String) {
todo_operations(id: $id, deleted: $deleted) {
id
}
}
`;
//Complete Task Mutation
const COMPLETE_TASK_OPERATIONS = gql`
mutation todo_operations(
$id: String
$status: String
$actual_due_date: String
) {
todo_operations(
id: $id
status: $status
actual_due_date: $actual_due_date
) {
id
}
}
`;
const styles = theme => ({
root: {
width: "100%",
marginTop: theme.spacing(3),
overflowX: "auto"
},
icon: {
margin: theme.spacing.unit,
fontSize: 20
},
button: {
margin: theme.spacing.unit
},
listroot: {
width: "100%",
minWidth: 900,
backgroundColor: theme.palette.background.paper
},
tasklist: {
marginTop: 30
},
taskwidth: {
width: "55%",
display: "inline-flex"
},
timerwidth: {
width: "25%"
},
width5: {
width: "5%"
},
margin: {
margin: theme.spacing.unit
},
input: {
display: "none"
},
datepadding: {
"padding-right": "10px;",
width: "17%"
},
formControl: {
minWidth: 120
},
elementpadding: {
"padding-right": "10px;"
},
completeIcon: {
color: "Green"
},
popover: {
pointerEvents: "none"
},
label: {
display: "inline",
padding: ".2em .6em .3em",
"font-size": "75%",
"font-weight": "700",
"line-height": 1,
color: "#fff",
"text-align": "center",
"white-space": "nowrap",
"vertical-align": "baseline",
"border-radius": ".25em"
},
labelcomplete: {
"background-color": "#5cb85c"
},
labelprogress: {
"background-color": "#5bc0de"
},
labelonhold: {
"background-color": "#d9534f"
},
labelqafail: {
"background-color": "#d9534f"
},
labelnotstated: {
"background-color": "#777"
},
labelqa: {
"background-color": "#337ab7"
},
labelqapassed: {
"background-color": "#777"
},
labeldefered: {
"background-color": "#f0ad4e"
},
hideelement: {
display: "none"
},
showelement: {
display: "block"
}
});
const DialogTitle = withStyles(theme => ({
root: {
borderBottom: `1px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing.unit * 2
},
closeButton: {
position: "absolute",
right: theme.spacing.unit,
top: theme.spacing.unit,
color: theme.palette.grey[500]
}
}))(props => {
const { children, classes, onClose } = props;
return (
<MuiDialogTitle disableTypography className={classes.root}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="Close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
});
const DialogContent = withStyles(theme => ({
root: {
margin: 0,
padding: theme.spacing.unit * 2
}
}))(MuiDialogContent);
const DialogActions = withStyles(theme => ({
root: {
borderTop: `1px solid ${theme.palette.divider}`,
margin: 0,
padding: theme.spacing.unit
}
}))(MuiDialogActions);
class DeveloperTasks extends React.Component {
state = {
start_date: new Date(),
end_date: new Date(),
status: "",
task: "",
searchTerm: "",
open: false,
anchorEl: null,
selectedValue: emails[1],
openreport: false,
openTodoForm: false,
taskid: ""
};
constructor(props) {
super(props);
this.searchUpdated = this.searchUpdated.bind(this);
}
handleDateChange = name => date => {
this.setState({ [name]: date });
};
handleChange = name => event => {
this.setState({ [name]: event.target.value });
};
handleClickOpen = name => event => {
this.setState({
open: true
});
};
handleClose = () => {
this.setState({ open: false });
};
handleClickDialogOpen = () => {
this.setState({ openreport: true });
};
handleDialogClose = value => {
this.setState({ selectedValue: value, openreport: false });
};
searchUpdated(term) {
this.setState({ searchTerm: term });
}
handlePopoverOpen = event => {
this.setState({ anchorEl: event.currentTarget });
};
handlePopoverClose = () => {
this.setState({ anchorEl: null });
};
handleClickTodoOpen(taskid) {
this.setState({ taskid: taskid, openTodoForm: true });
}
componentWillReceiveProps(newProps) {
this.setState({ openTodoForm: newProps.open });
}
render() {
let todoinlineform = "";
const { classes, contact_id } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
let currdatetime = getCDTime.getCurrentDateTime();
let shownbutton = {
display: "block"
};
if (
this.state.openTodoForm &&
this.state.openTodoForm === true &&
this.state.taskid != ""
) {
todoinlineform = (
<TodoInlineForm
open={this.state.openTodoForm}
taskid={this.state.taskid}
modaltitle="Edit Todo"
/>
);
}
return contact_id === "" ? (
""
) : (
<Query query={tasksQuery} variables={{ contact_id_c: contact_id }}>
{({ loading, error, data: { Developertasklist } }) => {
if (error) return <p>{error}</p>;
if (loading) return <CircularProgress className={classes.progress} />;
//Filter with task name
if (this.state.task && this.state.task != "") {
Developertasklist = Developertasklist.filter(
developertasklist =>
developertasklist.name
.toLowerCase()
.indexOf(this.state.task.toLowerCase()) != -1
);
}
//Task status wise filter
if (this.state.status && this.state.status != "") {
Developertasklist = Developertasklist.filter(
developertasklist => developertasklist.status == this.state.status
);
}
//Label array for apply class on status label
let labelcolor = [
{ status: "In Progress", class: classes.labelprogress },
{ status: "Completed", class: classes.labelcomplete },
{ status: "On Hold", class: classes.labelonhold },
{ status: "QA Fail", class: classes.labelqafail },
{ status: "Not Started", class: classes.labelnotstated },
{ status: "QA", class: classes.labelqa },
{ status: "QA Passed", class: classes.labelqapassed },
{ status: "Deferred", class: classes.labeldefered }
];
return (
<Fragment>
<br />
<div className={classes.tasklist}>
<div className="picker">
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<DatePicker
label="Start Date"
name="start_date"
value={this.state.start_date}
format="yyyy-MM-dd"
onChange={this.handleDateChange("start_date")}
className={classes.datepadding}
animateYearScrolling
/>
<DatePicker
label="End Date"
name="end_date"
value={this.state.end_date}
format="yyyy-MM-dd"
onChange={this.handleDateChange("end_date")}
className={classes.datepadding}
animateYearScrolling
/>
</MuiPickersUtilsProvider>
<Button
type="submit"
variant="contained"
color="primary"
className={classes.button}
>
Search
</Button>
<Button
variant="contained"
color="secondary"
className={classes.button}
>
Reset
</Button>
</div>
<FormControl className={classes.formControl}>
<InputLabel htmlFor="status-simple">Status</InputLabel>
<Select
value={this.state.status}
onChange={this.handleChange("status")}
className={classes.elementpadding}
inputProps={{
name: "status",
id: "status"
}}
>
<MenuItem value="">
<em>Please Select</em>
</MenuItem>
<MenuItem value="Not Started">Not Started</MenuItem>
<MenuItem value="In Progress">In Progress</MenuItem>
<MenuItem value="On Hold">On Hold</MenuItem>
<MenuItem value="Deferred">Deferred</MenuItem>
<MenuItem value="Completed">Completed</MenuItem>
<MenuItem value="QA">QA</MenuItem>
<MenuItem value="QA Passed">QA Passed</MenuItem>
<MenuItem value="QA Fail">QA Fail</MenuItem>
</Select>
</FormControl>
<TextField
id="standard-name"
label="Task"
className={classes.textField}
value={this.state.task}
onChange={this.handleChange("task")}
/>
</div>
<div className={classes.tasklist}>
<Paper className={classes.listroot}>
<List className={classes.listroot}>
{Developertasklist.map((task, index) => {
let statusLabel = labelcolor.filter(
obj => obj.status == task.status
);
let completeStatusClass = classes.hideelement;
let pendingStatusClass = "";
let hidetimer = "";
if (task.status === "Completed") {
pendingStatusClass = classes.hideelement;
hidetimer = "hide";
completeStatusClass = "";
}
if (statusLabel.length > 0)
statusLabel = statusLabel[0].class;
return (
<ListItem key={index} divider="true">
<div className={classes.taskwidth}>
<Avatar>
<AssignmentIcon />
</Avatar>
<ListItemText
primary={
<React.Fragment>
{task.name} - {task.due_date}
</React.Fragment>
}
secondary={
<React.Fragment>
<Typography
component="span"
className={[classes.label, statusLabel]}
color="textPrimary"
>
{task.status}
</Typography>
</React.Fragment>
}
/>
</div>
<div className={classes.timerwidth}>
<div>
<TaskTimer
developerlist={task}
hidetimer={hidetimer}
/>
</div>
</div>
<div className={classes.width5}>
<EditIcon
onClick={event => {
this.handleClickTodoOpen(task.id);
}}
/>
</div>
<div className={classes.width5}>
<Mutation mutation={COMPLETE_TASK_OPERATIONS}>
{(todo_operations, { loading, error }) => (
<CheckCircleOutline
className={pendingStatusClass}
aria-owns={
open ? "mouse-over-popover" : undefined
}
aria-haspopup="true"
onMouseEnter={this.handlePopoverOpen}
onMouseLeave={this.handlePopoverClose}
onClick={event => {
todo_operations({
variables: {
id: task.id,
actual_due_date: currdatetime,
status: "Completed"
}
});
}}
/>
)}
</Mutation>
<Popover
id="mouse-over-popover"
className={classes.popover}
classes={{
paper: classes.paper
}}
open={open}
anchorEl={anchorEl}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
onClose={this.handlePopoverClose}
disableRestoreFocus
>
<Typography>Mark as completed.</Typography>
</Popover>
<CheckCircle
className={[
classes.completeIcon,
completeStatusClass
]}
/>
</div>
<div className={classes.width5}>
<div className={pendingStatusClass}>
{/* <Typography variant="subtitle1">
Selected: {this.state.selectedValue}
</Typography> */}
<AssignmentInd
onClick={this.handleClickDialogOpen}
/>
<UserList
selectedValue={this.state.selectedValue}
open={this.state.openreport}
onClose={this.handleDialogClose}
/>
</div>
</div>
<div className={classes.width5}>
<NotesIcon onClick={this.handleClickOpen()} />
<Dialog
onClose={this.handleClose}
aria-labelledby="customized-dialog-title"
open={this.state.open}
>
<DialogTitle
id="customized-dialog-title"
onClose={this.handleClose}
>
Notes
</DialogTitle>
<DialogContent>
<Note />
</DialogContent>
</Dialog>
</div>
<div className={classes.width5}>
<Mutation mutation={DELETE_TODO}>
{(todo_operations, { loading, error }) => (
<DeleteIcon
aria-label="Delete"
onClick={event => {
todo_operations({
variables: {
id: task.id,
deleted: "1"
}
});
}}
/>
)}
</Mutation>
</div>
</ListItem>
);
})}
</List>
</Paper>
</div>
{todoinlineform}
</Fragment>
);
}}
</Query>
);
}
}
export default withStyles(styles, { withTheme: true })(DeveloperTasks);
TaskTimer.js
import { Mutation, Query } from "react-apollo";
import gql from "graphql-tag";
const React = require("react");
const ms = require("pretty-ms");
import PropTypes from "prop-types";
import { withStyles } from "#material-ui/core/styles";
import StartIcon from "#material-ui/icons/PlayCircleFilled";
import StopIcon from "#material-ui/icons/Stop";
import getCDTime from "../util/commonfunc";
//Start timer mutation
const TODO_OPERATIONS = gql`
mutation todo_operations(
$id: String
$status: String
$dtask_start_time: String
$time_tracking_flag: String
$developer_daily_hours: String
$is_task_started: String
$actual_start_date: String
) {
todo_operations(
id: $id
status: $status
dtask_start_time: $dtask_start_time
time_tracking_flag: $time_tracking_flag
developer_daily_hours: $developer_daily_hours
is_task_started: $is_task_started
actual_start_date: $actual_start_date
) {
id
}
}
`;
//Stop timer mutation
const STOP_TIMER = gql`
mutation todo_operations(
$id: String
$dtask_stop_time: String
$dtask_total_time: String
$time_tracking_flag: String
) {
todo_operations(
id: $id
dtask_stop_time: $dtask_stop_time
dtask_total_time: $dtask_total_time
time_tracking_flag: $time_tracking_flag
) {
id
}
}
`;
const styles = theme => ({
button: {
margin: theme.spacing.unit
},
stopbutton: {
margin: theme.spacing.unit,
color: "Red"
},
input: {
display: "none"
},
clock: {
color: "Green",
fontWeight: "700",
fontSize: "15px"
},
hideelement: {
display: "none"
},
timerClass: {
display: "none"
}
});
class TaskTimer extends React.Component {
constructor(props) {
const total_time = !props.developerlist.dtask_total_time
? parseInt(0)
: parseInt(props.developerlist.dtask_total_time);
let statetime = total_time;
let stateison = false;
super(props);
if (props.developerlist.time_tracking_flag === "yes") {
let currentdatetime = new Date(getCDTime.getCurrentDateTime());
let start_time = new Date(props.developerlist.dtask_start_time);
let time_diff = Math.abs(currentdatetime - start_time) / 1000;
statetime = time_diff + total_time;
stateison = true;
this.state = {
time: statetime,
isOn: stateison
};
this.startTimer();
}
this.state = {
time: statetime,
isOn: stateison,
timerClass: "test"
};
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
}
startTimer(next) {
this.setState({
isOn: true,
time: this.state.time,
timerClass: "abc"
});
this.timer = setInterval(
() =>
this.setState({
time: parseInt(this.state.time + 1)
}),
1000
);
}
stopTimer() {
this.state.time = parseInt(this.state.time);
this.setState({ isOn: false });
clearInterval(this.timer);
}
render() {
let totalTaskTime = parseInt(this.state.time) * 1000;
const { classes, theme } = this.props;
let hideTimerClass =
this.props.hidetimer === "hide" ? classes.hideelement : "";
let currdatetime = getCDTime.getCurrentDateTime();
let start =
(this.state.time == 0 || this.state.time > 0) && this.state.isOn == 0 ? (
<Mutation mutation={TODO_OPERATIONS}>
{(todo_operations, { loading, error }) => (
<StartIcon
variant="contained"
color="primary"
className={[
classes.button,
hideTimerClass,
this.state.timerClass
]}
//className={this.state.timerClass}
onClick={event => {
this.startTimer();
todo_operations({
variables: {
id: this.props.developerlist.id,
status: "In Progress",
dtask_start_time: currdatetime,
time_tracking_flag: "yes",
developer_daily_hours: dailyhours,
is_task_started: "yes",
actual_start_date: currdatetime
}
});
}}
/>
)}
</Mutation>
) : null;
let stop =
this.state.isOn && this.state.isOn == 1 ? (
<Mutation mutation={STOP_TIMER}>
{(todo_operations, { loading, error }) => (
<StopIcon
variant="contained"
className={[classes.stopbutton, hideTimerClass]}
disabled={true}
onClick={event => {
this.stopTimer();
todo_operations({
variables: {
id: this.props.developerlist.id,
dtask_stop_time: currdatetime,
dtask_total_time: this.state.time,
time_tracking_flag: "stop"
}
});
}}
/>
)}
</Mutation>
) : null;
return (
<div>
<div className={classes.clock}>{ms(totalTaskTime)}</div>
{start}
{stop}
</div>
);
}
}
export default withStyles(styles, { withTheme: true })(TaskTimer);
if I'm understanding your problem correctly you are trying to cause an update in sister components when a certain event happens.
I believe that the best way to do this would be to have a state in your parent component (DeveloperTasks) that holds whether or not each timer should be disabled. Then pass a callback into each timer that would update the disabled list in the way that you're looking for.
The way I'm imagining such a callback would work is:
function getDisableOtherTimersCallback(timerNum) {
return timerNum => {
return this.state.setState({disabledList: disabledList.map((value, index) => index == timerNum)})
// this line just goes through and makes sure that the index corresponding to timerNum is the only one that's true
};
}
Then when you render your component for timer n you would pass in the timer you get for timer n.
<TaskTimer
developerlist={task}
hidetimer={this.state.disabledList[n]}
disableOtherTimersCallback={getDisableOtherTimersCallback(n)}
/>
Then whenever you want to disable the other timers you would call this method and your done!
(Also note that you can now use the disabledList to show or hide each timer!)
(Also also note that you need to add the disabledList to your state and initiate it in the way you are looking for)
Hope this helps!

Resources