I want to get an attribute from a function within a component. The function is called checkbox.js:
export default function Checkboxes() {
const [checked, setChecked] = React.useState(false);
const handleChange = (event) => {
setChecked(event.target.checked);
};
return (
<div>
<Checkbox
checked={checked}
onChange={handleChange}
color="primary"
inputProps={{ 'aria-label': 'primary checkbox' }}
/>
</div>
);
}
The component Checkbox multiple times in a table row. props.characterData is an array that has various attributes.
const TableBody = props => {
const rows = props.characterData.map((row, index) => {
return (
<tr key={index}>
<td>
<Checkbox />
</td>
<td>{row.task}</td>
<td>{row.desc}</td>
</tr>
)
})
return <tbody>{rows}</tbody>
}
What I want to do is to save the "checked" boolean value into the row.checked attribute every time it is changed, but nothing I have tried or searched up worked.
The row.checked comes from an array of characters where each character is initialized in the form:
{ task: '', desc: '', checked: false}
In the TableBody, it is mapped for each element in the array.
Any help would be greatly appreciated.
import React, { useState, useEffect } from 'react'
const Checkboxes =({checked, cbOnChange})=> {
const handleChange = (event) => {
cbOnChange(event.target.checked);
};
return (
<div>
<Checkbox
checked={checked}
onChange={handleChange}
color="primary"
inputProps={{ 'aria-label': 'primary checkbox' }}
/>
</div>
);
}
const TableBody = props => {
const [checkboxValue, setCheckboxValue] = useState({})
useEffect(() => {
setCheckboxValue({})
}, [JSON.stringify(props.characterData)])
const handleChange =(index, checked)=>{
setCheckboxValue((state)=>({
...state,
[index]: checked
}))
}
const rows = props.characterData.map((row, index) => {
return (
<tr key={index}>
<td>
<Checkboxes cbOnChange={(checked)=> handleChange(index, checked)} checked={checkboxValue[index] || false}/>
</td>
<td>{row.task}</td>
<td>{row.desc}</td>
</tr>
)
})
return <tbody>{rows}</tbody>
}
export default TableBody
you should write like this:
export default function App() {
const [characterData , setCharacterData] = React.useState([{
id: 1,
priority : true,
task: "task1",
desc: "desc1"
},
{
id: 2,
priority : false,
task: "task2",
desc: "desc2"
}])
const handleChangePriority = (id , value) => {
let cloneCharacterData = [...characterData]
const selectData = characterData.filter(itm => itm.id === id)[0];
const index = cloneCharacterData.findIndex(itm => itm.id === id)
selectData.priority = value;
cloneCharacterData[index] = selectData;
setCharacterData(cloneCharacterData)
}
return (
<div >
<TableBody characterData={characterData} handleChangeProps={handleChangePriority} />
</div>
);
}
TableBody:
const TableBody = props => {
const rows = props.characterData.map((row, index) => {
return (
<tr key={index}>
<td>
<Checkbox id={row.id} priority={row.priority} handleChangeProps={props.handleChangeProps} />
</td>
<td>{row.task}</td>
<td>{row.desc}</td>
</tr>
)
})
return <tbody>{rows}</tbody>
}
And Checkboxes:
export default function Checkboxes({priority , handleChangeProps , id}) {
// const [checked, setChecked] = React.useState(false);
const handleChange = event => {
// setChecked(event.target.checked);
handleChangeProps(id , event.target.checked)
};
return (
<div>
<Checkbox
checked={priority}
onChange={handleChange}
color="primary"
inputProps={{ "aria-label": "primary checkbox" }}
/>
</div>
);
}
Related
I got an error. react-dom.development.js:86 Warning: Cannot update a component (TableComponent) while rendering a different component (EditComp). To locate the bad setState() call inside EditComp, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
at EditComp (http://localhost:3000/static/js/bundle.js:10830:5).
How can i fix this?
My EditComp component
function EditComp({ id, idItem, Edit, editingText, submitEdits, setIdItem, handleEditing, item, style, classBull}) {
return (
<>
{
id === idItem ?
<Fragment>
<td className={classBull(id)}
><input
className={style}
onChange={Edit}
value={editingText}
type="text" /></td>
<td> <button
onClick={() => submitEdits(item)}
>Edit</button></td>
<td> <img onClick={() => setIdItem(null)} src={cansel} alt="cancel" /></td>
</Fragment>
: <th onDoubleClick={() => handleEditing(id)}>{item.text}</th>
}
</>
)
}
My parent component
function TableComponent({ table, removeItem, setTable }) {
const [editingText, setEditingText] = useState("");
const [idItem, setIdItem] = useState(null);
const [style, setStyle] = useState('null');
const [currentItem, setCurrentItem] = useState(null);
const styleActive = () => {
setStyle('table_data_active');
}
const classBull = (id) => {
if (id === idItem) {
return setStyle('table_data_active');;
} else {
return 'table_item'
}
}
const handleEditing = (id) => {
setIdItem(id);
}
const Edit = (e) => {
setEditingText(e.currentTarget.value);
}
const submitEdits = (item) => {
axios.patch(`http://localhost:3004/item/${item.id}`, { text: editingText })
setIdItem(null);
setEditingText('')
}
return (
<thead>
{
table.map((item, id) => {
return (
<tr
onDragStart={(e) => dragStartHandler(e, item)}
onDragLeave={(e) => dragEndHandler(e)}
onDragEnd={(e) => dragEndHandler(e)}
onDragOver={(e) => dragOVerHandler(e)}
onDrop={(e) => dragHandler(e, item)}
draggable={true}
key={item.id}>
<DataItem
styleActive={styleActive}
table={table}
style={style}
idItem={idItem}
handleEditing={handleEditing}
item={item} id={id} />
<EditComp
classBull={classBull}
style={style}
item={item}
handleEditing={handleEditing}
setIdItem={setIdItem}
submitEdits={submitEdits}
editingText={editingText}
Edit={Edit}
id={id}
idItem={idItem} />
<RemoveItem
id={id}
items={item}
removeItem={removeItem} />
</tr>
)
})
}
</thead>
)
}
Here is my code
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1)
},
},
button: {
margin: theme.spacing(1)
}
}))
function CreateCourse() {
const classes = useStyles();
const [sectionFields, setSectionFields] = useState([{
sectionName: '',
overview: '',
videoContents: [{
videoName: '', videoUrl: ''
}]
}])
function handleChangInput(index, event) {
const values = [...sectionFields];
values[index][event.target.name] = event.target.value;
setSectionFields(values);
}
function handleChangVideoInput(index, i, event) {
const values = [...sectionFields];
values[index].videoContents[i][event.target.name] = event.target.value;
setSectionFields(values);
console.log(index, event.target.name)
}
const handleSubmit = (event) => {
event.preventDefault();
console.log("Input Field ", sectionFields)
}
const handleRemoveFields = (index) => {
const values = [...sectionFields];
values.splice(index, 1)
setSectionFields(values)
}
const handleAddFields = () => {
setSectionFields([...sectionFields, {
sectionName: '',
overview: '',
videoContents: [{videoName: '', videoUrl: ''}]
}])
}
return (
<div className='container mb-5'>
<Container>
<h1>Add New Member</h1>
<form className={classes.root} onSubmit={handleSubmit}>
{sectionFields.map((inputField, index) => (
<div key={index}>
<TextField
name="sectionName"
label="Section Name"
variant="filled"
value={inputField?.sectionName}
onChange={event => handleChangInput(index, event)}
/>
<TextField
name="overview"
label="Section Overview"
variant="filled"
value={inputField?.overview}
onChange={event => handleChangInput(index, event)}
/>
<IconButton onClick={() => handleRemoveFields(index)}>
<RemoveIcon/>
</IconButton>
<IconButton onClick={handleAddFields}>
<AddIcon/>
</IconButton>
{inputField?.videoContents?.map((v, i) => (
<div key={i}>
<TextField
name="videoName"
label="Enter Video Name"
variant="filled"
value={v.videoName}
onChange={event => handleChangVideoInput(index, i, event)}
/>
<TextField
name="videoUrl"
label="Enter Video Url"
variant="filled"
value={v.videoUrl}
onChange={event => handleChangVideoInput(index, i, event)}
/>
</div>
))}
</div>
))}
<Button
className={classes.button}
variant='contained'
color='primary'
type='submit'
endIcon={<Icon/>}
onClick={handleSubmit}
>
SEND
</Button>
</form>
</Container>
</div>
);
}
export default CreateCourse;
Output in Screenshot
when i click on plus icon creates a new input like
But I want one sectionName has many videoName and videoUrl like I want to create plus icon on the videoUrl side and when user clicks plus icon, it creates many videoName and videoUrl as many as user wants and if user clicks section then it creates one section row with one video row. How can I solve this using react?
First of all, when you use the current value of a state in order to calculate the new state value, it's preferable to use a callback function. This way it's not influenced by re-renders and guarantees the calculation uses the most updated state value.
So assuming you have
const [state, setState] = useState([]);
Don't use:
const next = [...state, newElement];
setState(next);
But instead, use:
setState((previous) => [...previous, newElement]);
In order to add more fields into a nested array, you can update the state like this:
function addToSection(i) {
setSectionFields((prev) => (
const updatedSection = {
...prev[i],
videoContents: [
...prev[i].videoContents,
{ videoName: '', videoUrl: '' },
],
};
return prev.map((section, index) => {
return index === i ? updatedSection : section;
});
);
}
After many hours of trying finally i did it and thanks to #GalAbra , he saves my lot of time and i post this because if it helps to anyone
import React, {useState} from "react";
import Container from "#material-ui/core/Container";
import {TextField} from "#material-ui/core";
import Icon from "#material-ui/icons/Send";
import {makeStyles} from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import IconButton from "#material-ui/core/IconButton";
import RemoveIcon from '#material-ui/icons/Delete';
import AddIcon from "#material-ui/icons/Add";
const useStyles = makeStyles((theme) => ({
root: {
'& .MuiTextField-root': {
margin: theme.spacing(1)
},
},
button: {
margin: theme.spacing(1)
}}))
function CreateCourse() {
const classes = useStyles();
const [sectionFields, setSectionFields] = useState([{
sectionName: '',
overview: '',
videoContents: [{
videoName: '', videoUrl: ''
}]}])
function handleChangInput(index, event) {
const values = [...sectionFields];
values[index][event.target.name] = event.target.value;
setSectionFields(values);
}
function handleChangVideoInput(index, i, event) {
const values = [...sectionFields];
values[index].videoContents[i][event.target.name] = event.target.value;
setSectionFields(values);
console.log(index, event.target.name)
}
const handleSubmit = (event) => {
event.preventDefault();
console.log("Input Field ", sectionFields)
}
const handleRemoveFields = (index) => {
const values = [...sectionFields];
if(index > 0) values.splice(index, 1)
setSectionFields(values)
}
const handleRemoveVideoFields = (index, i) => {
const values = [...sectionFields];
if(i > 0)
values[index].videoContents.splice(i, 1)
setSectionFields(values)
}
const handleAddFields = (index) => {
setSectionFields((prevState => (
[...prevState, {
videoContents: [{videoName: '', videoUrl: ''}]
}]
)))
}
const handleAddVideoFields = (i) => {
setSectionFields(prev => {
const updatedSection = {
...prev[i],
videoContents: [
...prev[i].videoContents,
{videoName: '', videoUrl: ''},
],
};
return prev.map((section, index) => {
return index === i ? updatedSection : section;
});
})
}
return (
<div className='container mb-5'>
<Container>
<form className={classes.root} onSubmit={handleSubmit}>
{sectionFields.map((inputField, index) => (
<div key={index}>
<p className='mb-0 mt-3 ml-2'>Enter Section Name</p>
<TextField
name="sectionName"
label="Section Name"
variant="filled"
value={inputField?.sectionName}
onChange={event => handleChangInput(index, event)}
/>
<TextField
name="overview"
label="Section Overview"
variant="filled"
value={inputField?.overview}
onChange={event => handleChangInput(index, event)}
/>
<IconButton onClick={() => handleRemoveFields(index)}>
<RemoveIcon/>
</IconButton>
<IconButton onClick={() => handleAddFields(index)}>
<AddIcon/>
</IconButton>
{inputField?.videoContents?.map((v, i) => (
<div key={i}>
<TextField
name="videoName"
label="Enter Video Name"
variant="filled"
value={v.videoName}
onChange={event => handleChangVideoInput(index, i, event)}
/>
<TextField
name="videoUrl"
label="Enter Video Url"
variant="filled"
value={v.videoUrl}
onChange={event =>
handleChangVideoInput(index, i, event)}
/>
<IconButton onClick={() =>
handleRemoveVideoFields(index, i)}>
<RemoveIcon/>
</IconButton>
<IconButton onClick={() =>
handleAddVideoFields(index)}>
<AddIcon/>
</IconButton>
</div>
))}
</div>
))}
<Button
className={classes.button}
variant='contained'
color='primary'
type='submit'
endIcon={<Icon/>}
onClick={handleSubmit}
>
SEND
</Button>
</form>
</Container>
</div>
);
}
export default CreateCourse;
The component loads with 3 todos. If you check the middle one it should get a line through it. Then if you click the [x] button on it, it goes away, but for some reason the todo below it gets checked.
Anyone see the reason for this?
const Todo = props => {
const markCompleted = (checked, index) => {
const newTodos = [...props.todos];
newTodos[index].isCompleted = checked;
props.setTodos(newTodos);
};
const deleteTodo = index => {
const newTodos = [...props.todos];
newTodos.splice(index, 1);
props.setTodos(newTodos);
};
return (
<div
style={{ textDecoration: props.todo.isCompleted ? 'line-through' : '' }}
className="todo"
>
<input
type="checkbox"
onChange={e => markCompleted(e.target.checked, props.index)}
/>
{props.todo.text}
<button onClick={() => deleteTodo(props.index)}>x</button>
</div>
);
};
const TodoForm = props => {
const [value, setValue] = React.useState('');
const addTodo = e => {
e.preventDefault();
if (!value) return;
const newTodos = [...props.todos, { text: value }];
props.setTodos(newTodos);
setValue('');
};
return (
<form onSubmit={addTodo}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
};
const App = () => {
const [todos, setTodos] = React.useState([
{ text: 'Learn about React', isCompleted: false },
{ text: 'Meet friend for lunch', isCompleted: false },
{ text: 'Build really cool todo app', isCompleted: false }
]);
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo {...{ key: index, todo, index, todos, setTodos }} />
))}
<TodoForm {...{ todos, setTodos }} />
</div>
</div>
);
};
ReactDOM.render(
<App />
, document.querySelector('#react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
The previously checked checkbox remains rendered. You should explicitly set its checked status instead, so that it's taken from the props every time, rather than possibly from user input:
checked={props.todo.isCompleted}
You also should use functional methods like filter instead of using mutating methods like splice, and newTodos[index].isCompleted = checked; is mutating the todo object - [...props.todos] only shallow clones the array of objects. Spread the todos around the changed object into the array passed to setTodos instead.
props.setTodos([
...todos.slice(0, index),
{ ...todos[index], isCompleted: checked },
...todos.slice(index + 1),
]);
const Todo = props => {
const markCompleted = (checked, index) => {
const { todos } = props;
props.setTodos([
...todos.slice(0, index),
{ ...todos[index], isCompleted: checked },
...todos.slice(index + 1),
]);
};
const deleteTodo = index => {
props.setTodos(props.todos.filter((todo, i) => i !== index));
};
return (
<div
style={{ textDecoration: props.todo.isCompleted ? 'line-through' : '' }}
className="todo"
>
<input
type="checkbox"
onChange={e => markCompleted(e.target.checked, props.index)}
checked={props.todo.isCompleted}
/>
{props.todo.text}
<button onClick={() => deleteTodo(props.index)}>x</button>
</div>
);
};
const TodoForm = props => {
const [value, setValue] = React.useState('');
const addTodo = e => {
e.preventDefault();
if (!value) return;
const newTodos = [...props.todos, { text: value }];
props.setTodos(newTodos);
setValue('');
};
return (
<form onSubmit={addTodo}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
};
const App = () => {
const [todos, setTodos] = React.useState([
{ text: 'Learn about React', isCompleted: false },
{ text: 'Meet friend for lunch', isCompleted: false },
{ text: 'Build really cool todo app', isCompleted: false }
]);
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo {...{ key: index, todo, index, todos, setTodos }} />
))}
<TodoForm {...{ todos, setTodos }} />
</div>
</div>
);
};
ReactDOM.render(
<App />
, document.querySelector('#react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
i am trying to make a todo list in reacthook. my App.js:
import React,{useState} from 'react';
import AddTodo from './TodoFiles/AddTodo'
import TodoList from './TodoFiles/TodoList'
const defaultItems=[
{id:1,title:'Write React Todo Project',completed:true},
{id:2,title:'Upload it to github', completed:false}
]
const App=()=>{
const [items,setItems]=useState(defaultItems)
return(
<div style={{width:400}}>
<AddTodo items={items} setItems={setItems}/>
<br/>
<hr/>
<TodoList items={items}/>
<hr/>
</div>
)
}
export default App;
the addTodo.js is:
import React,{useState} from 'react'
const AddTodo=({items,setItems})=>{
const[title,setTitle]=useState('')
const handleTitle=(event)=>{
setTitle(event.target.value)
}
const handleAddTodo=()=>{
const NewItem={title}
setItems([NewItem,...items])
}
return(
<form onSubmit={e=>{e.preventDefault();handleAddTodo()}}>
<input type="text" placeholder="enter new task..." style={{width:350,height:15}}
value={title} onChange={handleTitle}/>
<input type="submit" style={{float:'right', marginTop:2}}/>
</form>
)
}
export default AddTodo
TodoList.js is:
import React, { useState } from "react";
const TodoItem = ({ title, completed, completeTodo, removeTodo, index }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(index)}>
Complete
</button>
<button style={{ float: "right" }} onClick={removeTodo}>
Remove
</button>
</div>
);
};
const TodoList = ({ items = [], index }) => {
const [, setItems] = useState("");
const completeTodo = index => {
console.log(index);
const newItem = [...items];
newItem[index].completed = true;
setItems(newItem);
};
const removeTodo = index => {
setItems(items.filter((p,index)=>p.index!==index))
};
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
CompeleteTodo has been resolved but when i press the remove button it is not working and nothing has been deleted. there is no error while executing npm start. developer tools showing a warning:
index.js:1 Warning: Failed prop type: You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.
in input (at TodoList.js:6)
in div (at TodoList.js:5)
in TodoItem (at TodoList.js:30)
in TodoList (at App.js:18)
in div (at App.js:13)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
what more i can to to fix it?
You don't set index in parameter of your function, simply you can do this:
const TodoItem = ({ title, completed, completeTodo, removeTodo, index }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(index)}>
Remove
</button>
<button style={{ float: "right" }} onClick={removeTodo}>
Complete
</button>
</div>
);
};
const TodoList = ({ items = [], index }) => {
const [, setItems] = useState("");
const completeTodo = index => {
console.log(index);
const newItem = [...items];
newItem[index].completed = true;
setItems(newItem);
};
const removeTodo = index => {
const newItem = [...items];
newItem.splice(index, 1);
setItems(newItem);
};
return items.map((p, index) => (
<TodoItem
{...p}
key={p.id}
index={index}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
and your remove and complete function is upside down :)
Check this
here is my suggestion > implement remove-complete functional with array items id and not index of elements.
please read this Lists and Keys
Here is Demo
App
import React, { useState } from "react";
import AddTodo from "./TodoFiles/AddTodo";
import TodoList from "./TodoFiles/TodoList";
const defaultItems = [
{ id: 1, title: "Write React Todo Project", completed: true },
{ id: 2, title: "Upload it to github", completed: false }
];
const App = () => {
const [items, setItems] = useState(defaultItems);
return (
<div style={{ width: 400 }}>
<AddTodo items={items} setItems={setItems} />
<br />
<hr />
<TodoList items={items} setItems={setItems} />
<hr />
</div>
);
};
export default App;
AddTodo
import React, { useState } from "react";
const AddTodo = ({ items, setItems }) => {
const [title, setTitle] = useState("");
const handleTitle = event => {
setTitle(event.target.value);
};
const handleAddTodo = () => {
const newItem = [
{
id: Math.max(...items.map(x => x.id), 0) + 1
completed: false,
title
},
...items
];
setItems(newItem);
};
return (
<form
onSubmit={e => {
e.preventDefault();
handleAddTodo();
}}
>
<input
type="text"
placeholder="enter new task..."
style={{ width: 350, height: 15 }}
value={title}
onChange={handleTitle}
/>
<input type="submit" style={{ float: "right", marginTop: 2 }} />
</form>
);
};
export default AddTodo;
TodoList
import React from "react";
import TodoItem from "./TodoItem";
const TodoList = ({ items, setItems }) => {
const completeTodo = id => {
setItems(
items.map(item => (item.id === id ? { ...item, completed: true } : item))
);
};
const removeTodo = id => {
setItems(items.filter(p => p.id !== id));
};
return items.map(p => (
<TodoItem
{...p}
key={p.id}
completeTodo={completeTodo}
removeTodo={removeTodo}
/>
));
};
export default TodoList;
TodoItem
import React from "react";
const TodoItem = ({ id, title, completed, completeTodo, removeTodo }) => {
return (
<div style={{ width: 400, height: 25 }}>
<input type="checkbox" checked={completed} onChange={() => {}} />
{title}
<button style={{ float: "right" }} onClick={() => completeTodo(id)}>
Complete
</button>
<button style={{ float: "right" }} onClick={() => removeTodo(id)}>
Remove
</button>
</div>
);
};
export default TodoItem;
Anyone know how to change the fontSize of the TableHeaderRow in a DevExtreme React Grid?
Here's an example of code from the website (https://devexpress.github.io/devextreme-reactive/react/grid/demos/featured/data-editing/) that I have been working with
import * as React from 'react';
import {
SortingState, EditingState, PagingState,
IntegratedPaging, IntegratedSorting,
} from '#devexpress/dx-react-grid';
import {
Grid,
Table, TableHeaderRow, TableEditRow, TableEditColumn,
PagingPanel, DragDropProvider, TableColumnReordering,
} from '#devexpress/dx-react-grid-material-ui';
import Paper from '#material-ui/core/Paper';
import Dialog from '#material-ui/core/Dialog';
import DialogActions from '#material-ui/core/DialogActions';
import DialogContent from '#material-ui/core/DialogContent';
import DialogContentText from '#material-ui/core/DialogContentText';
import DialogTitle from '#material-ui/core/DialogTitle';
import Button from '#material-ui/core/Button';
import IconButton from '#material-ui/core/IconButton';
import Input from '#material-ui/core/Input';
import Select from '#material-ui/core/Select';
import MenuItem from '#material-ui/core/MenuItem';
import TableCell from '#material-ui/core/TableCell';
import DeleteIcon from '#material-ui/icons/Delete';
import EditIcon from '#material-ui/icons/Edit';
import SaveIcon from '#material-ui/icons/Save';
import CancelIcon from '#material-ui/icons/Cancel';
import { withStyles } from '#material-ui/core/styles';
import { ProgressBarCell } from '../../../theme-sources/material-ui/components/progress-bar-cell';
import { HighlightedCell } from '../../../theme-sources/material-ui/components/highlighted-cell';
import { CurrencyTypeProvider } from '../../../theme-sources/material-ui/components/currency-type-provider';
import { PercentTypeProvider } from '../../../theme-sources/material-ui/components/percent-type-provider';
import {
generateRows,
globalSalesValues,
} from '../../../demo-data/generator';
const styles = theme => ({
lookupEditCell: {
paddingTop: theme.spacing.unit * 0.875,
paddingRight: theme.spacing.unit,
paddingLeft: theme.spacing.unit,
},
dialog: {
width: 'calc(100% - 16px)',
},
inputRoot: {
width: '100%',
},
});
const AddButton = ({ onExecute }) => (
<div style={{ textAlign: 'center' }}>
<Button
color="primary"
onClick={onExecute}
title="Create new row"
>
New
</Button>
</div>
);
const EditButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Edit row">
<EditIcon />
</IconButton>
);
const DeleteButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Delete row">
<DeleteIcon />
</IconButton>
);
const CommitButton = ({ onExecute }) => (
<IconButton onClick={onExecute} title="Save changes">
<SaveIcon />
</IconButton>
);
const CancelButton = ({ onExecute }) => (
<IconButton color="secondary" onClick={onExecute} title="Cancel changes">
<CancelIcon />
</IconButton>
);
const commandComponents = {
add: AddButton,
edit: EditButton,
delete: DeleteButton,
commit: CommitButton,
cancel: CancelButton,
};
const Command = ({ id, onExecute }) => {
const CommandButton = commandComponents[id];
return (
<CommandButton
onExecute={onExecute}
/>
);
};
const availableValues = {
product: globalSalesValues.product,
region: globalSalesValues.region,
customer: globalSalesValues.customer,
};
const LookupEditCellBase = ({
availableColumnValues, value, onValueChange, classes,
}) => (
<TableCell
className={classes.lookupEditCell}
>
<Select
value={value}
onChange={event => onValueChange(event.target.value)}
input={(
<Input
classes={{ root: classes.inputRoot }}
/>
)}
>
{availableColumnValues.map(item => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</Select>
</TableCell>
);
export const LookupEditCell = withStyles(styles, { name: 'ControlledModeDemo' })(LookupEditCellBase);
const Cell = (props) => {
const { column } = props;
if (column.name === 'discount') {
return <ProgressBarCell {...props} />;
}
if (column.name === 'amount') {
return <HighlightedCell {...props} />;
}
return <Table.Cell {...props} />;
};
const EditCell = (props) => {
const { column } = props;
const availableColumnValues = availableValues[column.name];
if (availableColumnValues) {
return <LookupEditCell {...props} availableColumnValues={availableColumnValues} />;
}
return <TableEditRow.Cell {...props} />;
};
const getRowId = row => row.id;
class DemoBase extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
columns: [
{ name: 'product', title: 'Product' },
{ name: 'region', title: 'Region' },
{ name: 'amount', title: 'Sale Amount' },
{ name: 'discount', title: 'Discount' },
{ name: 'saleDate', title: 'Sale Date' },
{ name: 'customer', title: 'Customer' },
],
tableColumnExtensions: [
{ columnName: 'amount', align: 'right' },
],
rows: generateRows({
columnValues: { id: ({ index }) => index, ...globalSalesValues },
length: 12,
}),
sorting: [],
editingRowIds: [],
addedRows: [],
rowChanges: {},
currentPage: 0,
deletingRows: [],
pageSize: 0,
pageSizes: [5, 10, 0],
columnOrder: ['product', 'region', 'amount', 'discount', 'saleDate', 'customer'],
currencyColumns: ['amount'],
percentColumns: ['discount'],
};
const getStateDeletingRows = () => {
const { deletingRows } = this.state;
return deletingRows;
};
const getStateRows = () => {
const { rows } = this.state;
return rows;
};
this.changeSorting = sorting => this.setState({ sorting });
this.changeEditingRowIds = editingRowIds => this.setState({ editingRowIds });
this.changeAddedRows = addedRows => this.setState({
addedRows: addedRows.map(row => (Object.keys(row).length ? row : {
amount: 0,
discount: 0,
saleDate: new Date().toISOString().split('T')[0],
product: availableValues.product[0],
region: availableValues.region[0],
customer: availableValues.customer[0],
})),
});
this.changeRowChanges = rowChanges => this.setState({ rowChanges });
this.changeCurrentPage = currentPage => this.setState({ currentPage });
this.changePageSize = pageSize => this.setState({ pageSize });
this.commitChanges = ({ added, changed, deleted }) => {
let { rows } = this.state;
if (added) {
const startingAddedId = rows.length > 0 ? rows[rows.length - 1].id + 1 : 0;
rows = [
...rows,
...added.map((row, index) => ({
id: startingAddedId + index,
...row,
})),
];
}
if (changed) {
rows = rows.map(row => (changed[row.id] ? { ...row, ...changed[row.id] } : row));
}
this.setState({ rows, deletingRows: deleted || getStateDeletingRows() });
};
this.cancelDelete = () => this.setState({ deletingRows: [] });
this.deleteRows = () => {
const rows = getStateRows().slice();
getStateDeletingRows().forEach((rowId) => {
const index = rows.findIndex(row => row.id === rowId);
if (index > -1) {
rows.splice(index, 1);
}
});
this.setState({ rows, deletingRows: [] });
};
this.changeColumnOrder = (order) => {
this.setState({ columnOrder: order });
};
}
render() {
const {
classes,
} = this.props;
const {
rows,
columns,
tableColumnExtensions,
sorting,
editingRowIds,
addedRows,
rowChanges,
currentPage,
deletingRows,
pageSize,
pageSizes,
columnOrder,
currencyColumns,
percentColumns,
} = this.state;
return (
<Paper>
<Grid
rows={rows}
columns={columns}
getRowId={getRowId}
>
<SortingState
sorting={sorting}
onSortingChange={this.changeSorting}
/>
<PagingState
currentPage={currentPage}
onCurrentPageChange={this.changeCurrentPage}
pageSize={pageSize}
onPageSizeChange={this.changePageSize}
/>
<IntegratedSorting />
<IntegratedPaging />
<CurrencyTypeProvider for={currencyColumns} />
<PercentTypeProvider for={percentColumns} />
<EditingState
editingRowIds={editingRowIds}
onEditingRowIdsChange={this.changeEditingRowIds}
rowChanges={rowChanges}
onRowChangesChange={this.changeRowChanges}
addedRows={addedRows}
onAddedRowsChange={this.changeAddedRows}
onCommitChanges={this.commitChanges}
/>
<DragDropProvider />
<Table
columnExtensions={tableColumnExtensions}
cellComponent={Cell}
/>
<TableColumnReordering
order={columnOrder}
onOrderChange={this.changeColumnOrder}
/>
<TableHeaderRow showSortingControls />
<TableEditRow
cellComponent={EditCell}
/>
<TableEditColumn
width={120}
showAddCommand={!addedRows.length}
showEditCommand
showDeleteCommand
commandComponent={Command}
/>
<PagingPanel
pageSizes={pageSizes}
/>
</Grid>
<Dialog
open={!!deletingRows.length}
onClose={this.cancelDelete}
classes={{ paper: classes.dialog }}
>
<DialogTitle>
Delete Row
</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure to delete the following row?
</DialogContentText>
<Paper>
<Grid
rows={rows.filter(row => deletingRows.indexOf(row.id) > -1)}
columns={columns}
>
<CurrencyTypeProvider for={currencyColumns} />
<PercentTypeProvider for={percentColumns} />
<Table
columnExtensions={tableColumnExtensions}
cellComponent={Cell}
/>
<TableHeaderRow />
</Grid>
</Paper>
</DialogContent>
<DialogActions>
<Button onClick={this.cancelDelete} color="primary">
Cancel
</Button>
<Button onClick={this.deleteRows} color="secondary">
Delete
</Button>
</DialogActions>
</Dialog>
</Paper>
);
}
}
export default withStyles(styles, { name: 'ControlledModeDemo' })(DemoBase);
The font size of the text labelling the columns (e.g. product, region, amount) is fixed, and I see no parameters that can change it. Any ideas?
I think there are a few ways around this, the way I have used is having a fully controlled component.
Looks a little like this
<TableHeaderRow cellComponent={this.ExampleHeaderCell} />
Where ExampleHeaderCell is a component that would look something like this
ExampleHeaderCell = (props: any) => (<TableHeaderRow.Cell
className={exampleClass}
{...props}
key={column.name}
getMessage={() => column.title}
/>)
From there you can pass it a class as shown with exampleClass
You can take this further and have it customised for a particular column.
ExampleHeaderCells = (props: any) => {
const exampleClass = css({ backgroundColor: "blue" })
const { column } = props
if (column.name === "name") {
return (
<TableHeaderRow.Cell
className={exampleClass}
{...props}
key={column.name}
getMessage={() => column.title}
/>
)
}
return <TableHeaderRow.Cell {...props} key={column.name} getMessage={() => column.title} />
}
The example above is returning a specific cell with the exampleClass if the column name is equal to "name". Otherwise it just returns the regular TableHeaderRow.Cell