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;
Related
Following Material-UI docs I've implemented a search filter on Datagrid table, but encoutered a problem there.
Search filter functionality works fine, but while clearing input value, table data doesn't update.
I tried to update personData state if input value changes, but didn't help.
Here is the code and sandbox link
import ClearIcon from "#mui/icons-material/Clear";
import SearchIcon from "#mui/icons-material/Search";
import Box from "#mui/material/Box";
import IconButton from "#mui/material/IconButton";
import data from "./data.json";
import TextField from "#mui/material/TextField";
import { DataGrid } from "#mui/x-data-grid";
import React, { useState } from "react";
const columns = [
{ field: "name", headerName: "Name", flex: 1 },
{ field: "status", headerName: "Status", flex: 1 }
];
function escapeRegExp(value) {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
function QuickSearchToolbar(props) {
return (
<div>
<TextField
variant="standard"
value={props.value}
onChange={props.onChange}
placeholder="Search…"
InputProps={{
startAdornment: <SearchIcon fontSize="small" />,
endAdornment: (
<IconButton
title="Clear"
aria-label="Clear"
size="small"
style={{ visibility: props.value ? "visible" : "hidden" }}
onClick={props.clearSearch}
>
<ClearIcon fontSize="small" />
</IconButton>
)
}}
/>
</div>
);
}
const WindParkTable = () => {
const [searchText, setSearchText] = useState("");
const [personData, setPersonData] = useState(data || []);
const requestSearch = React.useCallback(
(searchValue) => {
setSearchText(searchValue);
const searchRegex = new RegExp(escapeRegExp(searchValue), "i");
const filteredRows = personData.filter((row) => {
return Object.keys(row).some((field) => {
return searchRegex.test(row[field].toString());
});
});
setPersonData(filteredRows);
},
[setPersonData, personData]
);
return (
<Box sx={{ height: 500, width: "100%", mt: "150px" }}>
{columns && (
<DataGrid
components={{ Toolbar: QuickSearchToolbar }}
rows={personData}
columns={columns}
componentsProps={{
toolbar: {
value: searchText,
onChange: (event) => requestSearch(event.target.value),
clearSearch: () => requestSearch("")
}
}}
/>
)}
</Box>
);
};
export default WindParkTable;
You are losing the original personData when you call to setPersonData(filteredRows). You have to filter in data instead of personData in your WindParkTable, something like this:
const filteredRows = data.filter((row) => {
return Object.keys(row).some((field) => {
return searchRegex.test(row[field].toString());
});
});
I am using React-Select in my first React App and I am not sure how to handle multiple selects in one form.
Is there anyway to pass props into the ReactSelect.jsx file so that I can have multiple selects in one form, each with their own attributes/data?
What is the proper way to deal with this?
Ref: Experimental Popout
App.js
import React from 'react';
import ReactSelect from './ReactSelect'
function App() {
return (
<ReactSelect name="select1" placeholder="Select 1" label="Select 1" data={} />
<ReactSelect name="select2" placeholder="Select 2" label="Select 2" data={} />
<ReactSelect name="select3" placeholder="Select 3" label="Select 3" data={} />
);
}
export default App;
ReactSelect.jsx
/** #jsx jsx */
import { Component } from 'react';
import { jsx } from '#emotion/core';
import Button from '#atlaskit/button';
import CreatableSelect from 'react-select/creatable';
import { defaultTheme } from 'react-select';
const { colors } = defaultTheme;
const selectStyles = {
control: provided => ({ ...provided, minWidth: 240, margin: 8 }),
menu: () => ({ boxShadow: 'inset 0 1px 0 rgba(0, 0, 0, 0.1)' }),
};
const State = {
isOpen: false,
options: [{ [""]: "" }],
value: "",
isLoading: false,
};
const createOption = (label = "") => ({
value: label.toLowerCase().replace(/\W/g, ''),
label,
});
const groupStyles = {
width: '100%',
textAlign: 'left',
borderRadius: 4,
backgroundColor: 'white',
border: '1px solid #ced4da',
};
const options=[
{ value: 'one', label: 'One' },
{ value: 'two', label: 'Two' },
{ value: 'three', label: 'Three' },
];
export default class ReactSelect extends Component {
state = {
isOpen: false,
options: options,
value: undefined,
isLoading: false,
};
toggleOpen = () => {
this.setState(state => ({ isOpen: !state.isOpen }));
};
onSelectChange = value => {
this.toggleOpen();
this.setState({ value });
console.log(value.value);
};
handleCreate = (inputValue = "") => {
this.setState({ isLoading: true });
setTimeout(() => {
const { options } = this.state;
const newOption = createOption(inputValue);
const newOptions = [...options, newOption];
newOptions.sort((a, b) =>
a.value.localeCompare(b.value)
);
this.setState(state => ({
isOpen: false,
options: newOptions,
value: newOption
}));
this.setState({ isLoading: false });
}, 1000);
}
render() {
const { isOpen, options, value, isLoading } = this.state;
return (
<Dropdown
isOpen={isOpen}
onClose={this.toggleOpen}
target={
<Button
iconAfter={<ChevronDown />}
onClick={this.toggleOpen}
isSelected={isOpen}
style={groupStyles}>
{value ? `Location: ${value.label}` : 'Select a Location'}
</Button>
}>
<CreatableSelect
backspaceRemovesValue={false}
components={{ DropdownIndicator, IndicatorSeparator: null }}
controlShouldRenderValue={true}
hideSelectedOptions={false}
isClearable={true}
menuIsOpen
isLoading={isLoading}
isDisabled={isLoading}
onChange={this.onSelectChange}
onCreateOption={this.handleCreate}
options={options}
placeholder="Search..."
styles={selectStyles}
tabSelectsValue={false}
value={value} />
</Dropdown>
);
}
}
// styled components
const Menu = props => {
const shadow = 'hsla(218, 50%, 10%, 0.1)';
console.log(props);
return (
<div
css={{
backgroundColor: 'white',
borderRadius: 4,
boxShadow: `0 0 0 1px ${shadow}, 0 4px 11px ${shadow}`,
marginTop: 8,
position: 'absolute',
zIndex: 2,
width:'100%',
}}
{...props}
/>
);
};
const Blanket = props => (
<div
css={{
bottom: 0,
left: 0,
top: 0,
right: 0,
position: 'fixed',
zIndex: 1,
}}
{...props}
/>
);
const Dropdown = ({ children, isOpen, target, onClose }) => (
<div css={{ position: 'relative' }}>
{target}
{isOpen ? <Menu>{children}</Menu> : null}
{isOpen ? <Blanket onClick={onClose} /> : null}
</div>
);
The following seems to work for me...
this.props.name
this.props.placeholder
this.props.data
Example: To set the placeholder
{value ? `${this.props.placeholder} ${value.label}` : this.props.placeholder}
I'm using the component MenuItem (material-UI) and I'm trying to choose an item when user press the 'tab' key, something like that:
{Object.keys(Countries).map(key => (
<MenuItem key={key} value={Countries[key]} onKeyDown={(ev) => {
if(ev.key === 'Tab') {
//How can I choose the selected item with tab key?
}
}}>
{Countries[key]}
</MenuItem>
))}
You need the menu item to have access to the change-handler for the Select. When a Tab occurs, it should send the menu item's value to the change-handler in a similar manner as occurs on click -- via event.target.value.
Below is a working example:
import React from "react";
import { makeStyles } from "#material-ui/core/styles";
import MenuItem from "#material-ui/core/MenuItem";
import TextField from "#material-ui/core/TextField";
const useStyles = makeStyles(theme => ({
container: {
display: "flex",
flexWrap: "wrap"
},
textField: {
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
width: 200
},
dense: {
marginTop: 19
},
menu: {
width: 200
}
}));
const currencies = [
{
value: "USD",
label: "$"
},
{
value: "EUR",
label: "€"
},
{
value: "BTC",
label: "฿"
},
{
value: "JPY",
label: "¥"
}
];
const SelectOnTabMenuItem = React.forwardRef(
({ "data-value": valueForEvent, value, onChange, ...other }, ref) => {
const handleKeyDown = event => {
if (event.key === "Tab") {
event.persist();
// Getting the value from the "data-value" prop is necessary
// due to the manner in which Material-UI clones the MenuItem during
// display of the Select: https://github.com/mui-org/material-ui/blob/v4.5.1/packages/material-ui/src/Select/SelectInput.js#L226
event.target = { value: valueForEvent };
onChange(event);
}
};
return (
<MenuItem ref={ref} value={value} onKeyDown={handleKeyDown} {...other} />
);
}
);
export default function TextFields() {
const classes = useStyles();
const [values, setValues] = React.useState({
currency: currencies[0]
});
const handleChange = name => event => {
setValues({ ...values, [name]: event.target.value });
};
const currencyHandleChange = handleChange("currency");
return (
<form className={classes.container} noValidate autoComplete="off">
<TextField
id="standard-select-currency"
select
label="Select"
className={classes.textField}
value={values.currency}
onChange={currencyHandleChange}
SelectProps={{
MenuProps: {
className: classes.menu
},
renderValue: option => option.label
}}
helperText="Please select your currency"
margin="normal"
>
{currencies.map(option => (
<SelectOnTabMenuItem
onChange={currencyHandleChange}
key={option.value}
value={option}
>
{option.label} ({option.value})
</SelectOnTabMenuItem>
))}
</TextField>
</form>
);
}
Or you can just use the onKeyDown prop on the menu item.. and since you're already doing a .map you have access to the items id or whatever you need.
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
};
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!