Accessing a functional component's method from parent component via ref - reactjs

Is there a way I can access the method triggerFoo in Child from the Parent? This is the code I have so far:
import React, {createRef} from 'react'
import ReactDOM from 'react-dom'
const Parent= () => {
let myRef = createRef()
return (
<>
<Test ref={(ref) => myRef = ref} />
<button onClick={() => myRef.triggerFoo()}>Click Me</button>
</>
)
}
const Child = React.forwardRef((props, ref) => {
const triggerFoo = () => console.log('Foo')
return <div>Testing</div>
})
Clicking on the button does not trigger anything currently.
One condition in my code is that it must use callback ref in the parent component, since this problem is part of a bigger problem I'm trying to solve involving a third party package.

Changed code.
const Parent= () => {
let myRef = createRef()
const triggerFoo = () => console.log('Foo')
return (
<>
<Child ref={myRef} triggerFoo={triggerFoo}>Click</Child>
</>
)
}
const Child = forwardRef((props, ref) => {
return <button onClick={props.triggerFoo} ref={ref}>{props.children}</button>
})

Related

Is it ok to use react state in render prop?

I have two components App and MyComponent, where MyComponent is used in App.
import { useState } from "react";
import { MyComponent } from "./myComponent";
export const App = () => {
const [state, setState] = useState(0);
return (
<>
<MyComponent
render={() => (
<button onClick={() => setState((prev) => prev + 50)}>{state}</button>
)}
/>
</>
);
}
export const MyComponent = (props) => {
const Content = props.render;
return (
<div>
<Content/>
</div>
);
};
Is it ok to use state in the return value of the render prop? Is it considered anti-pattern?
Is it ok to use react state in render prop?
Yes, but... why? children prop was created to achieve exactly what you want here.
<MyComponent>
<button onClick={() => setState((prev) => prev + 50)}>{state}.</button>
</MyComponent>
export const MyComponent = ({ children }) => (
<div>
{children}
</div>
);

How to call a Parent method in Child Component using Hook (ForwardRef concept)

I tried the following code but it fails
So, this is my Parent Component:
import React from 'react'
import ChildComponent from './ChildComponent';
const ParentComponent = (props) => {
//step 1
// const inputRef = React.createRef();
const buttonRef = React.useRef();
const focusHandler = () => {
alert("hi");
}
return (
<div>
{/* In parent, we generally pass reference to child which we dint do here, lets see if props children help here */}
{props.children}
<ChildComponent ref="buttonRef" />
</div>
)
}
export default ParentComponent;
This is my child component:
import React from 'react'
const ChildComponent = React.forwardRef((props, ref) => {
return (
<div>
<button onClick={ref.focusHandler}>Focus Input</button>
</div>
)
})
export default ChildComponent;
On click of the button above in child component, I wish to call Parent method.
How can that be achieved?
EDITED
The reason you're getting the error is because refs in function components need to be passed using ref={buttonRef}, not ref="buttonRef". Class components have a thing they can do with string refs, but it's not recommended even there.
As for calling a function from a parent component, you don't need refs to do this. So if that was the only reason you were using a ref, you can remove the ref. Instead, pass the function as a prop:
const ParentComponent = (props) => {
const focusHandler = () => {
alert("hi");
}
return (
<div>
<ChildComponent focusHandler={focusHandler} />
</div>
)
}
const ChildComponent = (props) => {
return (
<div>
<button onClick={props.focusHandler}>Focus Input</button>
</div>
)
}
Just replace ref by focusHandler like below in parent component
<ChildComponent focusHandler={focusHandler} />
Then in ChildComponent, remove ref as well.
If you wonder how to use refs in this case (even though this is not the recommended way to pass callbacks), you need to assign focusHandler key and use the ref with ref.current, refer to Components and Props docs.
const ParentComponent = () => {
const buttonRef = React.useRef({ focusHandler: () => alert("hi") });
return (
<div>
<ChildComponent ref={buttonRef} />
</div>
);
};
const ChildComponent = React.forwardRef((props, ref) => {
return (
<div>
<button onClick={ref.current.focusHandler}>Focus Input</button>
</div>
);
});

React component does no re-render when it is swapped with the same one

Please consider the following code:
import React from "react";
import "./styles.css";
const Component = ({ title }) => {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
console.log("Mounted");
}, []);
return (
<div>
<h2>{title}</h2>
<p>{count}</p>
<button onClick={() => setCount(c => c + 1)}>count up</button>
</div>
);
};
export default function App() {
const [index, setIndex] = React.useState(0);
const changeComponent = () => {
setIndex(c => (c === 1 ? 0 : 1));
};
const components = [
{
render: () => <Component title="one" />
},
{
render: () => <Component title="two" />
}
];
return (
<>
<button onClick={changeComponent}>toggle component</button>
{components[index].render()}
</>
);
}
https://codesandbox.io/s/mystifying-hermann-si7cn
When you click toggle component, title changes, but component is not unmounted, you can see it because count is not reset.
How to make it so that new component is mounted on toggle component click?
React needs a way to differentiate one component instance from the other. This will fix it
const components = [
{
render: () => <Component key={1} title="one" />
},
{
render: () => <Component key={2} title="two" />
}
];
Its the same reason react requires dynamically rendered lists to have a key prop. It informs react of which component to update.

