Children ref undefined react native - reactjs

I created a component wrapper around ViewPagerAndroid (simplified version)
class TabView extends Component {
constructor(props){
super(props)
this.state = { position: 0 }
}
changePage = (key) => {
this._pagerRef.setPage(key)
this.setState({position: key})
}
render(){
return(
<ViewPagerAndroid ref={(ref) => this._pagerRef = ref}>
{ this.props.scenes }
</ViewPagerAndroid>
)
}
}
I want to trigger changePage from outside the component (eg from: <TabView ref={(ref) => this._ref = ref} />, and run this._ref.changePage(key)).
However, each time I try to do so, this._pagerRef is undefined inside the changePage function of TabView.
What am I missing ?

There is a more idiomatic React solution to the problem you are trying to solve -- namely making TabView a controlled component and setting ViewPager page on componentDidUpdate:
class TabView extends Component {
componentDidUpdate = ({ page }) => {
// call setPage if page has changed
if (page !== this.props.page && this._pagerRef) {
this._pagerRef.setPage(page);
}
};
render() {
return (
<ViewPagerAndroid
initialPage={this.props.page}
ref={ref => this._pagerRef = ref}
onPageSelected={e => this.props.pageChanged(e.nativeEvent.position)}
>
{this.props.scenes}
</ViewPagerAndroid>
);
}
}
You can then move the current page tracking to the parent component's state and pass it down to TabView as a prop, along with a handler that updates it when the value changes:
render() {
return (
<TabView
page={this.state.page}
pageChanged={page => this.setState({page})}
/>
)
}

You're trying to access the ref from outside of the component which has no instance to it.
Therefore you need to pass it as a prop from the parent component itself. Also you need to move the changePage to the parent component to access it from outside.
Parent
changePage = (key) => { //... Call the function here
this._pagerRef.setPage(key)
this.setState({position: key})
}
accessRef (ref) {
this._pagerRef = ref . //... Bind the ref here
}
<TabView passRef={this.accessRef} /> //...Pass the ref here
Child
<ViewPagerAndroid ref={this.props.passRef}> . // ... Set the ref here
{ this.props.scenes }
</ViewPagerAndroid>

Related

Scroll to element in an array after rendering using React ref callback

I would like to use ref callback to scroll to an element in an array after rendering.
Depending on state, it will scroll to different element based on this.state.scrollToId.
But I think the callback in the below code was not triggered before componentDidUpdate
P.S. Item is a customised component.
I have tried componentDidMount too.
Researches done:
Using 'ref' as array in React
https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
How to assign refs to multiple components
class A extends React.Component{
//...
state = { scrollToId: null}
scrollToMyRef = (myRef) =>{
window.scrollTo(0, myRef.current.offsetTop)
}
constructor(props) {
super(props)
this.itemRefs = {};
this.setItemRef = (element, id) => {
this.itemRefs[id] = element;
}
}
componentDidUpdate(){
if (this.state.scrollToId){
scrollToMyRef(this.itemRefs[this.state.scrollToId])
}
render(){
return (
<div>
{
this.state.aList.map((item)=>{
return (
<Item ref={(element) => {this. setItemRef(element, item.id)}}
</Item>
)
})
}
</div>)
}
}
Expected: based on the state, the page would scrolled to the corresponding element
Actual: itemRef[id] was undefined, and throw exception on scrollToMyRef

How to get the DOM node from a Class Component ref with the React.createRef() API

I have these two components:
import { findDOMNode } from 'react-dom';
class Items extends Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.selectedItemRef = React.createRef();
}
componentDidMount() {
if (this.props.selectedItem) {
this.scrollToItem();
}
}
componentWillReceiveProps(nextProps) {
if (this.props.selectedItem !== nextProps.selectedItem) {
this.scrollToItem();
}
}
scrollToItem() {
const itemsRef = this.ref.current;
const itemRef = findDOMNode(this.selectedItemRef.current);
// Do scroll stuff here
}
render() {
return (
<div ref={this.ref}>
{this.props.items.map((item, index) => {
const itemProps = {
onClick: () => this.props.setSelectedItem(item.id)
};
if (item.id === this.props.selectedItem) {
itemProps.ref = this.selectedItemRef;
}
return <Item {...itemProps} />;
})}
</div>
);
}
}
Items.propTypes = {
items: PropTypes.array,
selectedItem: PropTypes.number,
setSelectedItem: PropTypes.func
};
and
class Item extends Component {
render() {
return (
<div onClick={() => this.props.onClick()}>item</div>
);
}
}
Item.propTypes = {
onClick: PropTypes.func
};
What is the proper way to get the DOM node of this.selectedItemRef in Items::scrollToItem()?
The React docs discourage the use of findDOMNode(), but is there any other way? Should I create the ref in Item instead? If so, how do I access the ref in Items::componentDidMount()?
Thanks
I think what you want is current e.g. this.selectedItemRef.current
It's documented on an example on this page:
https://reactjs.org/docs/refs-and-the-dom.html
And just to be safe I also tried it out on a js fiddle and it works as expected! https://jsfiddle.net/n5u2wwjg/195724/
If you want to get the DOM node for a React Component I think the preferred way of dealing with this is to get the child component to do the heavy lifting. So if you want to call focus on an input inside a component, for example, you’d get the component to set up the ref and call the method on the component, eg
this.myComponentRef.focusInput()
and then the componentRef would have a method called focusInput that then calls focus on the input.
If you don't want to do this then you can hack around using findDOMNode and I suppose that's why it's discouraged!
(Edited because I realized after answering you already knew about current and wanted to know about react components. Super sorry about that!)

