Rerender Tooltip when Scrolling React - css

Thanks for your help in advance, I have an issue with a tooltip, it is supposed that I should show the tooltip when a condition is given, but due to the scroll when rerendering the list the validation fails.
Here is working right, the complete list shows the tooltips where it is supposed to be. enter image description here
But then, when I scroll down the view is re-render and the tooltip fails. enter image description here
The idea is that the tooltip (and the underline) should be shown when I have group names too long using this boolean disableHoverListener={textDoesOverflow}, and it is working at the beginning but then ... fails.
Here's the code and the styles.
Please help!!
export const BaseFilteredUsersGroups: React.FC<IFilteredUsersGroups> = (props) => {
const {
userId,
filteredGroupIds = [],
localize,
} = props;
const sizeGroupsRef = React.useRef(null);
const sizeTitleRef = React.useRef(null);
const styles = useStyles();
const usersGroups = useSelector((state: IDuxStore) => {
const groups = filteredGroupIds.map(groupId => select.group.getGroupByGroupId(state, groupId));
return groups.filter(group => group?.memberships?.some(user => user.userId === userId));
});
const labelTitle = localize.formatItems(usersGroups.map(group => group.title));
const textDoesOverflow = sizeGroupsRef?.current?.getBoundingClientRect()?.width >= sizeTitleRef?.current?.getBoundingClientRect()?.width;
const finalStyle = textDoesOverflow ? styles.groupTitle : styles.groupTitleOverflow;
return (<div className={styles.usersGroups} ref={sizeGroupsRef}>
{<Tooltip title={labelTitle} disableHoverListener={textDoesOverflow} placement="top" onScrollCapture={}>
{<span className={finalStyle} ref={sizeTitleRef}>
{labelTitle}
</span>}
</Tooltip>}
</div >);
};
Here the styles:
export const useStyles = makeStyles(theme => {
return createStyles({
usersGroups:{
textOverflow: 'ellipsis',
overflow: 'hidden',
},
groupTitle: {
whiteSpace: 'nowrap',
fontWeight: theme.typography.fontWeightMedium,
color: theme.palette.text.secondary,
},
groupTitleOverflow: {
whiteSpace: 'nowrap',
fontWeight: theme.typography.fontWeightMedium,
color: theme.palette.text.secondary,
textDecorationLine: 'underline',
}
});
});

const textDoesOverflow =
sizeGroupsRef?.current?.getBoundingClientRect()?.width
>= sizeTitleRef?.current?.getBoundingClientRect()?.width;
const finalStyle = textDoesOverflow ? styles.groupTitle : styles.groupTitleOverflow;
The conditional logic here is reversed. Right now if the text width is greater than the sizeTitleRef width it will return groupTitle not groupTitleOverflow. So instead you may want to switch up the ternary operator to this:
const finalStyle = textDoesOverflow ? styles.groupTitleOverflow : styles.groupTitle;

Related

change the id inside an svg on scroll at viewport in React

So I have an svg with an animation, but I want the animation to start when I'm scolled on viewport. I cant quite get there tho.
My code:
export const useScrollHandler = () => {
const [scroll, setScroll] = useState(1);
useEffect(() => {
const onScroll = () => {
const scrollCheck = window.scrollY > 100;
setScroll(scrollCheck);
};
document.addEventListener("scroll", onScroll);
return () => {
document.removeEventListener("scroll", onScroll);
};
}, [scroll, setScroll]);
return scroll;
};
then I add it on my svg component ( i wont put the whole svg cause its too long):
const ImageEssentials = (props) => {
const scroll = useScrollHandler();
<svg ...>
<g id={scroll ? "" : "Box1"}>
</svg>
}
So basicly when I scroll i want the svg group to get the id "Box1" that it has the animation.

Write JS inside material ui styles

