Pass down a canvas ref through Context in React - reactjs

I am working on implementing some canvas rendering by using Components. I have the following Component which attempts to layout the canvas to the page, and have subcomponents in the Stage component draw to its context.
I need a reference to the canvas elements that my Renderer component is rendering. So I ask for the ref in the JSX, and set it on my component when I get it. I'm also using React context, so that all the children components can access this canvas reference. The problem I'm having is that it seems getChildContext is called before the canvas ref is assigned and so all the children component access an undefined canvas and no drawing is done.
export default class Renderer extends React.Component {
static childContextTypes = {
canvas: PropTypes.object
};
getChildContext() {
console.log("get child context")
return { canvas: this.canvas };
}
render() {
return (
<div className="CanvasHolder" key={0}>
<canvas className="MainCanvas" ref= {
canvas => {
this.canvas = canvas
console.log("got ref");
}
} />
<Stage />
</div>
)
}
}

You can access the ref only after in componentDidMount (after it is rendered). But the parent component is considered rendered only when all of its children are rendered. So children can't access parent's ref before it is rendered. Instead of returning canvas ref directly, try to pass custom function getCanvas() instead:
getCanvas = () => {
return this.canvas
}
And then in your children component call this function to retrieve canvas ref.

Related

ReactTS component class: how to save ref on the element

I use TypeScript and class component way and I need to save reference on my element. I am going to get the state of target element when 'resize' event will happen.
Accordingly How to use refs in React with Typescript I tried this:
export default class Carousel extends Component<CarouselProps, CarouselState> {
private ref: React.LegacyRef<HTMLDivElement>;
constructor(...) {
this.ref = React.createRef();
window.addEventListener('resize', (e) => this.resizeCarouselElements());
}
componentDidUpdate(...){...}
componentDidMount(...){...}
resizeCarouselElements = () => {
<...get current state of target element here ...>
}
render() {
return (
<div className='name' ref={this.ref}>
})
}
But after resizing the window I have got null:
How can I save reference on my div className='name' and use it after user's window resizing? Which way is the best practice? Store the ref in state, const or somewhere else?

What is the difference between forwardingRef vs callback refs in React?

As per the React.js official documentation, the below code is an example of callback refs.
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class Parent extends React.Component {
componentDidMount(props) {
//Here, this.inputElement in Parent will be set to the DOM node corresponding to the element in the CustomTextInput
console.log(this.inputElement);
}
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el}
/>
);
}
}
Here, this.inputElement in Parent will be set to the DOM node corresponding to the element in the CustomTextInput.
In case of forwarding ref, as per the official document,
const FancyButton = React.forwardRef((props, ref) => {
return (
<button ref={ref} className="FancyButton" data-name="My button">
{props.children}
</button>
);
});
//Parent Component
class FancyButtonWrapper extends React.Component {
constructor(props) {
super(props);
this.buttonRef = React.createRef();
}
componentDidMount(props) {
//Here this.ref will point to button element. Because of this reason, ref.current will give the value of button.
console.log(this.buttonRef.current.getAttribute("data-name"));
}
render() {
return (
//Here we are passing the ref to the child component.
<FancyButton ref={this.buttonRef} data-attr="Hello">
Click me!{" "}
</FancyButton>
);
}
}
Here, in this case, this.ref will point to the button element. Because of this reason, ref.current will give the value of the button.
Is there any difference between forwardRef and callbackRefs? We can access the child node's reference from parent in both of these cases.
I am not an expert but here are something to think about:
- callback refs are used when we need to dynamically set refs.
- Forward refs are commonly used when access to child refs are needed.
Well for the difference between the use of forwardingRef vs callback ref is in the HOC.
if you pass ref prop to HOC then inside HOC you cannot further pass it down to the enclosing component(which HOC wraps) since the props attributes does not store the ref inside it. ref is not a key ,see here: https://reactjs.org/docs/forwarding-refs.html 
so apart form this use case they work in same way.
Hope that helps !!
The difference is in the case of the ref callback, you can run side-effects when the ref changes. If you use useRef, you can access ref at any time but you will not know when it is set, or run a useEffect with the ref as a dependency
Callback refs have more control - they allow you to for example set a state when the ref is set, which will rerender the component when the component mounts, and you can use the ref node to do whatever you need.
In short - generally use useRef as it is the simplest. But ref callbacks can give you more control when needed

