I have a situation where I want to scroll to the current month in a calendar on load. However useEffect is ran only once at the initial render when listRef.current is undefined and that's it. I would've assumed useEffect to run again once listRef.current has a value however it does not. Could this be a bug? Here is the code to reproduce:
function Calendar() {
const listRef = useRef()
useEffect(() => {
listRef.current?.scrollToItem(10)
}, [listRef.current])
return (
<AutoSizer>
{({ width, height }) => (
<VariableSizeList
itemCount={100}
itemSize={200}
height={height}
width={width}
ref={listRef}
>
{({ index, style }) => (
<Month index={index} key={index} style={style} />
)}
</VariableSizeList>
)}
</AutoSizer>
)
}
You need to change the array parameter to your useEffect to [listRef] instead of [listRef.current]. Inside the function, you need to check for listRef.current to be defined/non-null, then execute listRef.current?.scrollToItem(10). So:
useEffect(() => {
if (listRef?.current) listRef.current.scrollToItem(10)
}, [listRef])
I use this exact structure currently on a different tool. The useEffect call will only be called when listRef changes - but it will be called every time listRef changes.
Thanks for your reply! Hmm that's odd, it doesn't seem to work for me, again useEffect is called only once when listRef.current is null.
````
let todayEventRef = useRef();
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
if (todayEventRef.current) {
todayEventRef.current.scrollIntoViewIfNeeded(true);
}
}, [todayEventRef, scrollPosition]);
````
Noting: scrollPosition is calculated in another useEffect to choose which item to scroll into view. The ref is attached to an item generated programmatically, and is inside a .map() inside another .map()
The useRef is declared before all the useEffects (about 3 of them), and the above useEffect is the last. The data that the scrollPosition is calculated on comes in as a prop, which triggers the useEffect that calculates a new scrollPosition, which triggers the above useEffect.
This won't work because of two things:
1) a dependency of the useEffect is a ref, which never changes, or a ref.current value, which changes, but a component won't re-render itself because of ref.current change (only state or props do it)
2) useEffect is in a bad scope
function CalendarList({ width, height }) {
const listRef = useRef()
useEffect(() => {
listRef.current.scrollToItem(10)
}, [])
return (
<VariableSizeList
itemCount={100}
itemSize={200}
height={height}
width={width}
ref={listRef}
>
{({ index, style }) => (
<Month index={index} key={index} style={style} />
)}
</VariableSizeList>
)
}
function Calendar() {
return (
<AutoSizer>
{({ width, height }) => (
<CalendarList width={width} height={height} />
)}
</AutoSizer>
)
}
This ensures useEffect is called only when the VariableSizeList is actually mounted. The reason why you need to do this is because of AutoSizer which "takes some time" to render its children.
@jancama2 turns out that was the problem, it worked once I did that, thanks for your help!
@pavsidhu np, btw would be better to use useLayoutEffect to avoid some ui flickering
Most helpful comment
This won't work because of two things:
1) a dependency of the
useEffectis aref, which never changes, or aref.currentvalue, which changes, but a component won't re-render itself because ofref.currentchange (only state or props do it)2)
useEffectis in a bad scopeThis ensures
useEffectis called only when theVariableSizeListis actually mounted. The reason why you need to do this is because ofAutoSizerwhich "takes some time" to render its children.