How to call a child method on parent events with react? - reactjs

I have a child component that need to listen to one of it's parent event. More precisely, I have a function in the child component that takes as parameter an event from the parent. I would like to call this function everytime the event occurs.
As an example, here is a code snippet:
class Parent extends React.Component {
handleKeyDown = (event) => {
// Call the child function doSomething()
}
render() {
return (
<input
type="text"
onKeyDown={this.handleKeyDown}
>
<Child />
)
}
}
class Child extends React.Component {
doSomething = (event) => {
// Get the event from parent
}
render() {
return (
...
)
}
}
I have considered two ways to do it:
Using ref to call the child function from the parent onKeyDown (supposing that I can access it)
Using a state to store the event and pass it as a props to the child, then listen to props changes with getDerivedStateFromProps.
However, none of these solutions seems very appealing. I have also thought about using a redux function but I need data from the child component as well as the event from the parent component... I was wondering if there is a clean way do to that?

Update:
I updated my components to use hooks and ended up using useRef(), useImperativeHandle() and forwardRef() to handle this case:
const Parent = () => {
const childRef = useRef();
const handleKeyDown = (event) => {
// Call the child function doSomething()
childRef.current.doSomething(event);
};
return (
<input
type="text"
onKeyDown={handleKeyDown}
>
<Child ref={childRef} />
);
};
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
doSomething: (event) => {
// Get the event from parent
}
}));
return (
[...]
);
});
I decided to use the solution provided by Francis Malloch on this post1:
class Parent extends React.Component {
childCallables = null;
setChildCallables = (callables) => {
this.childCallables = callables;
}
handleKeyDown = (event) => {
// Call the child function doSomething()
this.childCallables.doSomething(event);
}
render() {
return (
<input
type="text"
onKeyDown={this.handleKeyDown}
>
<Child setCallables={this.setChildCallables} />
)
}
}
class Child extends React.Component {
componentDidMount() {
this.props.setCallables({
doSomething: this.doSomething
});
}
doSomething = (event) => {
// Get the event from parent
}
render() {
return (
[...]
)
}
}
Basically, I'm using a props to store the child's methods I need to access from the parent. The methods are saved in the props just after the child component is mounted.
1. Since it is an answer to a completely different question, I don't think marking this one as a duplicate would make sense.

You can write a HOC like this:
const withChild = Wrapped => class Child extends React.Component {
doSomething = (event) => {
}
render() {
return (
<React.Fragment>
<Wrapped {...this.props} onKeyDown={this.doSomething}/>
whatever Child should render
</React.Fragment>
)
}
}
const ParentWithChild = withChild(class Parent extends React.Component {
handleKeyDown = (event) => {
// Call the child function doSomething()
if (typeof(this.props.onKeyDown) === 'function') {
this.props.onKeyDown(event);
}
}
render() {
return (
<input
type="text"
onKeyDown={this.handleKeyDown}
>
)
}
});

Try calling doSomething in render method before returning on the basis of props changed but this will result in an infinite loop in case you are changing the state of child component in doSomething.

Related

Learner question: How to pass data from one class to another

