I'm using the package react-transition-group, I have tried using the nodeRef props on the CSSTransition component, and added a wrapper on my component but I still get the warning regarding findDOMNode.
Here's the code:
<CSSTransition
key={entry.id}
timeout={500}
classNames="timesheet-entry"
>
<TimesheetEntry
taskOptions={taskOptions || []}
deleteHandler={(event) => {
deleteHandler(event, entry.id.toString());
}}
data={entry}
dateChangeHandler={(date: Date) =>
dateChangeHandler(date, entry.id)
}
hoursChangeHandler={(event) => hoursChangeHandler(event, entry.id)}
taskCodeChangeHandler={(event, value) =>
taskCodeChangeHandler(event, value, entry.id)
}
/>
</CSSTransition>
Code for the TimesheetEntry component:
function TimesheetEntry(props: TimesheetEntryProps) {
return (
<div>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<KeyboardDatePicker
label="Date"
style={{ marginRight: '15px', height: '20px', marginTop: '-2px' }}
disableToolbar
variant="inline"
format="MM/dd/yyyy"
margin="normal"
value={props.data.date}
onChange={props.dateChangeHandler}
size="small"
KeyboardButtonProps={{
'aria-label': 'change date',
}}
/>
</MuiPickersUtilsProvider>
<Autocomplete
size="small"
style={{
width: 300,
display: 'inline-block',
marginRight: '15px',
}}
options={props.taskOptions}
getOptionLabel={(option) => option.name}
getOptionSelected={(option, value) => {
return option.id === value.id && option.name === value.name;
}}
onChange={props.taskCodeChangeHandler}
renderInput={(params) => (
<TextField {...params} label="Task" variant="outlined" />
)}
/>
<TextField
size="small"
style={{ marginRight: '15px', height: '20px' }}
label="Hours"
type="number"
inputProps={{ min: 0.5, step: 0.5 }}
onChange={props.hoursChangeHandler}
InputLabelProps={{
shrink: true,
}}
/>
<Button
style={{ marginRight: '15px' }}
variant="contained"
color="secondary"
size="small"
startIcon={<DeleteIcon />}
onClick={props.deleteHandler}
>
Delete
</Button>
</div>
);
}
export default TimesheetEntry;
I've also made a somewhat similar code setup in codesandbox here
I've tried adding nodeRef and a ref reference through a div wrapper on my TimesheetEntry component but that seems to make the animation behave improperly(adding new entries works properly but when I try to delete the entry, the animation doesn't seem to work anymore). I'm also looking for a way without creating a div wrapper on the TimesheetEntry component.
There are actually two distinct findDOMNode warnings in your CodeSandbox demo:
When you first add or remove an entry, which originates from the direct usage of react-transition-group for TimesheetEntry.
When you save your timesheet, which originates from the indirect usage of react-transition-group through Material UI's Snackbar component.
Unfortunately, you have no control over the latter, so let's fix the former; you're managing a list of transitioning TimesheetEntry components, but to correctly implement nodeRef, each element needs a distinct ref object, and because you cannot call React hooks within a loop (see rules of hooks), you have to create a separate component:
const EntryContainer = ({ children, ...props }) => {
const nodeRef = React.useRef(null);
return (
<CSSTransition
nodeRef={nodeRef}
timeout={500}
classNames="timesheet-entry"
{...props}
>
<div ref={nodeRef}>
{children}
</div>
</CSSTransition>
);
};
which you will wrap around TimesheetEntry:
const controls: JSX.Element[] = entries.map((entry: entry, index: number) => {
return (
<EntryContainer key={entry.id}>
<TimesheetEntry
deleteHandler={event => {
deleteHandler(event, entry.id.toString());
}}
data={entry}
dateChangeHandler={(date: Date) => dateChangeHandler(date, entry.id)}
hoursChangeHandler={event => hoursChangeHandler(event, entry.id)}
taskCodeChangeHandler={(event, value) =>
taskCodeChangeHandler(event, value, entry.id)
}
/>
</EntryContainer>
);
});
You said that you tried something like this already, but my suspicions are that you forgot to forward EntryContainer's props to CSSTransition, which is the crucial step because those are being passed down by TransitionGroup.
Caleb Taylor's answer solves the problem by forwarding the ref, and you can avoid using // @ts-ignore if you test the child element's validity before cloning it. For instance,
import { Children, cloneElement, isValidElement, useRef } from 'react';
import { CSSTransition as _CSSTransition } from 'react-transition-group';
import { CSSTransitionProps } from 'react-transition-group/CSSTransition';
export const CSSTransition = ({ children, ...props }: CSSTransitionProps) => {
const nodeRef = useRef(null);
return (
<_CSSTransition {...props} nodeRef={nodeRef}>
<>
{Children.map(children, child =>
isValidElement(child)
? cloneElement(child, { ref: nodeRef })
: child
)}
</>
</_CSSTransition>
);
};
The idea came from this answer to an unrelated question.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With