I wanted to put some conditional logic inside mui styles
Currently using inline style, which works
<div className={classes.form} style={{alignItems: ((Code.length>10 || Description.length>100) ? 'start' : 'center')}}>
...
</div>
But want to do the same thing inside mui styles, which gives error
// Code and Description state
...
const useStyles = makeStyles(theme => ({
form: {
alignItems: {(Code.length>10 || Description.length>100) ? 'start' : 'center'}, //Code not defined
}
You can pass a props the the useStyles hook and use it like this:
const classes = useStyles(props);
const useStyles = makeStyles(theme => ({
form: {
alignItems: props => props.Code.length>10 || props.Description.length>100 ? 'start' : 'center',
}
You never mentioned how Code and Description are defined, but you can pass props to makeStyles.
const useStyles = makeStyles(theme => ({
form: {
alignItems: props => (props.Code.length > 10 || props.Description.length > 100) ? 'start' : 'center'
}
}));
// Usage
const classes = useStyles(props);

Style for disabled property in css object model

For some reason, I need to pass CSS objects using CSS object model inside a react component. Here, I need styles for buttons when they are disabled and when not disabled. Like we do with: backgroundColor, borderRadius and such.
const controlButton = {
backgroundColor:"#006191",
padding:"10px 20px",
border:"none",
borderRadius:"5px"
}
This is where im trying to use it and wanto to pass the object in style.
const App = () => {
const [disable, setDisable] = useState(false);
return(
<div>
<button style={(style.active, disable && style.disable)}>Click</button>
</div>
)
}
Not too elegant but working solution.
import React, { useState } from 'react';
const controlButtonEnabled = {
backgroundColor: '#006191',
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
};
const controlButtonDisabled = {
backgroundColor: 'gray',
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
};
export default function MyAwesomeComponent() {
const [enabled, setEnabled] = useState(true);
const onEnable = () => {
setEnabled(!enabled);
};
return (
<button
onClick={onEnable}
style={enabled ? controlButtonEnabled : controlButtonDisabled}
>
Button
</button>
>
);
}

changing Material-ui table style in row.getRowProps()

In my react application I'm using Material-UI enhanced table which is based on react-table and I would like to change the style of their rows.
Reading from documentation (https://material-ui.com/api/table-row/) in the component TableRow should be used the prop "classes" to change the style, but in the MaterialUi code props are read this way:
<TableRow {...row.getRowProps()}>
My question is how can I use the prop classes if the TableRow props are added automatically? I thought I needed to have this:
<TableRow classes="rowStyle ">
where rowStyle is:
const styles = {
rowStyle : {
padding: 10,
border: "1px solid red"
}
};
But obviously I can't this way, how can I add "classes" to the getRowProps() and the new style in it?
I couldn't find an explanation or a good example in official documentation or stackOverflow
Many thanks for the help
EnhancedTable.js:
import React from "react";
import Checkbox from "#material-ui/core/Checkbox";
import MaUTable from "#material-ui/core/Table";
import PropTypes from "prop-types";
import TableBody from "#material-ui/core/TableBody";
import TableCell from "#material-ui/core/TableCell";
import TableContainer from "#material-ui/core/TableContainer";
import TableFooter from "#material-ui/core/TableFooter";
import TableHead from "#material-ui/core/TableHead";
import TablePagination from "#material-ui/core/TablePagination";
import TablePaginationActions from "./TablePaginationActions";
import TableRow from "#material-ui/core/TableRow";
import TableSortLabel from "#material-ui/core/TableSortLabel";
import TableToolbar from "./TableToolbar";
import {
useGlobalFilter,
usePagination,
useRowSelect,
useSortBy,
useTable,
} from "react-table";
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<div>
<Checkbox ref={resolvedRef} {...rest} />
</div>
);
}
);
const inputStyle = {
padding: 0,
margin: 0,
border: 0,
background: "transparent",
};
// Create an editable cell renderer
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
updateMyData, // This is a custom function that we supplied to our table instance
}) => {
// We need to keep and update the state of the cell normally
const [value, setValue] = React.useState(initialValue);
const onChange = (e) => {
setValue(e.target.value);
};
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value);
};
// If the initialValue is changed externall, sync it up with our state
React.useEffect(() => {
setValue(initialValue);
}, [initialValue]);
return (
<input
style={inputStyle}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
);
};
EditableCell.propTypes = {
cell: PropTypes.shape({
value: PropTypes.any.isRequired,
}),
row: PropTypes.shape({
index: PropTypes.number.isRequired,
}),
column: PropTypes.shape({
id: PropTypes.number.isRequired,
}),
updateMyData: PropTypes.func.isRequired,
};
// Set our editable cell renderer as the default Cell renderer
const defaultColumn = {
Cell: EditableCell,
};
const EnhancedTable = ({
columns,
data,
setData,
updateMyData,
skipPageReset,
}) => {
const {
getTableProps,
headerGroups,
prepareRow,
page,
gotoPage,
setPageSize,
preGlobalFilteredRows,
setGlobalFilter,
state: { pageIndex, pageSize, selectedRowIds, globalFilter },
} = useTable(
{
columns,
data,
defaultColumn,
autoResetPage: !skipPageReset,
// updateMyData isn't part of the API, but
// anything we put into these options will
// automatically be available on the instance.
// That way we can call this function from our
// cell renderer!
updateMyData,
},
useGlobalFilter,
useSortBy,
usePagination,
useRowSelect,
(hooks) => {
hooks.allColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox. Pagination is a problem since this will select all
// rows even though not all rows are on the current page. The solution should
// be server side pagination. For one, the clients should not download all
// rows in most cases. The client should only download data for the current page.
// In that case, getToggleAllRowsSelectedProps works fine.
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]);
}
);
const handleChangePage = (event, newPage) => {
gotoPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setPageSize(Number(event.target.value));
};
const removeByIndexs = (array, indexs) =>
array.filter((_, i) => !indexs.includes(i));
const deleteUserHandler = (event) => {
const newData = removeByIndexs(
data,
Object.keys(selectedRowIds).map((x) => parseInt(x, 10))
);
setData(newData);
};
const addUserHandler = (user) => {
const newData = data.concat([user]);
setData(newData);
};
// Render the UI for your table
return (
<TableContainer>
<TableToolbar
numSelected={Object.keys(selectedRowIds).length}
deleteUserHandler={deleteUserHandler}
addUserHandler={addUserHandler}
preGlobalFilteredRows={preGlobalFilteredRows}
setGlobalFilter={setGlobalFilter}
globalFilter={globalFilter}
/>
<MaUTable {...getTableProps()}>
<TableHead>
{headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<TableCell
{...(column.id === "selection"
? column.getHeaderProps()
: column.getHeaderProps(column.getSortByToggleProps()))}
>
{column.render("Header")}
{column.id !== "selection" ? (
<TableSortLabel
active={column.isSorted}
// react-table has a unsorted state which is not treated here
direction={column.isSortedDesc ? "desc" : "asc"}
/>
) : null}
</TableCell>
))}
</TableRow>
))}
</TableHead>
<TableBody>
{page.map((row, i) => {
prepareRow(row);
return (
<TableRow {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<TableCell {...cell.getCellProps()}>
{cell.render("Cell")}
</TableCell>
);
})}
</TableRow>
);
})}
</TableBody>
<TableFooter>
<TableRow>
<TablePagination
rowsPerPageOptions={[
5,
10,
25,
{ label: "All", value: data.length },
]}
colSpan={3}
count={data.length}
rowsPerPage={pageSize}
page={pageIndex}
SelectProps={{
inputProps: { "aria-label": "rows per page" },
native: true,
}}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
ActionsComponent={TablePaginationActions}
/>
</TableRow>
</TableFooter>
</MaUTable>
</TableContainer>
);
};
EnhancedTable.propTypes = {
columns: PropTypes.array.isRequired,
data: PropTypes.array.isRequired,
updateMyData: PropTypes.func.isRequired,
setData: PropTypes.func.isRequired,
skipPageReset: PropTypes.bool.isRequired,
};
export default EnhancedTable;
Correct me if i'm wrong, but i think the first step to your solution is the correct definition of styles for the material-ui element. I can't say for certain if there is another way to generate these styles, but a simple object such as:
const inputStyle = { padding: 0, margin: 0, border: 0, background: "transparent", };
will probably not work. You probably have to use the material styles for that.
import { makeStyles } from "#material-ui/core/styles";
const useStyles = makeStyles({
root: {
border: "1px solid red",
padding: 10
},
});
and then you have to use the style hook in the component definition:
const classes = useStyles();
When overriding a material-ui definition this part is the important one to define which part is going to overriden (not allowed to include pictures yet, sorry):
Material-Ui CSS keys for Table Row
Then you can override the style <TableRow classes={{ root: classes.root }}> or in your case maybe more like <TableRow classes={{ root: classes.root }} {...row.getRowProps()}>
An additional problem you might face is that you have to override the style of the TableCells too, because the overlap the TableRow border.
I have here a code sandbox that is by no means perfect, but should help you on the right track: https://codesandbox.io/s/material-demo-535zq?file=/demo.js
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles(() => ({
rowStyle : {
padding: 10,
border: "1px solid red"
}
}));
const EnhancedTable = ()=>{
const classes = useStyles();
return(
<TableRow className={classes.rowStyle}/>
)
}