Change react hook state from parent component

I have a hook component like this:
import React, { useState} from "react";
const MyComponent = props => {
const [value, setValue] = useState(0);
const cleanValue = () => {
setValue(0);
};
return (<span><button onClick={()=>setValue(1)}/>{value}<span>)
}
I want to reset value from the parent component. How I can call clean value from parent component? the parent component is a stateful component.
If the parent has to control the child state then probably the state must reside in the parent component itself. However you can still update child state from parent using ref and exposing a reset method in child. You can make use of useImperativeHandle hook to allow the child to only expose specific properties to the parent
const { useState, forwardRef, useRef, useImperativeHandle} = React;
const Parent = () => {
const ref = useRef(null);
return (
<div>
<MyComponent ref={ref} />
<button onClick={() => {ref.current.cleanValue()}} type="button">Reset</button>
</div>
)
}
const MyComponent = forwardRef((props, ref) => {
const [value, setValue] = useState(0);
const cleanValue = () => {
setValue(0);
};
useImperativeHandle(ref, () => {
return {
cleanValue: cleanValue
}
});
return (<span><button type="button" onClick={()=>setValue(1)}>Increment</button>{value}</span>)
});
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>
From the React documentation about Fully uncontrolled component with a key:
In order to reset the value ..., we can use the special React attribute called key. When a key changes, React will create a new component instance rather than update the current one. Keys are usually used for dynamic lists but are also useful here.
In this case, we can use a simple counter to indicate the need for a new instance of MyComponent after pressing the Reset button:
const { useState } = React;
const Parent = () => {
const [instanceKey, setInstanceKey] = useState(0)
const handleReset = () => setInstanceKey(i => i + 1)
return (
<div>
<MyComponent key={instanceKey} />
<button onClick={handleReset} type="button">Reset</button>
</div>
)
}
const MyComponent = () => {
const [value, setValue] = useState(0)
return (
<span>
<button type="button" onClick={()=>setValue(v => v + 1)}>{value}</button>
</span>
)
};
ReactDOM.render(<Parent />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app"/>
You can't / shouldn't. Using hooks instead of stateful class components doesn't change the fact that if you want the parent to own the state, you need to declare the state in the parent.
It should look something like this, depending on when you want to reset the value (here I used another button):
const MyButton = (props) = (
// Whatever your button does, e.g. styling
<span>
<button {...props} />
<span>
)
const Parent = props => {
const [value, setValue] = useState(0);
const cleanValue = () => setValue(0);
return (
<div>
<MyButton onClick={() => setValue(1)}>
{value}
</MyButton>
<button onClick={cleanValue}>
Reset
</button>
</div>
)
}

How to focus something on next render with React Hooks

I'm playing with hooks, and I'm trying to do the following:
import React, { useState, useRef } from 'react';
const EditableField = () => {
const [isEditing, setEditing] = useState(false);
const inputRef = useRef();
const toggleEditing = () => {
setEditing(!isEditing);
if (isEditing) {
inputRef.current.focus();
}
};
return (
<>
{isExpanded && <input ref={inputRef} />}
<button onClick={toggleEditing}>Edit</button>
</>
);
};
This is going to fail, because current is null, since the component haven't re-rendered yet, and the input field is not yet rendered (and therefore can't be focused yet).
What is the right way to do this? I can use the usePrevious hook proposed in the React Hooks FAQ, but it seems like a painful workaround.
Is there a different way?
You can use the useEffect hook to run a function after every render when isEditing changed. In this function you can check if isEditing is true and focus the input.
Example
const { useState, useRef, useEffect } = React;
const EditableField = () => {
const [isEditing, setEditing] = useState(false);
const toggleEditing = () => {
setEditing(!isEditing);
};
const inputRef = useRef(null);
useEffect(() => {
if (isEditing) {
inputRef.current.focus();
}
}, [isEditing]);
return (
<div>
{isEditing && <input ref={inputRef} />}
<button onClick={toggleEditing}>Edit</button>
</div>
);
};
ReactDOM.render(<EditableField />, document.getElementById("root"));
<script src="https://unpkg.com/react#16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I know the accepted answer covers the requested element in the above question.
But as an additional note, if you are using functional components, make use of React.forwardRef to pass down the reference to child components. It might be
definitely useful for someone who refers to this question later on.
In a more cleaner way, you can write your child component which accept the ref as given below:
const InputField = React.forwardRef((props, ref) => {
return (
<div className={props.wrapperClassName}>
<input
type={props.type}
placeholder={props.placeholder}
className={props.className}
name={props.name}
id={props.id}
ref={ref}/>
</div>
)
})
Or Simply use this component
import { FC, useEffect, useRef } from 'react'
export const FocusedInput: FC<JSX.IntrinsicElements['input']> = (props) => {
const inputRef = useRef<null | HTMLElement>(null)
useEffect(() => {
inputRef.current!.focus()
}, [])
return <input {...props} type="text" ref={inputRef as any} />
}

Resources