So I have a table with coordinates, and when I click on one particular row it should be highlighted and other rows should be default color.
For now it looks like this:
const TableComponent = () => {
const [active, setActive] = useState(false);
useEffect(() => {
console.log(active);
}, [active]);
return (
<Table
dataSource={dataSource}
columns={columns}
rowClassName={active ? "green" : null}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
setActive(true);
}, // click row
};
}}
/>
);
};
export default TableComponent;
When I click on one row all of the rows get highlighted, how could I make it only to one row?
You can set the active record, and compare it with the record argument of the rowClassName prop function. If they are the same, then set your custom class name to this row you clicked.
rowClassName prop has function(record, index): string signature, you should always return a string instead of null.
type ID = string | number;
const TableComponent = () => {
const [activeRecord, setActiveRecord] = useState<{ id: ID }>();
console.log(activeRecord);
return (
<Table
dataSource={dataSource}
columns={columns}
rowClassName={(record) => record.id === activeRecord?.id ? "green" : ''}
onRow={(record) => {
return {
onClick: () => {
setActiveRecord(record);
},
};
}}
/>
);
};
export default TableComponent;
antd version: v5.0.5
const App = () => {
const [activeIndex, setActiveIndex] = useState()
return (
<Table
columns={columns}
dataSource={data}
rowClassName={(record, index) => (index === activeIndex ? 'green' : null)}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
setActiveIndex(rowIndex)
}, // click row
}
}}
/>
)
}
Related
I'm using function component to create a MUI dataGrid, and trying to add a button in a column, and I have a onRowClick function to open a side pane when user clicking row. The problem is, once I click row, react will report error:
Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
Here is the code:
const openViewPane = (params: GridRowParams, e): void => {
setRightSlidePlaneContent(
<ViewAccountPane
close={closeForm}
params={params}
/>,
);
setRightSlidePlaneOpen(true);
};
const formatDates = (columns): GridColDef[] => {
return columns;
};
const addTooltipsToData = (columns: GridColDef[]): GridColDef[] => {
console.log('render tool bar called');
return columns.map((column) => {
const { description, field, headerName } = column;
console.log('inside map');
if (field === ID) {
console.log('直接return');
return column;
}
return {
...column,
renderCell: (): JSX.Element => {
console.log('render run');
return (
<Tooltip arrow title={description || ''} >
<span className={classes.headerCell}>{headerName}</span>
</Tooltip>
);
},
};
});
};
const formatColumns = (columns: GridColDef[]): GridColDef[] => {
const dateFormatted = formatDates(columns);
return addTooltipsToData(dateFormatted);
};
console.log('generic table rendered');
return (
<MuiThemeProvider theme={theme}>
<DataGrid
columns={formatColumns(columns)}
rows={rows}
autoHeight
className={classes.table}
components={{
Toolbar: CustomToolbar,
}}
density={GridDensityTypes.Compact}
filterMode={tableMode}
hideFooterSelectedRowCount
loading={loading}
onFilterModelChange={handleFilterChange}
onSortModelChange={handleSortChange}
sortModel={sortModel}
sortingMode={tableMode}
onRowClick={openViewPane}
/>
</MuiThemeProvider>
);
However, if I change the renderCell to renderHeader, it will work fine.
setRightSlidePlaneContent
setRightSlidePlaneOpen
Above are two state passed by parent component in props. it will open a slide pane.
After I comment setRightSliePlaneOpen, it will work well. But no slide pane show.
Please help me slove it. Or do you know how can I add a button in column not using renderCell?
const PageFrame: FC<IProps> = (props: IProps) => {
const classes = useStyles();
const dispatch = useAppDispatch();
const { Component, userInfo } = props;
const [navBarOpen, setNavBarOpen] = useState(false);
const [rightSlidePlaneOpen, setRightSlidePlaneOpen] = useState(false);
const [rightSlidePlaneContent, setRightSlidePlaneContent] = useState(
<Fragment></Fragment>,
);
const [rightSlidePlaneWidthLarge, setRightSlidePlaneWidthLarge] = useState(
false,
);
useEffect(() => {
dispatch({
type: `${GET_USER_LOGIN_INFO}_${REQUEST}`,
payload: {
empId: userInfo.empId,
auth: { domain: 'GENERAL_USER', actionType: 'GENERAL_USER', action: 'VIEW', empId: userInfo.empId},
},
meta: { remote: true },
});
}, []);
return (
<div className={classes.root}>
<HeaderBar
navBarOpen={navBarOpen}
toggleNavBarOpen={setNavBarOpen}
/>
<NavigationBar open={navBarOpen} toggleOpen={setNavBarOpen} />
<Component
setRightSlidePlaneContent={setRightSlidePlaneContent}
setRightSlidePlaneOpen={setRightSlidePlaneOpen}
setRightSlidePlaneWidthLarge={setRightSlidePlaneWidthLarge}
/>
<PersistentDrawerRight
content={rightSlidePlaneContent}
open={rightSlidePlaneOpen}
rspLarge={rightSlidePlaneWidthLarge}
/>
</div>
);
};
export default PageFrame;
The component that calls setRightSidePlaneOpen
interface IProps {
setRightSlidePlaneContent: React.Dispatch<React.SetStateAction<JSX.Element>>;
setRightSlidePlaneOpen: React.Dispatch<React.SetStateAction<boolean>>;
setRightSlidePlaneWidthLarge: React.Dispatch<SetStateAction<boolean>>;
}
const TagDashboard = (props: IProps): JSX.Element => {
const { setRightSlidePlaneContent, setRightSlidePlaneOpen, setRightSlidePlaneWidthLarge } = props;
const employeeId = useAppSelector((store) => store.userInfo.info.employeeNumber);
const rows = useAppSelector((state) => state.tag.rows);
const accountId = useAppSelector(store => store.userInfo.accountId);
const updateContent = useAppSelector(state => state.tag.updateContent);
const numOfUpdates = useAppSelector(state => state.tag.numOfUpdates);
const dispatch = useAppDispatch();
const closeAddForm = (): void => {
setRightSlidePlaneContent(<Fragment />);
setRightSlidePlaneOpen(false);
};
const openAddForm = (): void => {
setRightSlidePlaneContent(
<AddForm
category={'tag'}
close={closeAddForm}
title={ADD_FORM_TITLE}
createFunction={createTag}
/>);
setRightSlidePlaneOpen(true);
};
const closeForm = (): void => {
setRightSlidePlaneContent(<Fragment />);
setRightSlidePlaneOpen(false);
setRightSlidePlaneWidthLarge(false);
};
const openViewPane = (params: GridRowParams, e): void => {
setRightSlidePlaneContent(
<ViewAccountPane
close={closeForm}
params={params}
/>,
);
setRightSlidePlaneOpen(true);
setRightSlidePlaneWidthLarge(true);
};
// to the RSP.
return (
<GenericDashboard
addFunction={openAddForm}
description={DESCRIPTION}
title={TITLE}
columns={columns}
handleRowClick={openViewPane}
rows={rows}
numOfUpdates={numOfUpdates}
updateContent={updateContent}
/>
);
};
This is the component of the right slide pane
const { content, open, rspLarge } = props;
const classes = useStyles();
const drawerClass = rspLarge ? classes.drawerLarge : classes.drawer;
const drawerPaperClass = rspLarge ? classes.drawerPaperLarge : classes.drawerPaper;
return (
<div className={classes.root}>
<CssBaseline />
<Drawer
className={drawerClass}
variant='temporary'
anchor='right'
open={open}
classes={{
paper: drawerPaperClass,
}}
>
<Fragment>{content}</Fragment>
</Drawer>
</div>
);
I am a beginner with javscript So i will be thankful for explanation.
{isolate_list.map((row) => {
return (
<FormControlLabel
control={
<Checkbox
color="primary"
checked={!!checked}
onChange={toggleCheckbox}
name="checkedA"
>
{" "}
</Checkbox>
}
label={row.isolatename}
>
{""}
</FormControlLabel>
);
})}
and i have this button
<Button
onClick={selectall}
style={{ margin: 50 }}
variant="outlined"
label="SELECT ALL ISOLATES"
>
SELECT ALL ISOLATES
</Button>
Can anyone help how can i use the button to select all checkboxes and in the same time i can select every checkbox alone by clicking on it?
I beginn with this part but i am not sure
const [checked, setChecked] = React.useState(true);
const toggleCheckbox = (event) => {
setChecked(event.target.checked);
};
You should hold checkbox value's in the and give the state value as a property to each. For example
<Checkbox
color="primary"
onChange={toggleCheckbox}
name="checkedA"
value={checked}
>
And then in the onClick function
setChecked();
The simplest implementations(without any form manager):
Declare state to store our checked ids array.
const [checkedIds, setCheckedIds] = useState([]);
implement handler.
const handleCheck = useCallback((id) => {
return () => {
setCheckedIds(prevIds => prevIds.includes(id) ? prevIds.filter(item => item !== id) : [...prevIds, id]);
};
}, []);
render our checkboxes and apply handler.
list.map(({ id, isolatename }) => (
<FormControlLabel
key={id}
control={
<Checkbox
color="primary"
checked={checkedIds.includes(id)}
onChange={handleCheck(id)}
name={`checkbox_${id}`}
/>
}
label={isolatename}
/>)
))
ps. in case if <Checkbox/> props 'onChange' returns callback like this (isChecked: boolean) => {} we can simplify (2) step.
const handleCheck = useCallback(id => {
return isChecked => {
setCheckedIds(prevIds => isChecked ? prevIds.filter(item => item == id) : [...prevIds, id]);
};
}, []);
You may remember that it is React JS and not only JS that we are talking about.
In React you want to control data in the way of a state. There are a lot of ways to do so with check boxes, I'm contributing with one that you can see in the code snippet below:
import React, {useState} from "react";
export default function CheckBoxesControllers() {
const [checkboxes, setCheckboxes] = useState(() => [
{ id: "0", checked: false },
{ id: "1", checked: false },
{ id: "2", checked: false },
]);
const handleUpdate = (event) => {
const { target: {id, checked} } = event;
setCheckboxes(currentState => {
const notToBeUpdated = currentState.filter(input => input.id !== id);
return [
...notToBeUpdated,
{ id, checked }
]
});
}
function toggleSelectAll() {
setCheckboxes(currentState => currentState.map(checkbox => ({...checkbox, checked: !checkbox.checked})));
}
return (
<>
{checkboxes?.length ? (
checkboxes.map((checkbox, index) => {
return (
<input
checked={checkbox.checked}
id={checkbox.id}
key={index}
type="checkbox"
onChange={event => handleUpdate(event)}
/>
);
})
) : <></>}
<button onClick={toggleSelectAll}>Toggle Select All</button>
</>
)
}
This code is meant to serve you as an example of how to work properly with react state in the hook way, but there are other way, as you can see in the Documentation
How do I add a function to connect to one of my components onChange? Creating a function like this returns an error code of 'cardActionResponse' is not defined.
What the benefit of using a const class like this?
const Target = props => {
const { markAsDone } = useContext(ItemContext);
const [{ isOver }, drop] = useDrop({
accept: 'Item',
drop: (item, monitor) => console.log(item),
collect: monitor => ({
isOver: !!monitor.isOver()
})
})
//Cannot do this. How else can I make a function to connect to CreateVideoCard?
cardActionResponse = (event) => {
console.log(event);
}
return (
<div className="target top80 right30" ref={drop} style={{ backgroundColor: isOver ? 'black' : '' }} >
<TitleDescription class="z1"/>
<div class="right10 left10">
<CreateVideoCard onChange={this.cardActionResponse} />
<CreateDescriptionCard></CreateDescriptionCard>
<CreateAudioCard></CreateAudioCard>
<CreateTermsCard></CreateTermsCard>
</div>
</div>
);
};
export default Target;
Functional components don't have it's own context (this), so you should simply use const variable.
Please use
const cardActionResponse = (event) => {
console.log(event);
}
and then
<CreateVideoCard onChange={cardActionResponse} />
I am using Fluent UI DetailsList. In the example the component is implemented as a class component but I am using a functional component.
I am having difficulties in getting the selected items, I assume and think my implementation is incorrect. The problem is I do not get ANY selected items.
export const JobDetails = () => {
const { actions, dispatch, isLoaded, currentTabJobs, activeTabItemKey } = useJobDetailsState()
let history = useHistory();
useEffect(() => {
if (actions && dispatch) {
actions.getJobListDetails()
}
}, [actions, dispatch])
const getSelectionDetails = (): string => {
let selectionCount = selection.getSelectedCount();
switch (selectionCount) {
case 0:
return 'No items selected';
case 1:
return '1 item selected: ' + (selection.getSelection()[0] as any).name;
default:
return `${selectionCount} items selected`;
}
}
const [selectionDetails, setSelectionDetails] = useState({})
const [selection, setSelection] = useState(new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails())
}))
useEffect(() => {
setSelection(new Selection({
onSelectionChanged: () => setSelectionDetails(getSelectionDetails())
}))
},[selectionDetails])
return (
<div>
<MarqueeSelection selection={selection}>
<DetailsList
items={currentTabJobs}
groups={getGroups()}
columns={_columns}
selection={selection}
selectionPreservedOnEmptyClick={true}
groupProps={{
onRenderHeader: props => {
return (
<GroupHeader
{...props}
selectedItems={selection}
/>
)
},
showEmptyGroups: true
}}
/>
</MarqueeSelection>
</div>
)
}
export default JobDetails;
I might have a more simple answer, this example is for a list with 'SelectionMode.single' activated but I think the principle of getting the selected item remains the same
const [selectedItem, setSelectedItem] = useState<Object | undefined>(undefined)
const selection = new Selection({
onSelectionChanged: () => {
setSelectedItem(selection.getSelection()[0])
}
})
useEffect(() => {
// Do something with the selected item
console.log(selectedItem)
}, [selectedItem])
<DetailsList
columns={columns}
items={items}
selection={selection}
selectionMode={SelectionMode.single}
selectionPreservedOnEmptyClick={true}
setKey="exampleList"
/>
I found a solution to the problem I was having and I had to memorize the details list
What I did:
const [selectedItems, setSelectedItems] = useState<IObjectWithKey[]>();
const selection = useMemo(
() =>
new Selection({
onSelectionChanged: () => {
//console.log('handle selection change',selection.getSelection())
setSelectedItems(selection.getSelection());
},
selectionMode: SelectionMode.multiple,
}),
[]);
const detailsList = useMemo(
() => (
<MarqueeSelection selection={selection}>
<DetailsList
items={currentTabJobs}
groups={getGroups()}
columns={columns}
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
ariaLabelForSelectionColumn="Toggle selection"
checkButtonAriaLabel="Row checkbox"
selection={selection}
selectionPreservedOnEmptyClick={true}
groupProps={{
onRenderHeader: (props) => {
return <GroupHeader {...props} selectedItems={selection} />;
},
showEmptyGroups: true,
}}
onRenderItemColumn={(item, index, column) =>
renderItemColumn(item, index!, column!)
}
/>
</MarqueeSelection>
),
[selection, columns, currentTabJobs, activeTabItemKey]
);
return (
<div>
{detailsList}
</div>
)
Put the selection object in a state.
Example:
...
export const Table: FunctionComponent<TableProps> = props => {
const { items, columns } = props
const { setCopyEnabled } = useCommandCopy()
const { setDeleteEnabled } = useCommandDelete()
const onSelectionChanged = () => {
if (selection.getSelectedCount() === 0) {
setCopyEnabled(false)
setDeleteEnabled(false)
}
else if (selection.getSelectedCount() === 1) {
setCopyEnabled(true)
setDeleteEnabled(true)
}
else {
setCopyEnabled(false)
setDeleteEnabled(true)
}
}
...
const [selection] = useState(new Selection({ onSelectionChanged: onSelectionChanged }))
useEffect(() => {
selection.setAllSelected(false)
}, [selection])
...
return (
<ScrollablePane styles={{
root: {
position: 'fixed',
top: 105, left: 285, right: 20, bottom: 20
},
}}>
<DetailsList
items={items}
columns={columns}
selection={selection}
selectionMode={SelectionMode.multiple}
layoutMode={DetailsListLayoutMode.justified}
constrainMode={ConstrainMode.horizontalConstrained}
...
/>
</ScrollablePane>
)
}
I think the main issue here is onSelectionChanged function is getting called twice, second time with empty data. Reason I found is React useState method re-rendering the data. Solution that worked for me here :
Store value in a normal variable instead of state variable(if you don't want to re-render detailslist after this):
let selectedItem = undefined;
const selection = new Selection({
onSelectionChanged: () => {
selectedItem = selection.getSelection()
// console.log(selectedItem)
// You can use selectedItem value later anywhere you want to
// track your selection.
}
})
<DetailsList
columns={columns}
items={items}
selection={selection}
selectionMode={SelectionMode.multiple}
selectionPreservedOnEmptyClick={true}
setKey="exampleList"
/>
I have a React component which is trying to render another component that has function inside it as a child. When I try to render that component it returns an [Object]. I am trying to find another way to render that child component.
Right now, I tried to render it with React.createElement(), yet it also returned an object. I am using react-beautiful-dnd library to use Drag and Drop feature. This library has Droppable component and it takes a function inside which has two parameters, provided, snapshot.
Since it takes a function, when I try to render Droppable component, it returns an object instead of react element.
DroppableContent.js
const DroppableContent = ({ droppedBlank, label, id }) => (
<Droppable droppableId={id || _.uniqueId('droppable_')}>
{(provided, snapshot) => (
<span ref={provided.innerRef} style={{ display: 'inline' }} {...provided.droppableProps}>
{/* blank placeholder */}
<span className={droppedBlank ? styles.dropped : styles.placeholder}
style={{ backgroundColor: !droppedBlank && snapshot.isDraggingOver ? '#88d2ee' : '#fff' }}
>
{droppedBlank ? <BlankItem label={label} index={id} /> : null}
</span>
</span>
)}
</Droppable>
);
DragAndDrop.js where I call QuestionPreview component.
import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
import _ from 'lodash';
import * as questionStyles from '../Questions.less';
import BlankList from './BlankList';
import BlankItem from './BlankItem';
import QuestionPreview from './QuestionPreview';
const DragAndDrop = ({
question, onAnswer, answer, hideTitle, className, t, readOnly,
}) => {
const handleDragEnd = (result) => {
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (destination.droppableId === source.droppableId && destination.index === source.index) {
return;
}
const destinationId = destination.droppableId;
const sourceId = draggableId;
const blank = {
textIndex: destinationId,
id: sourceId,
// answer: _.find(questionBlanks, b => b.id === sourceId).answer,
};
let updatedBlanks;
if (destinationId === 'answerBlanks') {
updatedBlanks = _.filter(answer.blanks, item => item.id !== blank.id);
} else {
updatedBlanks = _.filter(answer.blanks, item => item.textIndex !== blank.textIndex);
updatedBlanks.push(blank);
}
onAnswer(question, { blanks: updatedBlanks });
};
const blankLabels = currentLabels => _.filter(currentLabels, l => !_.includes(_.map(answer.blanks, ab => ab.id), l.id)).map((label, index) => (
<BlankItem key={label.id} label={label} index={index} />
));
const blankItems = currentBlanks => _.map(currentBlanks, (currentBlank, index) => (
<BlankItem key={currentBlank.id} label={currentBlank} index={index} readOnly />
));
const { text } = question;
const shuffledLabels = question.labels && _.shuffle(question.labels);
// filtering answers from blank items so that whenever we drag an item to a blank,
// answer will be removed.
const filteredLabels = shuffledLabels && blankLabels(shuffledLabels);
const filteredBlanks = blankItems(question.blanks);
return (
<DragDropContext onDragEnd={handleDragEnd}>
<div>
<p style={{ fontWeight: 600 }}>
{t('defaultDndText', { numberOfBlanks: question.blanks.length })}
</p>
<Droppable droppableId="answerBlanks">
{provided => (
<div>
<BlankList innerRef={provided.innerRef} {...provided.droppableProps}>
{readOnly ? filteredBlanks : filteredLabels}
</BlankList>
</div>
)}
</Droppable>
{!hideTitle && (
<QuestionPreview blanks={_.filter(question.blanks, blank => blank.textIndex < 100)}
labels={question.labels}
selectedBlanks={answer.blanks}
text={text}
className={[questionStyles.title, className].join(' ')}
/>
)}
</div>
</DragDropContext>
);
};
DragAndDrop.propTypes = {
question: PropTypes.shape({
text: PropTypes.string,
}).isRequired,
answer: PropTypes.shape({
blanks: PropTypes.arrayOf(PropTypes.shape({})),
}),
readOnly: PropTypes.bool,
disabled: PropTypes.bool,
hideTitle: PropTypes.bool,
onAnswer: PropTypes.func,
className: PropTypes.string,
};
DragAndDrop.defaultProps = {
onAnswer: () => {},
disabled: false,
hideTitle: false,
className: '',
answer: { blanks: [] },
readOnly: false,
};
export default withTranslation('question')(DragAndDrop);
QuestionPreview.js where I try to render DroppableContent component.
const QuestionPreview = ({
text, labels, selectedBlanks, readOnly,
}) => {
const readOnlyContent = (id) => {
const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank => blank.textIndex === id);
const label = droppedBlank && _.find(labels, l => l.id === droppedBlank.id);
return (
<span className={droppedBlank ? styles.dropped : styles.placeholder}>
{droppedBlank && <BlankItem label={label} readOnly />}
</span>
);
};
const splittedText = splitTextWithBlanks(text);
const blankIndices = getBlankIndices(text);
const getContentId = index => blankIndices[index];
const tempArray = [];
const html = () => {
_.map(splittedText, (element, index) => {
const contentId = getContentId(index);
const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank => blank.textIndex === contentId);
const label = droppedBlank && _.find(labels, l => l.id === droppedBlank.id);
const blankContent = readOnly ? readOnlyContent(contentId) : <DroppableContent id={contentId} droppedBlank={droppedBlank} label={label} />;
const htmlContent = <span dangerouslySetInnerHTML={{ __html: toHTML(element) }} />;
tempArray.push(htmlContent);
if (index !== splittedText.length - 1) {
tempArray[index] = tempArray[index] + blankContent;
}
});
return tempArray;
};
const createdElement = React.createElement('div', null, html());
return createdElement;
};
This does not return any error but what I want to achieve is that combining htmlContent variable with blankContent. When I do that, it does render blankContent as an Object. In the end, I just want to find a way to parse Droppable component.
You might have error in the following line
const blankContent = readOnly ? readOnlyContent : <DroppableContent id={contentId} droppedBlank={droppedBlank} label={label} />;
You are passing reference of readOnlyContent, May be you want to call
readOnlyContent (contentId) .BTW your code is complex and hard to maintain/read. try to refactor it
Edit 1 Try this QuestionPreview.js
const QuestionPreview = ({
text, labels, selectedBlanks, readOnly,
}) => {
const readOnlyContent = (id) => {
const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank =>
blank.textIndex === id);
const label = droppedBlank && _.find(labels, l => l.id ===
droppedBlank.id);
return (
<span className={droppedBlank ? styles.dropped : styles.placeholder}>
{droppedBlank && <BlankItem label={label} readOnly />}
</span>
);
};
const splittedText = splitTextWithBlanks(text);
const blankIndices = getBlankIndices(text);
const getContentId = index => blankIndices[index];
const tempArray = [];
const html = () => {
return _.map(splittedText, (element, index) => {
const contentId = getContentId(index);
const droppedBlank = selectedBlanks && _.find(selectedBlanks, blank =>
blank.textIndex === contentId);
const label = droppedBlank && _.find(labels, l => l.id ===
droppedBlank.id);
const blankContent = readOnly ? readOnlyContent(contentId) :
<DroppableContent id={contentId} droppedBlank={droppedBlank} label=
{label}
/>;
let htmlContent = <span dangerouslySetInnerHTML={{ __html:
toHTML(element) }} />;
if (index !== splittedText.length - 1) {
return (
<Fragment>
{htmlContent}
{blankContent}
</Fragment>
)
}
return htmlContent
});
};
return (
{html()}
)
};