react-spring#next - a spring with async func as 'to' animating every render

A react-spring (version 9) spring whose 'to' value is an async funtion goes through its animation cycle every single rerender. If 'to' is a plain object, the animation triggers only initially, as expected.
Consider this component:
const Component = () => {
// let's trigger some rerenders
const [state, setState] = useState(false);
useEffect(() => {
setInterval(() => {
setState(x => !x);
}, 1000);
}, []);
// a spring with an async func provided to 'to'
const props = useSpring({
to: async (next, cancel) => {
await next({opacity: 1, color: '#ffaaee'})
await next({opacity: 0, color: 'rgb(14,26,19)'})
},
from: {opacity: 0, color: 'red'}
});
return <animated.div style={props}>I will fade in and out</animated.div>
};
The text will keep flashing forever.
I believe this is not the intended behaviour. Is this a bug, or I'm doing something wrong?
I think the intended behaviour is to show the current state of the to property of the useSpring. When it is constant then it will always show the same state at each render. But you can change the to property of the usespring. For example:
const ComponentColor = () => {
const [color, setColor] = React.useState("red");
const props = useSpring({
to: { color },
from: { color: "red" }
});
return (
<>
<animated.h2 style={props}>This color will change</animated.h2>
<button onClick={() => setColor("blue")}>blue</button>
<button onClick={() => setColor("green")}>green</button>
<button onClick={() => setColor("orange")}>orange</button>
</>
);
};
In this case the color of the text will change to the color you pressed. I think your example is in line with this one. At each render it will show the current state of the to property which is a sequence. So I think it is the intended behaviour.
If you want that useState animate only on first render. Then you can refactor the animation part to a new component and make sure, that it will only render for the first time. For example if you use React.memo it will rerender your function component only if one of its properties change. In this example there is no property so it will render only for the very first time.
const Component = () => {
// let's trigger some rerenders
const [state, setState] = React.useState(false);
React.useEffect(() => {
setInterval(() => {
setState(x => !x);
}, 2000);
}, []);
return <FirstTimeAnimate />;
};
const FirstTimeAnimate = React.memo(() => {
const props = useSpring({
to: async (next, cancel) => {
await next({ opacity: 0.25, color: "#black" });
await next({ opacity: 1, color: "blue" });
},
from: { opacity: 0, color: "red" }
});
return <animated.h2 style={props}>I will fade in and out</animated.h2>;
});
https://codesandbox.io/s/fervent-margulis-drt5l

Resources