I am working on a react project where I am using react-virtualized to render a table with columns and I am using a cellRenderer to render data in different cells but I want a Input field in one of the cells so I created a custom cellRenderer to return that component but in the table its showing as object.
Here's my code:
codeDataGetter = ({
dataKey,
rowData,
}) => {
let dataToReturn = '';
if (rowData) {
if (typeof rowData.get === 'function') {
dataToReturn = rowData.get(dataKey);
}
dataToReturn = rowData[dataKey];
} else dataToReturn = '';
console.log('codeDataGetter', dataToReturn);
return (
<Input
type='text'
className='form-control'
value={dataToReturn}
placeholder={translateText('Code')}
onChange={() => this.handleCodeChange(event, index)}
style={{ height: '70%' }}
/>
);
}
Table:
<StyledTable
height={this.props.height}
width={this.props.width}
headerHeight={headerHeight}
rowHeight={rowHeight}
rowRenderer={this.rowRenderer}
rowCount={this.props.codeSets.length}
rowGetter={({ index }) => this.props.codeSets[index]}
LoadingRow={this.props.LoadingRow}
overscanRowCount={5}
tabIndex={-1}
className='ui very basic small single line striped table'
columnsList={columns}
/>
Columns:
const columns = (
[ <Column
key={3}
label='Code'
dataKey='code'
width={widthOfEachGrid * 1}
cellDataGetter={this.codeDataGetter}
/>,
<Column
key={4}
label='Code Description'
dataKey='code_description'
width={widthOfEachGrid * 2}
cellDataGetter={this.codeDescriptionDataGetter}
/>,
]
);
Object... is getting shown in place of Input Box.Any help will be appreciated.
Related
I noticed that "onAdd" property is removed from the updated version of Material-UI, MUI. The only property that is a function is "onDelete" and "onClick". I want to create new chips depending on user input tags. Is there any equivalent way of doing so?
You can use input fields to trigger the creation of new chips. This jsFiddle demo (click "Run" to start the demo) has a button that creates chips.
The important code in that demo relating to your question is below under index.jsx. The key items are:
createNewChip function,
chipName TextField, and
Create New Chip button.
The createNewChip function acts as an event listener for the onClick event of the Create New Chip button. createNewChip takes the text value from the chipName TextField and adds it to the list variable which is managed by React.useState.
list is an array of chip data where each element is an object that looks something like this:
{
id: '11bf5b37-e0b8-42e0-8dcf-dc8c4aefc000',
value: 'A Wonderful Chip',
isValid: true
}
Hopefully, this helps to get you started on a solution.
index.jsx
...
export default function ChipMaker() {
const [ list, setList ] = React.useState( [] );
const selectedItems = list.filter( ( item ) => item.isValid );
const selectedLengthIndex = selectedItems.length - 1;
let listById = {};
for ( let item of list ) {
listById[ item.id ] = item;
}
...
function createNewChip() {
let chipName = document.getElementById( 'chipName' ).value;
let newList = list.concat( {
id: uuidv4(),
value: chipName,
isValid: true
} );
setList( newList );
}
...
return (
<Stack spacing={3} sx={{ width: 500 }}>
<Autocomplete
multiple
id="tags-filled"
filterSelectedOptions={ true }
options={ list.map(( item ) => item.id) }
value={ list.map(( item ) => item.id) }
freeSolo
renderTags={( listIds, getTagProps) =>
listIds.map(( id, index) => (
<Chip
key={ index }
variant="outlined"
label={ listById[ id ].value }
sx={ {
color: ( theme ) => {
let chipColor = '#fff';
if ( typeof( listById[ id ] ) == 'object' ) {
chipColor = listById[ id ].isValid
? theme.palette.common.white
: theme.palette.common.white
}
return chipColor;
},
backgroundColor: ( theme ) => {
let chipColor = '#fff';
if ( typeof( listById[ id ] ) == 'object' ) {
chipColor = listById[ id ].isValid
? theme.palette.primary.main
: theme.palette.error.main
}
return chipColor;
},
[`& .MuiSvgIcon-root.MuiSvgIcon-fontSizeMedium.MuiChip-deleteIcon.MuiChip-deleteIconMedium.MuiChip-deleteIconColorDefault.MuiChip-deleteIconOutlinedColorDefault`]: {
fill: ( theme ) => theme.palette.grey[200]
}
} }
{...getTagProps({ index })}
/>
))
}
renderInput={(params) => (
<TextField
{...params}
variant="filled"
label="Material-UI Chip Input Test"
placeholder="Favorites"
/>
)}
onChange={ validateInput }
/>
<div>
{ selectedItems.map( ( item, index ) => {
let comma = null;
if ( selectedLengthIndex != index ) {
comma = (<span key={ 'idx'+index }>, </span>);
}
return (
item.isValid
? <span key={ index }>{ item.value }{ comma }</span>
: null
);
} ) }
</div>
<TextField
id='chipName'
name='chipName'
className={ 'test' }
type='text'
label={ 'Chip name' }
fullWidth={ false }
variant='standard'
inputProps={ { maxLength: 20 } }
helperText={ 'Enter chip name' }
InputLabelProps={ {
'variant': 'standard',
'color': 'primary',
'disableAnimation': false
} }
FormHelperTextProps={ { 'variant': 'standard' } }
error={ false }
defaultValue={ '' }
placeholder={ 'New chip name' }
color={ 'info' }
/>
<Button
variant={ 'contained' }
onClick={ createNewChip }
sx={ {
width: '200px'
} }
>
{ 'Create new chip' }
</Button>
</Stack>
);
}
/**
* Inject component into DOM
*/
root.render(
<ChipMaker />
);
Actually, I've just come to find out myself that MUI has a component called, "Autocomplete" which is the equivalent of making new chips (and adding tags from user input) with the "ChipInput" component from the older material-UI.
I am working on a react app where I am rendering a table and 2 of the column fields instead of containing text contains a dropdown menu and I have different options in those dropdown menu,The dropdown is rendering but on opening the dropdown it gets hidden as if the row has a limited size which is quite unexpected.Here's my code:
const StyledTable = styled(CommonInfiniteTable)`
.ReactVirtualized__Table__Grid {
overflow-y: visible !important;
}
`;
export const StyledDropdownIcon = styled(Icon)`
top: 1px !important;
right: 5px !important;
`;
const rowHeight = 41;
const headerHeight = 30;
class DefaultCodeSetsTable extends React.Component {
constructor(props) {
super(props);
this.codeDataGetter = this.codeDataGetter.bind(this);
this.codeDescriptionDataGetter = this.codeDescriptionDataGetter.bind(this);
}
codeDataGetter = ({
dataKey,
rowData,
rowIndex,
}) => (
<div>
<Form.Select
name='Code'
value={this.props.codeSets.length ? this.props.codeSets[rowIndex][`${this.props.masterName}_code`] : ''}
options={this.props.defaultCodeSetsList[rowIndex].codesOptions}
onChange={this.handleDefaultCodeChange(rowIndex)}
icon={
<StyledDropdownIcon iconName={DownCheveron} iconSize='15' className='dropdown' />
}
/>
</div>
)
codeDescriptionDataGetter = ({
dataKey,
rowData,
rowIndex,
}) => (
<div>
<Form.Select
className='drop'
name='Code Description'
value={this.props.codeSets.length ? this.props.codeSets[rowIndex][`${this.props.masterName}_code_description`] : ''}
options={this.props.defaultCodeSetsList[rowIndex].codeDescOptions}
onChange={this.handleDefaultDescriptionChange(rowIndex)}
icon={
<StyledDropdownIcon iconName={DownCheveron} iconSize='15' className='dropdown' />
}
/>
</div>
)
render() {
const widthOfEachGrid = this.props.width / 16;
const columns = (
[<Column
key={1}
label='Master Name'
dataKey='master_name'
width={widthOfEachGrid * 2}
cellDataGetter={defaultCellDataGetter}
/>,
<Column
className='column'
key={2}
label='Code'
dataKey='code'
width={widthOfEachGrid * 1}
cellRenderer={this.codeDataGetter}
/>,
<Column
className='column'
key={3}
label='Code Description'
dataKey='code_description'
width={widthOfEachGrid * 2}
cellRenderer={this.codeDescriptionDataGetter}
/>,
]
);
return (
<StyledTable
height={this.props.height}
width={this.props.width}
headerHeight={headerHeight}
rowHeight={rowHeight}
rowRenderer={this.rowRenderer}
rowCount={this.props.codeSets.length}
rowGetter={({ index }) => this.props.codeSets[index]}
LoadingRow={this.props.LoadingRow}
overscanRowCount={5}
tabIndex={-1}
className='ui very basic small single line striped table'
columnsList={columns}
/>
);
}
}
In the above table,I have 3 columns 1 being a plain text and other two are dropdowns with options to select from,when I open the dropdown this is the behaviour:
The options gets hidden as there been an overflow,any leads on this would definitely help!
I am beginner in ReactJS and currently working with Material UI Autocomplete component. I have a data object with keys as table name and columns as their value; an array of objects called columnData(is populated from values of data) with field, ifSelected as its attributes, and field attribute used to set the option label of Autocomplete(which are rendered as Chip). When an option is selected in Autocomplete dropdown then corresponding ifSelected attribute is set to true. However, problem comes when columnData object changes when a user selects different table name from data object and its corresponding value is assigned to columnData, in which previously selected option label isn't getting removed even when ifSelected attribute of all the option is false.
I went through material-ui docs here and tried fixing it using getOptionSelected prop but faced issue with deletion of chips(i.e options) with that.
Following is my code:-
import React, { Component } from "react";
import { Chip, TextField } from "#material-ui/core";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default class MultiSelect extends Component {
state = {
table: "",
tableData: [],
data: {
table1: ["col1", "col2", "col3", "col4", "col5"],
table2: ["cola", "colb", "colc", "cold", "cole"]
},
columnData: []
};
handleTableChange = (event, value) => {
console.log("Table change value is ", value);
if (this.state.data[value]) {
let columnData = this.state.data[value].map((column) => ({
field: column,
ifSelected: false
}));
console.log("Table change value is ", columnData);
this.setState({ table: value, columnData });
}
};
handleColumnChange = (columns) => {
let data = this.state.columnData;
if (data && columns) {
data.forEach((column) => (column.ifSelected = false));
console.log("Columns are ", columns);
for (let column of columns) {
let index = data.findIndex((item) => item.field === column.field);
console.log("index is ", index);
if (index !== -1) data[index].ifSelected = true;
}
this.setState({ columnData: data });
}
};
componentDidMount() {
let tableData = Object.keys(this.state.data);
this.setState({ tableData });
this.handleTableChange("table1");
}
render() {
return (
<>
<Autocomplete
id="table"
options={this.state.tableData}
getOptionLabel={(option) => option}
renderInput={(params) => (
<TextField {...params} variant="outlined" margin="normal" />
)}
value={this.state.table}
onChange={this.handleTableChange}
/>
<Autocomplete
disabled={!this.state.table}
style={{ marginTop: "1rem" }}
multiple
disableCloseOnSelect
limitTags={3}
options={this.state.columnData}
getOptionLabel={(option) => option.field}
renderInput={(params) => (
<TextField
style={{ fontWeight: "700" }}
{...params}
variant="outlined"
/>
)}
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
key={option}
label={option.field}
{...getTagProps({ index })}
/>
))
}
onChange={(event, newValues) => this.handleColumnChange(newValues)}
/>
</>
);
}
}
Would appreciate any hint. I have added demo here
You can fix the issue by passing the value prop to select the columns with ifSelected set to true.
<Autocomplete
value={this.state.columnData.filter((column) => column.ifSelected)}
// other props
/>
CodeSandbox
I'm using the material ui autocomplete component but I noticed that when the chips (tags) are deleted directly from the (x) button on the chip the autocomplete's onchange function is not triggered. Any ideas how I can get the onchange to trigger when a tag is deleted directly from the chip component?
Bellow is my code:
My component which uses autocomplete
export default function FormSearchInput( props ) {
const classes = FormStyle();
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
const checkedIcon = <CheckBoxIcon fontSize="small" />;
return (
<Grid item xs = {props.xs} className = {classes.formItem}>
<Autocomplete
className = {props.className}
size = {props.size}
limitTags = {4}
multiple
options = {props.options}
disableCloseOnSelect
getOptionLabel = {( option ) => option}
defaultValue = {props.selectedOptions}
renderOption = {( option, {selected} ) => (
<React.Fragment>
<Checkbox
icon = {icon}
checkedIcon = {checkedIcon}
style = {{ marginRight: 8 }}
checked = {selected}
onChange = {props.onChange( option, selected )}
/>
{option}
</React.Fragment>
)}
renderInput = {( params ) => (
<TextField {...params} variant = "outlined" label = {props.label}/>
)}
/>
</Grid>
)
}
My onchange handler which i pass to my formsearchcomponent:
function handleCollaboratorsChange( option, selected ) {
console.log("triggering")
let tempCollaborators = collaborators
if( selected && !tempCollaborators.includes(option) ) {
// If collaborator not in list add to list
tempCollaborators.push( option )
} else if( !selected && tempCollaborators.includes(option) ) {
// If collaborator in list remove from list
tempCollaborators.splice( tempCollaborators.indexOf(option), 1 );
}
setCollaborators( tempCollaborators )
}
Capture the "reason" in the onchange.
In the following example, I've an autocomplete that allows the user to add new options and display them as chips.
// HANDLE: ON CHANGE
const on_change = (
event: React.SyntheticEvent,
newValue: any,
reason: string,
details: any,
) => {
const selected_item = newValue[newValue.length - 1]
switch (reason) {
case 'removeOption':
case 'remove-option':
if (details.option.name) {
// remove an existing option
remove_tag(details.option.name)
} else {
// remove a new created option
remove_tag(details.option.inputValue)
}
break
case 'createOption':
case 'selectOption':
if (typeof selected_item === 'string') {
if (can_add()) create_tag(newValue)
} else if (selected_item && selected_item.inputValue) {
// Create a new value from the user input
if (can_add()) create_tag(selected_item.inputValue)
} else {
if (can_add()) {
if (!tags.includes(selected_item)) set_tag([...tags, selected_item])
}
}
break
}
}
And define the component like this:
<Autocomplete
multiple={true}
autoHighlight={true}
limitTags={tags_limit}
id="cmb_tags"
options={full_tags}
getOptionLabel={on_get_option_label}
defaultValue={tags}
freeSolo
filterOptions={on_filter}
selectOnFocus
noOptionsText={i18n.t('sinopciones')}
onChange={on_change}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
placeholder={placeholder}
/>
)}
renderOption={(props, option, { selected }) => (
<li {...props}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
style={{ marginRight: 8 }}
checked={selected}
/>
{option.name}
</li>
)}
/>
Let me know if this helps you.
Add the onChange property...
<Autocomplete
className = {props.className}
size = {props.size}
limitTags = {4}
multiple
options = {props.options}
disableCloseOnSelect
getOptionLabel = {( option ) => option}
defaultValue = {props.selectedOptions}
**onChange = {(event, newValue) => { handleCollaboratorsChange(newValue); }}**
renderOption = {( option, {selected} ) => (
I have a component to display data on a material-ui table called UserRow.
This component is used on another component called users.
But in order to display the data properly in each field the only way I found was to create a conditional rendering, so i could just render the data that i wanted in each field, otherwise it would be duplicated.
Is there a way to just call once the <UserRow/> component and get the same results as i get in the image bellow?
UserRow:
export default function UserRow( props, {name, details}) {
const style = styles();
function UserName(props) {
const showUserRow = props.showUserRow;
if (showUserRow) {
return <ListItem>
<ListItemIcon >
<PeopleIcon className={style.iconColor}/>
</ListItemIcon>
<ListItemText className={style.text}>{props.userRow}</ListItemText>
</ListItem>
}
return<div></div>;
}
function IconDetails(props) {
const showDetailsRow = props.showDetailsRow;
if (showDetailsRow) {
return <Link to={`/users/${props.detailsRow}`}>
<ListItemIcon >
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>;
}
return<div></div>;
}
return (
<List>
<ListItem>
<UserName userRow={props.name} showUserRow={props.showUserRow}/>
<IconDetails detailsRow={props.details} showDetailsRow={props.showDetailsRow}/>
</ListItem>
</List>
)
}
users:
export default function User({ data }) {
const style = styles();
const userList = data.map((row) => {
return { name: row, details: row };
});
const [state] = React.useState({
users: [
...userList,
]
});
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<UserRow showUserRow={true} showDetailsRow={false} name={rowData.name} />
)
},
{
title: "Details",
field: "details",
render: rowData => (
<UserRow showUserRow={false} showDetailsRow={true} details={rowData.details} />
)
},
]}
data={state.users}
options={{
search: true
}}
/>
</div>
)
}
What i had before:
UserRow:
export default function UserRow( props, {name, details}) {
const style = styles();
return (
<List>
<ListItem>
<ListItemIcon >
<PeopleIcon className={style.color}/>
</ListItemIcon>
<ListItemText className={style.text}>{name}</ListItemText>
<Link to={`/users/${details}`}>
<ListItemIcon >
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>
</ListItem>
</List>
)
}
users:
return (
<div>
<MaterialTable
icons={tableIcons}
title={<h1 className={style.title}>Users</h1>}
columns={[
{
title: "Name",
field: "name",
render: rowData => (
<UserRow name={rowData.name} details={rowData.details} />
)
},
{
title: "Details",
},
]}
data={state.users}
options={{
search: true
}}
/>
</div>
)
}
The problem here, in the previous solution, is that if we create a title Details, the material-ui table creates a div for the details and I can't place my icon there, and this would be a problem if i had more data and need to place the data in the respective position.
What i was trying to achieve with the previous solution was to cut down some code, because if i have many fields i will repeat too much code.
Link that might be useful: https://material-table.com/#/docs/get-started