Currently I have a simple material-table like this:
<MaterialTable
options={myOptions}
title="MyTitle"
columns={state.columns}
data={state.data}
tableRef={tableRef} // Not working
editable={{
onRowAdd: ...,
onRowDelete: ...,
onRowUpdate: ...
}}
/>;
where I'm trying to a create new add button (not edit the current one): each Row in the Bar Column should have a custom add button. I've looked through the MaterialTable source code but I couldn't reproduce the code that is used for the default add button which is:
calculatedProps.actions.push({
icon: calculatedProps.icons.Add,
tooltip: localization.addTooltip,
position: "toolbar",
disabled: !!this.dataManager.lastEditingRow,
onClick: () => {
this.dataManager.changeRowEditing();
this.setState({
...this.dataManager.getRenderState(),
showAddRow: !this.state.showAddRow,
});
},
});
in particular I can't get to access the dataManager variable.
That is how the current table looks like, and I need to add the add button where there is the red sign.
I think this is what you are looking for:
The Actions column represents the default actions set. I added an specific button using custom column rendering (docs):
//..previous columns definition
{
title: "Custom Add",
field: "internal_action",
editable: false,
render: (rowData) =>
rowData && (
<IconButton
color="secondary"
onClick={() => addActionRef.current.click()}
>
<AddIcon />
</IconButton>
)
}
*Using rowData as conditional, prevents from rendering while filling the addition row.
Then I triggered the add action as shown here:
const MyComponent() {
const addActionRef = React.useRef();
return (
<>
<button onClick={() => addActionRef.current.click()}>
Add new item
</button>
<MaterialTable
//...
components={{
Action: props => {
//If isn't the add action
if (typeof props.action === typeof Function || props.action.tooltip !== 'Add') {
return <MTableAction {...props} />
} else {
return <div ref={addActionRef} onClick={props.action.onClick}/>;
}}
}}
editable={{
onRowAdd: (newData, oldData) => Promise.resolve(); //your callback here
}}
/>
</>
);
}
I extended the original snippet in order to complete the addition cycle. If you need to handle different types of actions, I think Editable section from the oficial docs would be handy.
Hope this works for you! Full code and sandbox here:
import React, { Fragment, useState } from "react";
import MaterialTable, { MTableAction } from "material-table";
import AddIcon from "#material-ui/icons/AddAlarm";
import IconButton from "#material-ui/core/IconButton";
export default function CustomEditComponent(props) {
const tableRef = React.createRef();
const addActionRef = React.useRef();
const tableColumns = [
{ title: "Client", field: "client" },
{ title: "Name", field: "name" },
{ title: "Year", field: "year" },
{
title: "Custom Add",
field: "internal_action",
editable: false,
render: (rowData) =>
rowData && (
<IconButton
color="secondary"
onClick={() => addActionRef.current.click()}
>
<AddIcon />
</IconButton>
)
}
];
const [tableData, setTableData] = useState([
{
client: "client1",
name: "Mary",
year: "2019"
},
{
client: "client2",
name: "Yang",
year: "2018"
},
{
client: "client3",
name: "Kal",
year: "2019"
}
]);
return (
<Fragment>
<MaterialTable
tableRef={tableRef}
columns={tableColumns}
data={tableData}
title="Custom Add Mode"
options={{
search: false
}}
components={{
Action: (props) => {
//If isn't the add action
if (
typeof props.action === typeof Function ||
props.action.tooltip !== "Add"
) {
return <MTableAction {...props} />;
} else {
return <div ref={addActionRef} onClick={props.action.onClick} />;
}
}
}}
actions={[
{
icon: "save",
tooltip: "Save User",
onClick: (event, rowData) => alert("You saved " + rowData.name)
}
]}
editable={{
onRowAdd: (newData) =>
Promise.resolve(setTableData([...tableData, newData]))
}}
/>
</Fragment>
);
Related
Default UI from MUI-Datatables v4.3.0 like this :
And i'am want to make this style :
For information i am using this package :
"#mui/material": "^5.11.4"
"mui-datatables": "^4.3.0"
How to make The TextField or Input with outlined style.
Based of example MUI-Databales from this link :
https://codesandbox.io/s/github/gregnb/mui-datatables
I have a answer and solution from my case.
My Component CustomSearchRender.js :
import React from "react";
import Grow from "#mui/material/Grow";
import TextField from "#mui/material/TextField";
import { withStyles } from "tss-react/mui";
import { SearchOutlined } from "#mui/icons-material";
import InputAdornment from "#mui/material/InputAdornment";
const defaultSearchStyles = (theme) => ({
searchText: {
flex: "0.8 0",
marginTop: 10,
},
searchIcon: {
"&:hover": {
color: theme.palette.error.main,
},
},
});
const CustomSearchRender = (props) => {
const { classes, onSearch, searchText } = props;
const handleTextChange = (event) => {
onSearch(event.target.value);
};
return (
<Grow appear in={true} timeout={300}>
<TextField
placeholder="Search"
size="medium"
className={classes.searchText}
value={searchText || ""}
onChange={handleTextChange}
fullWidth
variant="outlined"
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchOutlined />
</InputAdornment>
),
}}
/>
</Grow>
);
};
export default withStyles(CustomSearchRender, defaultSearchStyles, {
name: "CustomSearchRender",
});
And put into options :
const options = {
selectableRows: "none",
filterType: "textField",
responsive: "simple",
confirmFilters: false,
jumpToPage: false,
download: false,
print: false,
enableNestedDataAccess: ".",
textLabels: {
body: {
noMatch: loading ? (
<TableSkeleton variant="h4" length={10} />
) : (
"Maaf, tidak ada data untuk ditampilkan"
),
},
},
searchOpen: true,
searchAlwaysOpen: true,
searchPlaceholder: "Search keyword",
customSearchRender: (searchText, handleSearch) => {
return (
<CustomSearchRender searchText={searchText} onSearch={handleSearch} />
);
},
};
And finnally the result like this :
I have the below component as the main layout. When I click on the menu item, I want them to navigate to the home, holiday calendar, and event pages. What can I do with this horizontal menu? The below code shows the main layout wrapped around the above pages. I am using AntD and react-router. this main layout is wrapped around all other routers.
import { Layout, Menu, Button, Avatar, Row, Col, Space, Dropdown } from "antd";
import React, { useState } from "react";
import { Outlet } from "react-router-dom";
const navigation = [
{ label: "Home", key: 1 },
{ label: "Holiday Calendar", key: 2 },
{ label: "Event", key: 3 },
];
const MainLayout = () => {
const [open, setOpen] = useState(false);
const showDrawer = () => {
setOpen(true);
};
const onClose = () => {
setOpen(false);
window.dispatchEvent(new Event("loadHolidayCalander"));
};
const handleLogOut = () => {
localStorage.removeItem("access-token");
};
const menu = (
<Menu
items={[
{
key: "1",
label: <Button onClick={handleLogOut}>Log out</Button>,
},
]}
></Menu>
);
return (
<Layout style={{backgroundColor:"white"}}>
<Row style={{ backgroundColor:"#404140"}}>
<Col
style={{padding:5, margin:0, height:48}}
flex="300px"
>
<a href="/holiday-calander">
<img src="/logo.png" alt="logo" style={{ height: 38 }} />
</a>
</Col>
<Col>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["2"]}
items={navigation}
/>
</Col>
<Col
flex="auto"
style={{
padding:5
}}
>
<div style={{ position: "absolute", right: "5px" }}>
<Space size={20}>
<Dropdown overlay={menu} placement="topRight" arrow>
<Avatar style={{ width: 38, height:38 }} />
</Dropdown>
</Space>
</div>
</Col>
</Row>
<Layout
style={{
padding: 0,
backgroundColor: "white",
marginLeft:28,
marginRight:28,
}}
>
<Outlet />
</Layout>
</Layout>
);
};
export default MainLayout;
You can add an onClick handler to the Menu component, which will be passed an object with the key property you can search the navigation array for the matching element.
Add a link target to the navigation array elements.
import { ..., useNavigate, ... } from 'react-router-dom';
...
const navigation = [
{ label: "Home", key: 1, target: "/" },
{ label: "Holiday Calendar", key: 2, "/holidaycalendar" },
{ label: "Event", key: 3, "/event" },
];
...
const navigate = useNavigate();
const handleMenuClick = ({ key }) => {
const { target } = navigation.find(item => item.key === key) || {};
if (target) {
navigate(target);
}
};
...
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["2"]}
items={navigation}
onClick={handleMenuClick}
/>
An improvement could be to make the key property the link target.
import { ..., useNavigate, ... } from 'react-router-dom';
...
const navigation = [
{ label: "Home", key: "/" },
{ label: "Holiday Calendar", key: "/holidaycalendar" },
{ label: "Event", key: "/event" },
];
...
const navigate = useNavigate();
const handleMenuClick = ({ key }) => {
if (key) {
navigate(key);
}
};
...
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={["/holidaycalendar"]}
items={navigation}
onClick={handleMenuClick}
/>
I have to fill the table with data which I have returned from different API : the first from smart contract and the second one from a MySQL table using axios.
Both of the sets of data returned are arrays and each elements with the same id I want to put it into a row.
I succeeded to put the data only from the smart contract.
Here's the code of my component :
function CandidaturesList() {
const [account, setAccounts] = useState(null);
const [contract, setContract] = useState(null);
const [candidatures, setCandidatures] = useState(null);
const [stateCandidatures, setStateCandidatures] = useState(null);
useEffect(() => {
async function fetchData() {
const web3 = await getWeb3();
const accounts = await web3.eth.getAccounts();
const networkId = await web3.eth.net.getId();
const deployedNetwork = OffreEmploiContract.networks[networkId];
const instance = new web3.eth.Contract(
OffreEmploiContract.abi,
deployedNetwork && deployedNetwork.address,
);
setAccounts(accounts);
setContract(instance);
const response = await instance.methods.getApply().call({ from: accounts[0] });
console.log(response);
setCandidatures(response);
axios.get('http://localhost:4000/api/getAll').then((res) => {
setStateCandidatures(res.data);
})
}
fetchData();
});
const project = [
{
title: "COMPANIES",
dataIndex: "nomCompagnie",
align: 'center',
render: text => (
<div className="avatar-info">
<Title level={5}>{text}</Title>
</div>
)
},
{
title: "JOB TITLE",
dataIndex: "titre_poste",
align: 'center',
render: text => (
<>
<div className="semibold">{text}</div>
</>)
},
{
title: "STATUS",
width: "15%",
dataIndex: "status",
align: 'center',
render: text => (
<>
<br>{text}</br>
<br></br>
<Steps current={1} progressDot={(dot, { status, index }) => (
<Popover
content={
<span>
step {index} status: {status}
</span>
}
>
{dot}
</Popover>
)} >
<Step title="Published" />
<Step title="Job interview" description="Mon, 18 Apr 2022" />
<Step title="Waiting for decision" />
<Step title="Completed" />
</Steps>,
</>)
},
{
title: "FINAL DECISION",
dataIndex: "decision",
align: 'center',
render: text => (
<>
{
text === "Valide" || text === "Refused" ?
<Tag color="cyan">{text}</Tag>
:
<Tag icon={<SyncOutlined spin />} color="processing">{text}</Tag>
}
</>
)
},
];
return (
<>
<Card
bordered={false}
className="criclebox tablespace mb-24"
title="Your Candidature"
extra={
<>
</>
}
>
<div className="table-responsive">
<Table
columns={project}
dataSource={candidatures }
pagination={false}
className="ant-border-space"
/>
</div>
</Card>
</>
);
}
export default CandidaturesList;
I must fill the Steps with the state stateCandidatures which is an array of objects , example :
{id: 1, date_entretien: 'Mon Apr 25 2022', status: 'waiting for interview'},
{id: 2, date_entretien: null, status: 'Published'}
and this is an example of the state candidatures :
[ id: '1', nomCompagnie:'Company1', titre_poste: 'manager', decision:'valide', …]
[ id: '2', nomCompagnie:'Company2', titre_poste: 'developer', decision:'processing', …]
and the second problem is i don't know how to fill the steps with those data ( the code shows a static steps)
I'm trying to use react-tabulator but I'm having troubles rendering a function inside my table columns.
The function is returning JSX but it is using other class functions, which fails to execute.
basically, the column "actions" has 2 icons, edit and delete, each of them is an icon component with onClick property which calls a function inside my class. The function fails to execute because "this" is undefined, so the function couldn't be found.
here is my code:
import React, {Component} from 'react';
import {reactFormatter, ReactTabulator} from 'react-tabulator'
import IconButton from "#material-ui/core/IconButton";
import {IconsDiv} from "./styles";
import {FontAwesomeIcon} from "#fortawesome/react-fontawesome";
import {faPencilAlt, faTrash} from "#fortawesome/free-solid-svg-icons";
class MyTable extends Component {
constructor(props) {
super(props);
this.state = {
teamsTableData: this.createFormattedData([1,2]),
rowID: 0,
};
this.options = {
movableColumns: true,
pagination: "local",
paginationSize: 15,
};
this.ref = null;
this.handleEdit = this.handleEdit.bind(this);
this.handleDelete = this.handleDelete.bind(this);
}
createFormattedData(data) {
return {
rows: data.map(ele => {
return {
id: ele.teamId,
name: "test1",
count: "test1",
};
}
),
columns: [
{
title: "Name", field: "name",
headerFilter: "input",
},
{
title: "Count", field: "count",
headerFilter: "input",
headerFilterFunc: "=",
},
{
title: "Actions", field: "actions",
formatter: reactFormatter(<this.createActions/>),
}
],
}
}
createActions(table) {
const rowData = table.cell._cell.row.data;
return <IconsDiv>
<IconButton onClick={() => this.handleEdit(rowData)}>
<FontAwesomeIcon icon={faPencilAlt} className={'Icon'}/>
</IconButton>
{" "}
<IconButton onClick={() => this.handleDelete(rowData)}>
<FontAwesomeIcon icon={faTrash} className='Icon'/>
</IconButton>
</IconsDiv>;
}
handleEdit(rowData) {
this.setState({rowID: rowData.id});
}
handleDelete(rowData) {
this.setState({deleteRow: rowData.id});
}
render() {
return (
<>
<ReactTabulator
ref={ref => (this.ref = ref)}
data={this.state.teamsTableData.rows}
columns={this.state.teamsTableData.columns}
tooltips={true}
options={this.options}
layout={"fitColumns"}
/>
</>
)
}
}
Does anyone know what is wrong? why the column is rendered properly, but the action fails? why does "this.handleEdit" or "this.handleDelete" couldn't be found? ( I guess "this" is undefined)
You can assign this to another variable so you can pass the scope into the function:
createActions(table) {
var self = this; //assign this to the self variable and pass into the icons
const rowData = table.cell._cell.row.data;
return <IconsDiv>
<IconButton onClick={() => self.handleEdit(rowData)}>
<FontAwesomeIcon icon={faPencilAlt} className={'Icon'}/>
</IconButton>
{" "}
<IconButton onClick={() => self.handleDelete(rowData)}>
<FontAwesomeIcon icon={faTrash} className='Icon'/>
</IconButton>
</IconsDiv>;
}
Have you tried using an arrow function instead?
createActions = (table) => {
const rowData = table.cell._cell.row.data;
return <IconsDiv>
<IconButton onClick={() => this.handleEdit(rowData)}>
<FontAwesomeIcon icon={faPencilAlt} className={'Icon'}/>
</IconButton>
{" "}
<IconButton onClick={() => this.handleDelete(rowData)}>
<FontAwesomeIcon icon={faTrash} className='Icon'/>
</IconButton>
</IconsDiv>;
}
I encountered an annoying problem with Textfield using MateriaUI framework. I have a form with many inputs and it seems to be a bit laggy when typing or deleting values inside the fields. In other components when there are like 2 or 3 inputs there's no lag at all.
EDIT: The problem seems to be with my onChange handler.
Any help is much appreciated. Thanks in advance.
This is my custom input code:
import React, { useReducer, useEffect } from 'react';
import { validate } from '../utils/validators';
import TextField from '#material-ui/core/TextField';
import { ThemeProvider, makeStyles, createMuiTheme } from '#material-ui/core/styles';
import { green } from '#material-ui/core/colors';
const useStyles = makeStyles((theme) => ({
root: {
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
const inputReducer = (state, action) => {
switch (action.type) {
case 'CHANGE':
return {
...state,
value: action.val,
isValid: validate(action.val, action.validators)
};
case 'TOUCH': {
return {
...state,
isTouched: true
}
}
default:
return state;
}
};
const Input = props => {
const [inputState, dispatch] = useReducer(inputReducer, {
value: props.initialValue || '',
isTouched: false,
isValid: props.initialValid || false
});
const { id, onInput } = props;
const { value, isValid } = inputState;
useEffect(() => {
onInput(id, value, isValid)
}, [id, value, isValid, onInput]);
const changeHandler = event => {
dispatch({
type: 'CHANGE',
val: event.target.value,
validators: props.validators
});
};
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const classes = useStyles();
return (
<ThemeProvider theme={theme}>
<TextField
className={classes.input}
InputProps={{
className: classes.root
}}
InputLabelProps={{
className: classes.label
}}
id={props.id}
type={props.type}
label={props.label}
onChange={changeHandler}
onBlur={touchHandler}
value={inputState.value}
title={props.title}
error={!inputState.isValid && inputState.isTouched}
helperText={!inputState.isValid && inputState.isTouched && props.errorText}
/>
</ThemeProvider>
);
};
export default Input;
In addition to #jony89 's answer. You can try 1 more workaround as following.
On each keypress (onChange) update the local state.
On blur event call the parent's change handler
const Child = ({ parentInputValue, changeValue }) => {
const [localValue, setLocalValue] = React.useState(parentInputValue);
return <TextInputField
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
onBlur={() => changeValue(localValue)} />;
}
const Parent = () => {
const [valMap, setValMap] = React.useState({
child1: '',
child2: ''
});
return (<>
<Child parentInputValue={valMap.child1} changeValue={(val) => setValMap({...valMap, child1: val})}
<Child parentInputValue={valMap.child2} changeValue={(val) => setValMap({...valMap, child2: val})}
</>
);
}
This will solve your problems if you do not want to refactor the existing code.
But the actual fix would be splitting the state so that update in the state of child1 doesn't affect (change reference or mutate) state of child2.
Make sure to extract all constant values outside of the render scope.
For example, each render you are providing new object to InputLabelProps and InputProps which forces re-render of child components.
So every new object that is not must be created within the functional component, you should extract outside,
That includes :
const touchHandler = () => {
dispatch({
type: 'TOUCH'
});
};
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
color: 'white'
},
input: {
margin: '10px',
'&& .MuiInput-underline:before': {
borderBottomColor: 'white'
},
},
label: {
color: 'white'
}
}));
const theme = createMuiTheme({
palette: {
primary: green,
},
});
Also you can use react memo for function component optimization, seems fit to your case.
I managed to get rid of this lagging effect by replacing normal <TextField /> by <Controller /> from react-hook-form.
Old code with Typing Lag
<Grid item xs={12}>
<TextField
error={descriptionError.length > 0}
helperText={descriptionError}
id="outlined-textarea"
onChange={onDescriptionChange}
required
placeholder="Nice placeholder"
value={description}
rows={4}
fullWidth
multiline
/>
</Grid>
Updated Code with react-hook-form
import { FormProvider, useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
const methods = useForm({
resolver: yupResolver(validationSchema)
});
const { handleSubmit, errors, reset } = methods;
const onSubmit = async (entry) => {
console.log(`This is the value entered in TextField ${entry.name}`);
};
<form onSubmit={handleSubmit(onSubmit)}>
<Grid item xs={12}>
<FormProvider fullWidth {...methods}>
<DescriptionFormInput
fullWidth
name="name"
placeholder="Nice placeholder here"
size={matchesXS ? 'small' : 'medium'}
bug={errors}
/>
</FormProvider>
</Grid>
<Button
type="submit"
variant="contained"
className={classes.btnSecondary}
startIcon={<LayersTwoToneIcon />}
color="secondary"
size={'small'}
sx={{ mt: 0.5 }}
>
ADD
</Button>
</form>
import React from 'react';
import PropTypes from 'prop-types';
import { Controller, useFormContext } from 'react-hook-form';
import { FormHelperText, Grid, TextField } from '#material-ui/core';
const DescriptionFormInput = ({ bug, label, name, required, ...others }) => {
const { control } = useFormContext();
let isError = false;
let errorMessage = '';
if (bug && Object.prototype.hasOwnProperty.call(bug, name)) {
isError = true;
errorMessage = bug[name].message;
}
return (
<>
<Controller
as={TextField}
name={name}
control={control}
defaultValue=""
label={label}
fullWidth
InputLabelProps={{
className: required ? 'required-label' : '',
required: required || false
}}
error={isError}
{...others}
/>
{errorMessage && (
<Grid item xs={12}>
<FormHelperText error>{errorMessage}</FormHelperText>
</Grid>
)}
</>
);
};
With this use of react-hook-form I could get the TextField more responsive than earlier.