Show SnackBar Material UI when appear erron in Mutation - reactjs

I use Snack bar from Materia-UI page (first example - Customized SnackBars)
const variantIcon = {
success: CheckCircleIcon,
warning: WarningIcon,
error: ErrorIcon,
info: InfoIcon,
};
const styles1 = theme => ({
success: {
backgroundColor: green[600],
},
error: {
backgroundColor: theme.palette.error.dark,
},
info: {
backgroundColor: theme.palette.primary.dark,
},
warning: {
backgroundColor: amber[700],
},
icon: {
fontSize: 20,
},
iconVariant: {
opacity: 0.9,
marginRight: theme.spacing.unit,
},
message: {
display: 'flex',
alignItems: 'center',
},
});
function MySnackbarContent(props) {
const { classes, className, message, onClose, variant, ...other } = props;
const Icon = variantIcon[variant];
return (
<SnackbarContent
className={classNames(classes[variant], className)}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<Icon className={classNames(classes.icon, classes.iconVariant)} />
{message}
</span>
}
action={[
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={onClose}
>
<CloseIcon className={classes.icon} />
</IconButton>,
]}
{...other}
/>
);
}
MySnackbarContent.propTypes = {
classes: PropTypes.object.isRequired,
className: PropTypes.string,
message: PropTypes.node,
onClose: PropTypes.func,
variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired,
};
const MySnackbarContentWrapper = withStyles(styles1)(MySnackbarContent);
const styles2 = theme => ({
margin: {
margin: theme.spacing.unit,
},
});
class CustomizedSnackbar extends React.Component {
state = {
open: false,
};
handleClick = () => {
this.setState({ open: true });
};
handleClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
this.setState({ open: false });
};
render() {
return (
<div>
<Snackbar
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
open={this.state.open}
autoHideDuration={2000}
onClose={this.handleClose}
>
<MySnackbarContentWrapper
onClose={this.handleClose}
variant="error"
message="This is an error message!"
/>
</Snackbar>
</div>
);
}
}
export default withStyles(styles2)(CustomizedSnackbar);
In the example the snack bar is shown when click on button "OPEN SUCCESS SNACKBAR"
I would like to show the error snack bar when Mutation from Apollo on my Form gives an error.
render(){
return(
<div>
<Mutation
mutation={this.mutationQuery}
onError={() =>
//here show Snack Bar
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
)}
The problem is I dont know how to trigger to show the SnackBar in the on Error function. How to change state of Snack Bar? I was trying the solution from here, but I receive an error that
openSnackbarFn is not a function
Thanks in advance.

Fundamentally, you want your Snackbar to be a sibling of your Mutation, and let their common parent (i.e. your component) handle the Snackbar open/closed state.
Class-style component
class FormWithMutationAndSnackbar extends React.Component {
state = {
open: false
}
handleOpen = () => this.setState({ open: true })
handleClose = () => this.setState({ open: false })
render() {
const { open } = this.state
return(
<React.Fragment>
<Mutation
mutation={this.mutationQuery}
onError={(err) => {
// use err to set Snackbar contents for example
this.handleOpen()
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
</Mutation>
<Snackbar
open={open}
onClose={this.handleClose}
// other Snackbar props
>
// Snackbar contents
</Snackbar>
</React.Fragment>
)
}
}
Functional component with Hooks
const FormWithMutationAndSnackbar = () => {
const [open, setOpen] = useState(false)
return(
<React.Fragment>
<Mutation
mutation={this.mutationQuery}
onError={(err) => {
// use err to set Snackbar contents for example
setOpen(true)
}
onCompleted={data => { console.log(data); }}
>
{mutation => (
//here is the form
)}
</Mutation>
<Snackbar
open={open}
onClose={() => setOpen(false)}
// other Snackbar props
>
// Snackbar contents
</Snackbar>
</React.Fragment>
)
}

Related

Can i read uploaded excel sheet file and loop through its items to update a SharePoint site column choices inside our SPFx

We have the following Panel to manually add/delete choices inside a SharePoint column named Category:-
and here is the related .tsx code for the above Panel:-
import * as React from "react";
import {
Stack,
ProgressIndicator,
Panel,
PanelType,
DefaultButton,
AnimationStyles,
mergeStyles,
TextField,
PrimaryButton,
Dropdown,
IDropdownOption,
MessageBar,
MessageBarType,
Label,
Text,
ILabelStyles,
Link,
IconButton,
} from "office-ui-fabric-react";
import { _copyAndSort } from "../../controls/helpers";
import * as moment from "moment";
import * as strings from "DocumentsViewerWebPartStrings";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import Dropzone from "../../controls/DropzoneExport";
import { IDocument } from "../../models/IDocument";
export interface ICategoriesPanelProps {
themeVariant: IReadonlyTheme | undefined;
showPanel: boolean;
hidePanel: () => void;
categories: string[];
addCategory: (category: string) => void;
removeCategory: (category: string) => void;
castFiletoIDoc: (file: File) => IDocument;
}
export interface ICategoriesPanelState {
busy: boolean;
newCategory: string;
uploadPlaceholders: IDocument[];
}
export default class CategoriesPanel extends React.Component<ICategoriesPanelProps, ICategoriesPanelState> {
constructor(props: ICategoriesPanelProps) {
super(props);
this.state = { busy: true, newCategory: null ,uploadPlaceholders: []};
}
public componentDidMount(): void {
this.setState({ busy: false });
}
private handleNewCategoryFieldChange = (e, newValue: string) => {
this.setState({ newCategory: newValue });
};
private add = async () => {
this.setState({ busy: true });
await this.props.addCategory(this.state.newCategory);
this.setState({ busy: false, newCategory: null });
};
private remove = async (category: string) => {
this.setState({ busy: true });
if (category) {
this.props.removeCategory(category);
}
this.setState({ busy: false });
};
private onDrop = (moreFiles) => {
const placeholders = [...this.state.uploadPlaceholders];
moreFiles.forEach((file, i) => {
const idoc = this.props.castFiletoIDoc(file);
placeholders.push({
...idoc,
key: i.toString(),
});
});
this.setState({ uploadPlaceholders: [...placeholders] });
// Upload the file
//this.props.uploadFolderIcon(moreFiles[0], this.props.folder);
};
private removeDocument = (document: IDocument) => {
this.setState({ uploadPlaceholders: [] });
};
public render(): React.ReactElement<ICategoriesPanelProps> {
const appearingStyle = mergeStyles(AnimationStyles.scaleDownIn100);
return (
<Panel
headerText={strings.ManageCategories}
type={PanelType.medium}
isOpen={this.props.showPanel}
onDismiss={this.props.hidePanel}
// You MUST provide this prop! Otherwise screen readers will just say "button" with no label.
closeButtonAriaLabel={strings.Close}
isBlocking={true}
hasCloseButton={true}
>
<Stack tokens={{ childrenGap: 15 }}>
<Stack.Item>
<Dropzone
themeVariant={this.props.themeVariant}
onDrop={this.onDrop}
uploadPlaceholders={this.state.uploadPlaceholders}
removeDocument={this.removeDocument}
/>
{/* <PrimaryButton
text={strings.StartUpload}
onClick={this.uploadDocuments}
disabled={this.state.uploading || this.state.uploadFiles.length === 0}
/> */}
</Stack.Item>
<Stack.Item align="end">
{this.props.categories.length} {strings.Categories.toLowerCase()}
</Stack.Item>
<Stack.Item>
<Stack tokens={{ childrenGap: 24 }}>
<Stack.Item
styles={{
root: {
padding: "10px 20px",
backgroundColor: this.props.themeVariant.palette.neutralLight,
},
}}
>
<Stack tokens={{ childrenGap: 4 }}>
<Stack.Item>
{this.props.categories.map((category, i) => (
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>{category}</Stack.Item>
<IconButton
iconProps={{ iconName: "Delete" }}
title={`${strings.Remove} ${category}`}
onClick={() => this.remove(category)}
disabled={this.state.busy}
/>
</Stack>
))}
</Stack.Item>
<Stack.Item>
<Stack
tokens={{ childrenGap: 6 }}
horizontal
horizontalAlign="space-between"
styles={{
root: {
alignItems: "center",
},
}}
className={appearingStyle}
>
<Stack.Item>
<TextField
label={strings.AddNewCategory}
name="newCategory"
value={this.state.newCategory}
onChange={this.handleNewCategoryFieldChange}
disabled={this.state.busy}
styles={{ root: { width: 300 } }}
/>
</Stack.Item>
<IconButton
iconProps={{ iconName: "Add" }}
title={`${strings.Add} ${this.state.newCategory}`}
onClick={this.add}
disabled={this.state.busy}
/>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Panel>
);
}
}
currently the SPFx allow to manually add/edit the choices, but my question is how we can read the uploaded excel sheet file (which will contain the choices) inside the DropZone, loop through the choices >> remove existing choices and add the ones inside the sheet? Can anyone advice please?
Here is the DropZoneExport.tsx:-
import * as React from "react";
import { Stack, IStyle } from "office-ui-fabric-react";
import { IReadonlyTheme } from "#microsoft/sp-component-base";
import * as strings from "DocumentsViewerWebPartStrings";
import { IDocument } from "../models/IDocument";
import DocumentRow from "./DocumentRow";
import { useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
export interface IDropzoneExportProps {
themeVariant: IReadonlyTheme | undefined;
onDrop: (files) => void;
uploadPlaceholders: IDocument[];
removeDocument: (document: IDocument) => void;
}
export interface IDocumentsDropzoneExportState {
files: any[];
}
export default function DropzoneExport(props: IDropzoneExportProps) {
// https://www.npmjs.com/package/react-dropzone
const onDrop = useCallback(async (acceptedFiles) => {
// Do something with the files
console.log("something dropped");
props.onDrop(acceptedFiles);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
maxFiles: 1,
accept: {
"text/csv*": [".csv"],
//acceptedFiles={[".csv, text/csv, application/vnd.ms-excel, application/csv, text/x-csv, application/x-csv, text/comma-separated-values, text/x-comma-separated-values"]}
},
});
const dropBoxStyle: IStyle = {
border: "1px dashed",
borderColor: props.themeVariant.semanticColors.inputBorder,
padding: "0.5rem 1rem",
marginBottom: ".5rem",
backgroundColor: props.themeVariant.palette.neutralQuaternary,
};
return (
<Stack>
<Stack.Item styles={{ root: dropBoxStyle }}>
<div {...getRootProps()} style={{ outline: "none" }}>
<input {...getInputProps()} />
{isDragActive ? <p>{strings.Item_DropHere}</p> : <p>{strings.Item_DropInfo}</p>}
<div
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{props.uploadPlaceholders.map((placeholder) => {
return <DocumentRow document={placeholder} themeVariant={props.themeVariant} removeDocument={props.removeDocument} />;
})}
</div>
</div>
</Stack.Item>
</Stack>
);
}
You can do that, but you may need to use a third-party library to read the excel sheet in the browser. A common solution for that is sheetjs library. There are no built-in helpers in the SPFx framework to parse Excel files, as far as I know.
But you should be able to install sheetjs using npm and then use it by import.

HOC's getting rendered every time

const withSnackbar = (WrappedComponent: React.FunctionComponent<any>): React.FunctionComponent<any> => {
const LoadedWrappedComponent = (props: any): ReactElement<any> => {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = useState(false);
const [messageData, setMessageData] = useState<IMessageData>({
message: '',
severityType: 'info',
duration: 2000,
vertical: 'bottom',
horizontal: 'right',
});
const showMessage = ({
message,
severityType,
vertical = 'bottom',
horizontal = 'right',
duration = 2000,
}: IMessageData): void => {
setMessageData({
message,
severityType,
duration: horizontal === 'left' ? 5000 : duration,
vertical,
horizontal: theme.direction === 'ltr' ? 'right' : 'left',
});
setOpen(true);
};
const handleClose = (): void => {
setOpen(false);
};
const { message, severityType, duration, horizontal, vertical } = messageData;
return (
<>
<WrappedComponent {...props} showToastMessage={showMessage} />
<Snackbar
anchorOrigin={{
vertical,
horizontal,
}}
autoHideDuration={duration}
open={open}
onClose={handleClose}
TransitionComponent={Slide}
>
<Alert
classes={{
root: classes.root,
}}
variant='filled'
severity={severityType}
action={
horizontal === 'right' ? (
<IconButton>
<Icon src='CloseOutlined' onClick={handleClose} />
</IconButton>
) : (
<></>
)
}
>
{message}
</Alert>
</Snackbar>
</>
);
};
return LoadedWrappedComponent;
};
export default withSnackbar;
**When I am closing toast message but page getting rerendered where my page components also rendering from json template with help of HOC too.. and also after api calls when I dispatch some success message or any reducer .. also my entire template page rerenders
Tried memo no luck.
Tried Render Props no luck**

React Display Separate Popover Component on Menu Click

I have the below menu:
So, ideally you'd click on those 3 dots which would open up another box next to it, I decided to use a popover(https://material-ui.com/components/popover/).. The problem is that when you click the 3 dots nothing happens. I assume this is because onClick function returns a functional popover component but that doesn't get displayed. I put in debuggers and alerts inside the functional component no hit at all.
This is those 3 dots
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
This is moreClick function
moreClick = (e, officeAccount) => {
return (
<AccountFavoritesPopover element={e} officeAccount={officeAccount} />
);
};
This is the whole popover
import React from "react";
import Popover from "#material-ui/core/Popover";
export default function AccountFavoritesPopover(element, officeAccount) {
const anchorEl = element;
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
//onClose={alert}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{officeAccount}</div>
</Popover>
</div>
);
}
Does popover have to be inside the main file? Currently the 3 dots is in the main file and AccountFavoritesPopover is a whole separate file.
I attempted to put "AccountFavoritesPopover" code inside the main file but I cannot utilize useState in the main file because it's a class. Also, I cannot convert it to actual state and use setState because once setState kicks in, the menu will disappear...
Edit:
Below is the Menu
<Creatable
id="Select"
menuIsOpen={this.state.Focused}
components={{
Option: this.Option
}}
/>
Below is the Options inside menu
<div style={{ position: "relative" }}>
<components.Option {...props} />
<div id="MoreBox">
<IconButton
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e, props.children)}
>
<More />
</IconButton>
</div>
</div>
Try this, this should work(Not tested)
Main.js
export default class Main extends Component {
constructor(props) {
this.state = {
selectedIndex: 0,
selectedId: 0,
anchorEl: null
};
}
moreClick = (anchorEl, selectedId, selectedIndex) => {
this.setState({
selectedId,
selectedIndex,
anchorEl,
open: true,
});
}
handleClose = () => {
this.setState({
open: false
});
}
render() {
const menu = [
{
id: 1,
text: '002',
more: 'more 003'
},
{
id: 2,
text: '003',
more: 'more 003'
},
{
id: 3,
text: '004',
more: 'more 003'
}
]
const menuDom = menu.map((m, index) => {
return (
<IconButton
key={m.id}
aria-describedby="simple-popover"
variant="contained"
onClick={e => this.moreClick(e.currentTarget, index, m.id)}>
{m.text}
</IconButton>
)
})
const more = (<More>{menu[this.state.selectedIndex].text}</More>)
return (
<div>
{menuDom}
<AccountFavoritesPopover open={this.state.open} anchorEl={this.state.anchorEl} onClose={this.handleClose}>
{more}
</AccountFavoritesPopover>
</div>
)
}
}
AccountFavoritesPopover.js
export default function AccountFavoritesPopover(open, anchorEl, onClose) {
const id = open ? "simple-popover" : undefined;
return (
<div>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<div>{this.props.children}</div>
</Popover>
</div>
);
}

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!

How to test my Material UI component function handlers?

Hi I'm having troubles testing my component function handlers.
I have this component:
class MUIToolbar extends React.Component {
state = {
title: this.props.title,
anchorEl: null,
value: 0,
open: false
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleClose = () => {
this.setState({ anchorEl: null });
};
handleChange = (event, value) => {
this.setState({ value });
};
homeRedirect = () => {
this.props.history.push('/groups');
}
render() {
const { classes } = this.props;
const { anchorEl, value, title } = this.state;
const open = Boolean(anchorEl);
return (
<div className={classes.root}>
<AppBar position="fixed" className={classes.root}>
<Toolbar>
<Typography variant="h6" color="inherit" className={classes.grow} onClick={this.homeRedirect}>
{title}
</Typography>
<Tabs
value={value}
onChange={this.handleChange}
classes={{ root: classes.tabsRoot, indicator: classes.tabsIndicator }}>
<Tab
classes={{ root: classes.tabRoot, selected: classes.tabSelected }}
label="Groups"
component={Link} to="/groups"/>
</Tabs>
<div>
<IconButton
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit">
<AccountCircle />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>Log out</MenuItem>
</Menu>
</div>
</Toolbar>
</AppBar>
<Switch>
<Route path="/groups" component={Groups} />
</Switch>
</div>
);
}
}
const enhance = compose(
defaultProps({
title: 'Daily Track',
}),
withStyles(styles),
withRouter,
);
export default enhance(MUIToolbar);
So, I want to test handleMenu, handleClose, handleChange and homeRedirect and I'm doing it like this way:
describe('Testing Toolbar component', () => {
let mount;
let shallow;
let mountWrapper;
let shallowWrapper;
let props = {};
beforeEach(() => {
props.onClick = jest.fn();
mount = createMount();
shallow = createShallow();
mountWrapper = mount(<Router><MUIToolbar /></Router>);
shallowWrapper = shallow(<MUIToolbar />);
});
it('should simulate user IconButton click.', () => {
const instance = mountWrapper.instance();
const userIconButton = shallow(<IconButton />);
userIconButton.simulate('click');
jest.spyOn(instance, 'handleMenu');
expect(instance.handleMenu).toHaveBeenCalled();
});
});
And I got this error
Cannot spy the handleMenu property because it is not a function; undefined given instead
I tried many ways to do that but I couldn't.
Thanks for the help

Resources