I am learning spfx dev. I am creating a form with several different classes to learn how they can interact and pass data between each other.
I have two separate classes. One Parent class has a submit button which uses the Parents state to submit to a SharePoint list.
The other class component has it's own set of states and fields. I want whatever is entered by the user in the child component, to be submittable(!) by the parent class.
Here's my submit function:
private _onSubmit() {
this.setState({
FormStatus: 'Submitted',
SubmittedLblVis: true,
}, () => {
pnp.sp.web.lists.getByTitle("JobEvaluationItems").items.add({
JobTitle: this.state.JobTitle,
Faculty: this.state.Faculty,
Department: this.state.SelectedDept,
SubDepartment: this.state.SubDepartment,
DeptContactId: this.state.DeptContact,
FormStatus: this.state.FormStatus,
EvalType: this.state.EvalType,
JobTitReportTo: this.state.JobTitReportTo
}).then((iar: ItemAddResult) => {
let list = pnp.sp.web.lists.getByTitle("JobEvaluationItems");
list.items.getById(iar.data.Id).update({
JobRef: 'JE'+iar.data.Id
});
this.setState({
JobRef: iar.data.Id
});
});
});
}
Here is a function from the child component which handles whatever is typed into a field:
private _onJobTitReportToChange = (ev: React.FormEvent<HTMLInputElement>, newValue?: string) => {
this.setState({
JobTitReportTo: newValue
});
}
How would I pass the state function above (which is held within the child component) to the Parent component?
class Child extends React.Component {
state = {
childValue: 1
}
onChange = e => {
this.setState({childValue: e.target.value}, () => {
this.props.onChange(this.state);
})
}
render () {
return <input value={this.state.childValue} onChange={this.onChange} />
}
}
class Parent extends React.Component {
state = {
parentValue: 123,
dataFromChild: null
}
handleChildChange = childData => {
this.setState({dataFromChild: childData});
}
render () {
return (
<div>
<Child onChange={this.handleChildChange} />
<pre>{JSON.stringify(this.state, null, 2)}</pre>
</div>
)
}
}
ReactDOM.render(<Parent />, document.querySelector("#root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
In React world are two common used ways to transfer data:
If you want to pass it down to the child component - use props;
If you want to pass it up to the parent component - use callback;
There is another way - Context, but it's a whole different story.
if you want to pass data from one component to other.Follow the below steps.
1.PARENT --> CHILD
In parent component's render
render(){
return (
<ChildComponent data1={} data2={}/>
)
}
2.CHILD-->PARENT
make a handler in your submit function which is received to this child component from props
//CHILD COMPONENT
onSubmit=()=>{
...
//some data
...
this.props.onSubmit(data)
}
//Parent component
render(){
return(
....
<ChildComponent onSubmit={this.onSubmit}/>
....
)
}
How would I pass the state function above (which is held within the child component) to the Parent component?
It's one of React's concepts called lifting state up.
class Parent extends React.Component {
const someFunction = () => {} // move the function to the parent
render() {
return (
<>
<ChildComponent someFunction={someFunction} /> // pass function down to child
</>
)
}
}
const ChildComponent = (props) => {
return <Button onClick={props.someFunction} /> // use parent function
}

Changing the state of parent component inside a child

I have started learning react and I couldn't find the answer for one of my questions.
Let's say I have a parent component called main where I have states and one of them is a counter.
Inside main i have a child component called secondary with a few buttons.
Is that possible to change state of the parent component using the function inside the child component?
If not, would Redux help to solve that task?
Thanks in advance for clarification
Your parent component can keep a state, initialized in the constructor like follows:
class Main extends Component {
constructor (props) {
super(props)
this.state = { counter: 0 }
}
...
Then, you want to create a method in Main that increments your state counter:
incrementCounter = () => {
this.setState({ counter: this.state.counter + 1 })
}
You can pass this function reference to a child component as a prop. So, you might see this in the render method of Main:
<Child incrementParent={this.incrementCounter} />
Then, in the Child, whenever you'd like, you can call this.props.incrementParent() - and this will call the function that was passed to it from Main, which will increment the state counter for Main.
Here is a code sandbox that shows all of this put together. In it, there is a parent container (Main) with its own state counter. There is a Child component which has 3 buttons. Each button click increments its own counter, but also any button click will increment the parent's counter.
Hope this helps!
Yes, you can create a function that updates the parent state, and pass it down via props, context or redux if your particular app needs it.
E.g. Using React hooks
function Parent() {
const [value, setValue] = useState(1);
const increment = () => {
setValue(value + 1);
}
return (
<Child increment={increment} />
);
}
function Child(props) {
const { increment } = props;
return (
<button onClick={increment} />
);
}
class Parent extends Component {
state = { counter: 0 };
updateCounter = () => {
this.setState(prevState => ({
counter: prevState.counter + 1
}))
};
render() {
return (
<>
<Text>{this.state.counter}</Text>
<ChildComponent updateCounter={this.updateCounter} />
</>
);
}
}
class ChildComponent extends Component {
...
render() {
return (<Button onPress={this.props.updateCounter}><Text>{'Press Me!'}</Text></Button>)
}
}
You should pass the function of updating your counter updateCounter from parent, pass that reference to ChildComponent
You can update parent state by passing a function to the child component to update the parent.
Upon calling updateCounterfunction in the example, you can decrement or increment the value of count by supplying an operator in the function's argument which can be plus or minus
class Parent extends Component {
state = {
count: 1
};
updateCounter = operator => {
if (["plus", "minus"].includes(operator)) {
this.state(prevState => ({
count: prevState.count + (operator === "plus" ? 1 : operator === "minus" ? -1 : 0)
}));
}
}
render() {
const { count } = this.state;
return (
<Child count={count} updateCounter={this.updateCounter} />
);
};
};
const Child = props => (
<div>
<button onClick={() => props.updateCounter("minus")}>MINUS</button>
<button onClick={() => props.updateCounter("plus")}>PLUS</button>
<h1>{props.count}</h1>
</div>
);

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.

Children ref undefined react native

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>

Dom contains or compareDocumentPosition analog in react

I have some component that contains another portaled component like
<Parent>
<Portal>
<Child>Lorem Ipsum</Child>
</Portal>
</Parent>
But on real dom it look's like:
<div class="parent"></div>
<div class="child">Lorem Ipsum</div>
I need to know, that native node of Child component is child of Parent.
Well, i had a slightly different problem. And that the Parent containes Child, we can know from event bubbling.
Portal:
class Portal extends React.Component {
el: any;
constructor(props: any) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
document.body.appendChild(this.el);
}
componentWillUnmount() {
document.body.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el,
);
}
}
Child:
class Child extends React.Component {
render() {
return <div className="child">Lorem Ipsum</div>;
}
}
Parent:
class Parent extends React.Component {
target: any;
componentDidMount () {
document.addEventListener('mousedown', this.onDocMouseDown);
}
componentWillUnmount () {
document.removeEventListener('mousedown', this.onDocMouseDown);
}
onDocMouseDown = (e: any) => {
const target = e.target;
const isChild = this.target === target;
if (isChild) {
// do something
}
this.target = null;
};
onElemMouseDown = (e: any) => {
this.target = e.target;
};
render() {
return (
<div className="parent" onMouseDown={this.onElemMouseDown}>
<Portal>
<Child />
</Portal>
</div>
);
}
}
From React Portal documentation:
An event fired from inside a portal will propagate to ancestors in the
containing React tree, even if those elements are not ancestors in the
DOM tree
In Parent class we have handle for event 'mousedown' on document and on the wrapper div. When mousedown is fired, first will work wrapper handle. We save it to some variable like 'this.target'. Then event bubbling to document and in handle for document we can equal 'e.target' with 'this.target'. Accordingly we can to know is this target in Parent or not.

Resources