Sending onMouseDown from parent to child

Is there a way to send the clickevent from the parent to the child?
This is my parent component:
<Component {...props}>
<Child />
{props.children}
</Component>
This is the child component:
<Component onMouseDown={e => this.handleClick(e, props)}></Component>
Whenever the parent component is clicked I want to trigger the handleclick component of my child.
Thanks in advance!
You can use a reference to your child component:
// parent.js
constructor(props) {
super(props);
this.child = React.createRef();
}
handleMouseDown = e => {
this.child.current.handleClick(e, this.props);
}
render() {
return (
<Component onMouseDown={this.handleMouseDown} {...props}>
<Child ref={this.child}/>
{props.children}
</Component>
)
}
You can do this using rxjs with Observable and Subscriptions. Here is a working example and I'll explain what's going on https://codesandbox.io/s/7wjwnznk3j
Relevant reading:
fromEvent: https://rxjs-dev.firebaseapp.com/api/index/function/fromEvent
subscription: https://rxjs-dev.firebaseapp.com/api/index/class/Subscription
I used Typescript since I prefer it, but is absolutely not a requirement. You parent class will look like this:
interface State {
obs$?: Observable;
}
class App extends React.Component<null, State> {
public readonly state: State = {};
public ref: React.Ref<React.ReactHTMLElement>;
componentDidMount() {
this.setState({
obs$: fromEvent(this.ref, 'click')
});
}
#Bind()
setParentRef(el: HTMLElement) {
this.ref = el;
}
render() {
return (
<div style={parentStyles} ref={this.setParentRef}>
<Child parentClick={this.state.obs$} />
</div>
);
}
}
We have our ref this.ref and set it through the function, we need this since it is the target of a fromEvent and click is the event. This automatically creates an observable that will emit to any subscribers when it is clicked. You will want to pass this as a prop to your child component. Then in that component you can subscribe to it and do whatever you want when there is a click in the parent.
interface Props {
parentClick?: Observable;
}
interface State {
onClick$?: Subscription;
numClicks: number;
}
class Child extends React.Component<Props, State> {
public readonly state: State = { numClicks: 0 };
componentDidMount() {
if (this.props.parentclick) {
this.handle();
}
}
componentDidUpdate(prevProps: Props) {
if (
this.props.parentClick !== undefined &&
this.state.onClick$ === undefined
) {
this.handleSubscribe();
}
}
componentWillUnmount() {
if (this.state.onClick$) {
this.state.onClick$.unsubscribe();
}
}
handleSubscribe() {
this.setState({
onClick$: this.props.parentClick.subscribe(this.onParentClick)
});
}
#Bind()
onParentClick() {
this.setState((prevState: State) => ({
numClicks: prevState.numClicks + 1
}));
}
render() {
return (
<div style={childStyles}>
Parent clicked {this.state.numClicks} time(s)
</div>
);
}
}
So in this instance, when the parent is clicked the subscription invokes the onParentClick method. Then in that method we implement a simple counter and display it in the HTML.
One thing important thing is to ALWAYS make sure you unsubscribe from subscriptions. If you don't this will create a memory leak and will be really tricky to track down, since it is easy to overlook.