pass ref to a class component with React.cloneElement and render prop

I'm writing a component that handle some internal state according to a ref of it's child (a mouse event related to that child's ref for example).
This component is using a render-prop to pass on the relevant piece of state to it's child, and render the child with the ref attached via React.cloneElement util.
The problem is that when the child is a class component, for some reason the ref is not available, and i can't find a way to render it as it's a react element object with a type of function (after i clone it of course).
But if the child is just a DOM node like a div for example, it is working as expected.
My work-around is to check the type of the child, and if it is a type of function I'll wrap the cloned element with my own div, if it's just a dom node then render as is.
However, i would like to not wrap the child with an extra div as i don't want to add unnecessary DOM nodes.
Here is a basic code example, most code removed for brevity:
The Parent component:
class Parent extends Component {
attachRef = node => {
this.ref = node;
}
render() {
const { render } = this.props;
const { someValue } = this.state;
const Child = render(someValue);
const WithRef = React.cloneElement(Child, {
ref: this.attachRef
});
if (typeof WithRef.type === 'string') { // node element
return WithRef;
}
else if (typeof WithRef.type === 'function') {
// this is a react element object.. not sure how to render it
// return ?
} else {
// need to find a way to render without a wrapping div
return (
<div ref={this.attachRef}>{Child}</div>
);
}
}
}
The usage:
class App extends Component {
render() {
return (
<div>
<Parent render={someValue => <div> {someValue}</div>} />
<Parent render={someValue => <Menu someValue={someValue} />} />
</div>
);
}
}
When i render regular DOM nodes like the first example it works fine, when i try to render the Menu (which is a class component) it doesn't work as mentioned above.
I had almost an identical issue.
i chose to use findDOMNode from react-dom, you can see the full solution in react-external-click.
Although the warning notes:
findDOMNode is an escape hatch used to access the underlying DOM node.
In most cases, use of this escape hatch is discouraged because it
pierces the component abstraction.
findDOMNode only works on mounted components (that is, components that
have been placed in the DOM). If you try to call this on a component
that has not been mounted yet (like calling findDOMNode() in render()
on a component that has yet to be created) an exception will be
thrown.
findDOMNode cannot be used on functional components.
I think this is the better solution for this particular challenge.
It let's you be "transparent" to the consumer, while being able to target the component in the DOM.
Ok here it is, grabbing the ref:
componentDidMount() {
this.ref = findDOMNode(this);
// some logic ...
}
this is how i use a render function with no wrapper of my own:
render() {
const { children, render } = this.props;
const { clickedOutside } = this.state;
const renderingFunc = render || children;
if (typeof renderingFunc === 'function') {
return renderingFunc(clickedOutside);
} else {
return null
}
}
}

Get component's height every time it renders

