I’m trying to trigger an event from a Parent component to a Child component and I don’t find any suitable solution for my case.
My use case is a Parent component which handle save action by displaying buttons or else, and a Child component which need to know when he has to save his data.
The only way which satisfies me is by using RxJS, but I feel it’s not a good practice.
I specify that I’m in a context where I try to write an UI library, so I try to simplify and type props as much as possible.
Moreover, my Child component is injected in the Parent component dynamically, so I can’t just wrap the Child component with Parent and pass a callback to Parent component.
I thought about two solutions:
- By using a boolean
My Parent component can handle a boolean and pass it to the Child component. My Child component can react when the boolean changes.
I think it is more a workaround than a real solution.
- By using a ref
The Parent component explicitly calls a function defined in Child component.
Why not, but I think it’s quite ugly. I loose typing, my Child component
Best solution for me using RxJS
If I could use RxJS I would pass an observable from Parent to Child, like this:
const Parent = () => {
const [subject] = useState(() => {
return new Subject<void>()
})
const onSaveClicked = () => {
subject.next()
}
return <>
<div>
<button onClick={onSaveClicked}>
Save something
</button>
</div>
<div>
<Child saveObservable={subject.asObservable()}/>
</div>
</>
}
const Child = ({saveObservable}: {saveObservable: Observable<void>}) => {
const [saveCounter, setSaveCounter] = useState(0)
useEffect(() => {
const subscription = saveObservable.subscribe({
next: () => {
// do someting, save for exemple
// Here I increment the count to show the event is well handled
setSaveCounter(x => x + 1)
}
})
return () => subscription.unsubscribe()
}, [saveObservable]);
return <>
Save triggered count: {saveCounter}
</>
}
I don’t understand why there is no mechanism already existing which allow me to do that, I feel like I messed something.
Someone can enlighten me ?
3
Answers
There is a Context API in react which you can leverage to achieve your requirement. With context, you can pass data down the component tree without passing props manually at every level(prop-drilling). You can create a context to handle the save action and consume it in the child component and it also simplifies the component structure and eliminates the need for third-party libraries like
RxJS
. Let me help you with your use-case using this.I would create a custom hook and a
Provider
.Then would wrap my Parent Component with the
SaveProvider
.Finally, I would use the save context in Parent and Child components:
SAMPLE DEMO
Note:
1
although the state is initialized with0
. The reason for it issetSaveCounter()
is called inside ofuseEffect
and on initial render it will trigger thesetSaveCounter()
.<StrictMode>
. If you’re using it(which you should and is strongly recommended in development mode in React18.x.x
) you will notice thesaveCounter
initializes from2
. The reason for this can be found here.You can preserve typing with
useRef
, and I think it’s probably the way to go with your constraints. You could also use the Context API, but that might reduce the reusability of your component, since you will create a dependency on the context. If the dependency issue is fine for you, then that might be a viable and clean option. Using ref, however, will allow you plug that component in anywhere without having to depend on a shared Context.Here is an example of that working: https://playcode.io/1744194
Declare subject in Parent:
Declare createSubject outside Parent:
Below is the concept, following React suggestion to avoid useEffect for React updates.