One of the big sticking points in my "react apps that use canvas" experiments keeps being "where does the canvas live". Most often I put it in the .tsx and use a ref to access it in functions and useEffects. It works but never feels great. If I have canvases that are never going to be rendered I can just create them where needed - caching behind a ref to make sure I don't end up creating each call (possibly it wouldn't matter performance-wise if I did).
Maybe I should sort of allocate and create all my canvases in the functional code and then also render "render" canvases in the UI that I draw the "real" ones too. But sometimes (like my current project) the number of canvases is dynamic - dependent on something else. In that case I need to update that dynamic number in both DOM and drawing code - having it in react state so it reliably triggers a DOM rerender helps, although sometimes I need to run drawing code without waiting for the state update to clear, leading to more helper refs...