So hey guys, basically I'm using react and I want to get the parent div's height, and make it's child to have the same height, by props. The parent div renders every time the window is resized. I tried using componentDidMount and setState to get the height of the parent, but componentDidMount is called only the first time my parent div renders.
And I can't use ReactDOM.findDOMNode(this).clientHeight inside render()function.
To simplify, these are the steps:
(Everytime) Window is resized
Div1 gets rendered
Gets Div1 height and set it state
Pass it by props to Div2.
Any ideas?
Here's a piece of code:
import React, { Component } from 'react';
import Div2 from './Div2';
class Div1 extends Component {
constructor(props){
super(props);
this.state = {
height: 0
};
}
componentDidMount() {
var height = (ReactDOM.findDOMNode(this).clientHeight);
this.setState({height: height})
}
render() {
return(
<div className='Div1'>
<Div2 height={this.state.height}/>
</div>
);
}
}
export default Div1;
There are 3 places you have to update your parent's state with new height at:
componentDidMount which will be called after the first render (first time parent's div will actually appear).
componentDidUpdate which is called after render-ing caused by props and state updates. You have to do only if you are actually using any props and their update can result in div's height change.
window resize.
You have to use refs to get parent div's DOM element inside render method. After that you cat use it in componentDidMount and componentDidUpdate (please, check React Component Lifecycle docs).
Combining everything together results in following code, where Foo passes it's root div height to Bar:
class Bar extends React.Component {
render() {
return (
<div className='bar' style={{height: `${this.props.height / 2 }px`}} />
);
};
};
class Foo extends React.Component {
constructor() {
super();
this.state = { height: 0 };
this.updateHeight = this.updateHeight.bind(this);
}
componentDidMount() {
this.updateHeight();
window.addEventListener("resize", this.updateHeight);
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateHeight);
}
componentDidUpdate() {
this.updateHeight();
}
updateHeight() {
if (this.state.height != this.div.clientHeight)
this.setState({ height: this.div.clientHeight })
}
render() {
return (
<div ref={ div => { this.div = div; } } className='foo'>
<Bar height={this.state.height} />
</div>
);
}
}
ReactDOM.render(<Foo/>, document.getElementById('app'));
Working example could be found here.

React — onScroll listener for parent element?

How can I add an onScroll listener in a component to catch a scroll of a parent element?
class ChildDiv extends React.Component {
constructor() {
super();
}
handleScrollOfParent() {
// do stuff when parent <main> scrolls;
}
render() {
return (
<div id="child" onScroll={this.handleScrollOfParent.bind(this)}>
// content with overflow: hidden and scroll handled by parent MAIN.
</div>
)
}
}
export default ChildDiv;
Rendered in a parent main, like this <main><ChildDiv /></main> and I want to catch the scroll of the main.
You could either:
1) try to grab the parent node from the child component:
componentDidMount() {
ReactDOM.findDOMNode(this).parentNode.addEventListener(...
}
2) pass the parent node as props and attach listener after rendered:
In parent:
render{
return(
<div className="parent" ref={(elem)=>{this.node=elem}}>
<Child parentNode={this.node}/>
</div>
)
}
and in child (parentNode is still undefined in constructor, check in next props):
componentWillReceiveProps(newProps){
newProps.parentNode.addEventListener(...
}
According to React's nature, which is passing props from Parent down to Children, so when you want the Child element to update its parent, you need to create a function on Parent, and pass to the Child as props
Please also refer to my answer to a similar issue (how to trigger the parent component to do something from the child/grandchild/or great-grandchild):
Re-initializing class on redirect
In your case, we may do something like this:
1/ Create a function on Parent that trigger the Parent itself to do something:
import React from 'react';
class Parent extends React.Component {
constructor(props) {
super(props);
// ...
}
doSomeThingOnParent = () => {
// do whatever you want with this Parent component, setState, forceUpdate etc.
}
render(){
// ...
<Child doSomeThingOnParent={this.doSomeThingOnParent} />
// ...
}
}
2/ On Child Component, trigger the above function using onScroll:
class Child extends React.Component {
...
render() {
return (
<div id="child" onScroll={this.props.doSomeThingOnParent}>
// your content
</div>
)
}
}
However, you cannot render the Parent by your mentioned way, which is:
You should use like the Parent's render method above, and also set the CSS to allow overflow for your Child component
You could define a state variable in the parent component that would store the current scrollTop value (assuming vertical scrolling) and update in every time the scroll event happens and then pass this variable to the ChildDiv which could detect that the variable has changed in componentWillReceiveProps:
if(this.props.yourVariable != nextProps.yourVariable){
// scroll event was fired => scroll position changed
}

Resources