React State not available from Parent?

I have a form with a child component that renders as a table.
ParentComponent extends React {
state = {
anArray: []
}
<ParentComponent>
<table>
map ( thing => <ChildComponent {someFunction= this.updateFunction;} />
When ChildComponent maps the data to individual TD's. In my onChange in the ChildComponent, I'm invoking
onChange = this.props.someFunction();
and the code is hitting my breakpoint which is great. It calls someFunction in the ParentComponent. In someFunction, I'm trying to access the parent's state so I can match the onChanged TD with the proper index in the array but I'm getting undefined.
someFunction(id) {
const index = this.state.anArray.findIndex( x => x.id === id) ;
if (index === -1)
// handle error
console.log("DIDN'T FIND ID: " + id);
});
}
Why wouldn't I have access to state on the function invocation from the ChildComponent? I expected to be able to access it.
It's not clear from the posted code, but I guess you haven't bind the someFunction and you have the context of the child, instead of parent's.
ParentComponent extends React {
constructor(props){
super(props)
this.someFunction = this.someFunction.bind(this)
}
someFunction(){
...
}
render(){
...
}
}
If you have the necessary babel plugins you can even do
ParentComponent extends React {
someFunction = () => {
...
}
render(){
...
}
}

React assign key to already rendered component

Is it possible?
I have a component where children are rendered by an arbitrary mapping function coming in as props. A simplified example:
class SomeComponent extends Component {
render() {
const { renderChild, businessObjects } = this.props
return <div>
{businessObjects.map(renderChild)}
</div>
}
}
I obviously get a warning saying children are rendered without the key attribute.
I tried assigning the key after the vdom element is rendered:
...
{
businessObjects.map(e => {
const vdom = renderChild(e)
vdom.key = e.id
return vdom
})
}
...
But the object returned from the JSX transform is frozen, so I can't do this. Also there is no API to temporarily unfreeze then re-freeze objects in js. Cloning is out of question for performance reasons (thousands of components are rendered like this)
What can I do?
Again, for performance reason I can't wrap the rendered children into another component, so a solution like this wouldn't work:
const Child = ({renderChild, bo}) => (<div>{renderChild(bo)}</div>)
// in SomeComponent
...
{
businessObjects.map(e => (<Child
key={e.id}
bo={e}
renderChild={renderChild}
/>)
)
}
...
Update
The reason for this structure is that SomeComponent is a dumb component, and has no access to application state (redux). But the rendered children do need to have access to dispatch (I do it in a form of connected action creators).
So you can imagine the whole thing like this:
const createChildRenderer = ({actionFoo, actionBar}) => (obj) => {
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in a connected component
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const renderChild = createChildRenderer({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
renderChild={renderChild}
businessObjects={this.props.businessObjects}>
}
}
The way I ended up solving this by taking an actual react component as an argument:
So that in the dumb component that previously took a renderer function, now I take a component:
class SomeComponent extends Component {
render() {
const { ChildComponent, businessObjects } = this.props
return <div>
{businessObjects.map((o) => (<ChildComponent
businessObject={o}
key={o.id}
/>)}
</div>
}
}
And where I previously created the renderer function, now I create the component:
const createChildComponent = ({actionFoo, actionBar}) =>
({ businessObject: obj }) => { // this is now a component created dynamically
switch(obj.type) {
case FOO:
return <div onClick={() => actionFoo()}>{obj.text}</div>
case BAR:
return <div onClick={() => actionBar()}>{obj.text}</div>
default:
return null
}
}
And in the connected component:
#connect(
({ businessObjects }) => { businessObjects },
{ actionFoo, actionBar}
)
class SmartComponent extends Component {
render() {
const ChildComponent = createChildComponent({
actionFoo: this.props.actionFoo, // action creators
actionBar: this.props.actionBar
})
return (<SomeComponent
ChildComponent={ChildComponent}
businessObjects={this.props.businessObjects}>
}
}
You can use cloneElement on the child received from renderChild:
React.cloneElement(
child,
{...child.props, key: yourKeyValue}
)

Resources