I am trying to create a custom component for edit/create in react admin. The problem I am facing is that the new updated value is not being taken, what my component is doing is adding and removing values from ChipArray with the values coming from a dropdown select
render() {
return (
<React.Fragment>
<Paper>
{this.state.scopes.map((item, key) => {
return (
<Chip
key={key}
label={item.description}
onDelete={(e) => this.handleDelete(e, item)}
/>
)
})}
</Paper>
<div>
<button onClick={(e) => this.displayChoices(e)}>
{this.state.show ? <span>Hide Scopes Select List</span> : <span>Show Scopes Select List</span>}
</button>
<div/>
{this.state.show ? <Field name="scope" component="Select" placeholder="Spaces"
onChange={(e) => this.handleAdd(e)}>
{this.state.data.map((option, index) =>
<option key={index} value={JSON.stringify(option)}>
{option.description}
</option>
)}
</Field> : ''}
</div>
</React.Fragment>
)
}
}
I manged to remove and add to the ChipArray but when I save the channges were never taken, here is the delete and add functions :
handleAdd = (e) => {
e.preventDefault();
const value = JSON.parse(e.target.value);
let selectedScopes = this.state.scopes;
if (selectedScopes.filter(s => (s.id === value.id)).length === 0) {
selectedScopes.push(value);
this.setState({scopes: [...selectedScopes]});
}
};
handleDelete = (e, value) => {
e.preventDefault();
const selectedScopes = this.state.scopes;
this.setState((prevState, props) => {
return {scopes: prevState.scopes.filter(scope => scope !== value)};
})
};
I know I am supposed to use <Field/> from "react-final-form" but I am not sure how or where.
Really sorry if this question was asked before but I looked online and wasn't so lucky and I am stuck on this.
Thanks
Related
I have an edit form in a modal that lets you edit user info. Here is the relevant portion:
export const EditUserModal = (Props) => {
const [formValues, setFormValues] = useState(Props.userData);
const [reporterData, setReporterData] = React.useState<any[]>([]);
React.useEffect(() => {
let apiClient = new APIClient();
apiClient.getOwners().then((response) => {
setReporterData(response);
});
}, []);
const handleInputChange = (e) => {
const { name, value } = e.target;
setFormValues({
...formValues,
[name]: value,
});
};
return (
<>
<Modal
open={Props.show}
onClose={() => { Props.toggleModal(false)}}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
component="form"
autoComplete="off"
>
<Typography id="modal-modal-title" variant="h4" component="h4">
Edit User {Props.userData.name}
</Typography>
<div>
<TextField
required
id="outlined-select-basic"
select
name="reports_to"
label="Reports To"
value={formValues.reports_to}
onChange={handleInputChange}
>
{reporterData.map((option) => (
<MenuItem key={option.owner} value={option.owner}>
{option.owner}
</MenuItem>
))}
</TextField>
</div>
</Box>
</Modal>
</>
)
};
The TextField is supposed to show the initial value of {formValues.reports_to}, but it remains blank (the value is still correct) until you make a new selection. reporterData just returns a list of names(string).
I have tried adding it as a defaultValue, which did not work.
I can pass props to the child component, but the input value doesn't change, when I update props (e.g. I send down "second" instead of "first", I can console.log "second" but the input value remains "first".) What could be the problem?
Code example:
// in parent component
const ParentComp = () => {
const [showEdit, setShowEdit] = useState(false);
const [currentElement, setCurrentElement] = useState('');
const myList = [{ label: 'first'}, {label: 'second'}]
const editElement = (el) => {
setShowEdit(true);
setCurrentElement(el);
}
return (
<div>
{myList.map((el, i) => (
<span key={i} onClick={() => editElement(el)}>
{el.label}
</span>
))}
{showEdit && (
<ChildComponent elData={currentElement} />
)}
</div>
)}
// in child component
const ChildComponent = ({ elData }) => {
const [testInput, setTestInput] = useState(elData.label)
return (
<input
onChange={(e) => setTestInput(e.target?.value)}
defaultValue={elData.label}
/>
)
}
I found a working solution but I'm not quite sure how it works.
// old code
<input
onChange={(e) => setTestInput(e.target?.value)}
defaultValue={elData.label}
/>
// new code
<input
onChange={(e) => setTestInput(e.target?.value)}
value={testInput || ''}
/>
Can anyone explain the difference?
I'm using react-dates for a simple birthday picker. I'm using the renderMonthElement prop to render 2 selects on top of the datepicker for months and years:
Whenever I select a month, the calendar updates correctly however the value shown in the react-select component doesn't update.
My Select looks like this:
<Select
placeholder="Monat"
styles={{ indicatorSeparator: () => {}, ...customStyles }}
options={months}
filterOption={createFilter(filterConfig)}
name="months"
value={bdaymonth}
className="monthSelect"
onChange={optionSelected => {
onMonthSelect(month, optionSelected.label)
setBdayMonth(optionSelected.label)
}}
/>
as soon as I take it out of the renderMonthElement, it works perfectly fine by either using bdaymonth from state or just directly with months.label
I thought it might have something to do with the SingleDatePicker being rendered in a Portal but even if I render it inline, the issue persists.
Here is the complete code of my renderMonthElement (pretty much stolen from https://stackoverflow.com/a/57666932/6118046) :
const renderMonthElement = ({ month, onMonthSelect, onYearSelect }) => {
let i
let years = []
for (i = moment().year() - 16; i >= moment().year() - 100; i--) {
years.push(
<option value={i} key={`year-${i}`}>
{i}
</option>
)
}
const months = moment.months().map((label, value) => {
return { value: value, label: label }
})
return (
<BdaySelect>
<div>
<Select
placeholder="Monat"
styles={{ indicatorSeparator: () => {}, ...customStyles }}
options={months}
filterOption={createFilter(filterConfig)}
name="months"
value={bdaymonth}
className="landSelect"
onChange={optionSelected => {
onMonthSelect(month, optionSelected.label)
setBdayMonth(optionSelected.label)
}}
/>
</div>
<div>
<select
value={month.year()}
onChange={e => onYearSelect(month, e.target.value)}
>
{years}
</select>
</div>
</BdaySelect>
)
}
Got it working by changing the value of the Select to
value={months.filter(function(month) {
return month.label === bdaymonth;
})}
Wanting to show a custom button at the top of the page only if the user is an admin. This works for showing/hiding certain fields on the same page. But for some reason I cannot get it to work within List Actions.
Code:
const UserListActions = ({ permissions, ...props }) => (
<CardActions> .
<RefreshButton />
{permissions === 'admin' &&
<Button color="primary"> .
<SyncIcon /> Sync Users .
</Button> .
} .
</CardActions> .
);
export const UserList = ({ permissions, ...props }) => (
<List actions={<UserListActions />} title="All users" {...props} bulkActions={false}>
Figured this out. I was missing a key piece in passing permissions into the actions property of the List
const RoleListActions = ({ basePath, permissions }) => (
<CardActions>
{permissions === 'admin' && <SyncAwsRolesButton color="primary" />}
{permissions === 'admin' && <CreateButton basePath={basePath} />}
<RefreshButton />
</CardActions>
);
export const RoleList = ({ permissions, ...props }) => (
<List title="All roles" actions={<RoleListActions permissions={permissions} />} {...props} bulkActions={false}>
^^ This bit right here
I have a material UI table in a react project, and I want a user to click on the pencil/Edit Icon to edit the table row.
This is a loaded question, but would this require an additional piece of Material UI? what would the logic look like to make this happen?? I have most of the code set up on the backend, but don't know how to write this code in the component???
Thanks for looking!
//STYLE VARIABLE BOR MATERIAL BUTTON
const style = {
margin: 12
//
};
const mapStateToProps = (state) => ({
user: state.user,
reduxState: state.getExpense
});
class ExpenseTable extends Component {
constructor(props) {
super(props);
this.state = {
getExpense: []
};
}
isSelected = (index) => {
return this.state.selected.indexOf(index) !== -1;
};
handleRowSelection = (selectedRows) => {
this.setState({
selected: selectedRows,
});
};
//on page load, DISPATCH GET_EXPENSE is
//SENT TO expenseSaga which then
//goes to getExpenseReducer and appends EXPENSE_DATA to the
//DOM
componentDidMount() {
const { id } = this.props.match.params;
this.props.dispatch({ type: USER_ACTIONS.FETCH_USER });
this.props.dispatch({ type: 'GET_EXPENSE' });
}
componentDidUpdate() {
if (!this.props.user.isLoading && this.props.user.userName ===
null) {
this.props.history.push('home');
}
}
logout = () => {
this.props.dispatch(triggerLogout());
// this.props.history.push('home');
};
//SETS STATE FOR ALL INPUTS
handleChange = (name) => {
return (event) => {
this.setState({
[name]: event.target.value
});
};
};
//SUBMIT BUTTON- TRIGGERS DISPATCH TO EXPENSE SAGA TO ADD DATA
handleClick = () => {
console.log('add expense', this.state);
this.props.dispatch({
type: 'ADD_EXPENSE',
payload: this.state
});
};
//TRASH ICON-TRIGGERS DISPATCH TO EXPENSE SAGA DELETE
handleClickRemove = (id) => {
console.log('delete expense', this.state);
this.props.dispatch({
type: 'DELETE_EXPENSE',
payload: id
});
};
handleClickEdit = (row) => {
console.log('edit expense', this.state)
this.props.dispatch({
type: 'EDIT_EXPENSE',
payload: row
})
}
render() {
console.log('HEY-oooo expense render', this.state);
let content = null;
if (this.props.user.userName) {
//MAP OVER REDUX STATE.
const tableRows = this.props.reduxState.map((row) => {
//.MAP SEPARATES DATA INTO INDIVIDUAL ITEMS.
const { id, item_description, purchase_date,
item_price, item_link } = row;
return (
<TableRow selectable={false}>
{/* TABLE ROWS */}
<TableRowColumn>{item_description}
</TableRowColumn>
<TableRowColumn>{purchase_date}
</TableRowColumn>
<TableRowColumn>${item_price}</TableRowColumn>
<TableRowColumn><a href={item_link}>{item_link}
</a></TableRowColumn>
<TableRowColumn><EditIcon class="grow:hover"
onClick={() => {this.handleClickEdit(row)}} />
</TableRowColumn>
<TableRowColumn><TrashIcon onClick={() =>
{this.handleClickRemove(id);
}}/>
</TableRowColumn>
</TableRow>
// END TABLE ROWS
);
});
<div>
{/* FORM FOR ADDING EXPENSES(DATA) */}
<form id="expenseForm">
<h3>
Add a new <br />
expense
</h3>
<input
type="text"
id="fname"
name="fname"
placeholder="Item description"
onChange=
{this.handleChange('item_description')}
/>
<br />
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item price"
onChange={this.handleChange('item_price')}
/>
<br />
<input
type="text"
id="lname"
name="lname"
placeholder="Item link"
onChange={this.handleChange('item_link')}
/>
<br />
{/* END FORM */}
<RaisedButton
id="expSubmit"
label="Submit Expense"
primary={true}
style={style}
onClick={this.handleClick}
/>
{/* TABLE TOTAL KEEPS CURRENT TOTAL OF PRICE COLOUMN */}
<h1>Total:</h1>
<br />
<h3>$748.93</h3>
</form>
{/* TABLE HEADERS */}
<Table>
<TableHeader>
<TableRow>
<TableHeaderColumn>Item
description</TableHeaderColumn>
<TableHeaderColumn>Purchase
Date</TableHeaderColumn>
<TableHeaderColumn>Item
Price</TableHeaderColumn>
<TableHeaderColumn>Item
Link</TableHeaderColumn>
<TableHeaderColumn>Edit
entry</TableHeaderColumn>
<TableHeaderColumn>Delete
entry</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody>
{tableRows}
</TableBody>
</Table>
</div>
);
}
return (
<div>
<Nav />
{content}
</div>
);
}
}
// this allows us to use <App /> in index.js
export default connect(mapStateToProps)(ExpenseTable);
The default material-ui table doesn't have the feature of in-line editing. You will have to write your own wrapper to achieve this which can be time consuming. To overcome the time issue we can use some good existing libraries.
Here's one powerful library from DevExtreme for the same which i used, but go through their licensing before you take the final decision to use.
https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/controlled-mode/