I have an interesting use case for storing references to a function that belongs to a child component. As an aside, I'm using the React's new context API to pass data to deeply nested children components.
I have found a number of other answers that address similar problems, but they dont quite match my use case. See here and here.
This is the high level problem:
- A
Provider
component called <Parent />
contains logic and state that is passed to all the <Child />
components which are the Consumers
.
Child
components receive a verify
prop that is essentially a validator function of sorts for that particular Child
. Each call to verify
produces a verificationResult
based on incoming state changes from the the Parent
.
- Importantly, all the other
Child
components must be notified or be aware of the result of each verificationResult
produced by its siblings.
- To make things extra interesting, I'd prefer not to store the so called
verificationResult
of each child in the parent's state if I dont have to since it is essentially derived state and can be computed in Parent
render()
.
Solution 1:
store verificationResult
of each child in Parent
. This could be done by waiting for the relevant value to change in each Child
's componentDidUpdate()
like so:
// Child.js
...
componentDidUpdate(pp) {
const { hash, value, verify, setVerificationResult } = this.props
// this check is a little more involved but
// the next line captures the gist of it.
if(pp.value[pp.hash] !== value[hash]) {
setVerificationResult(hash, verify(value[hash], value))
}
}
...
// Parent.js
...
setVerificationResult(hash, result) {
this.setState(({ verificationResults }) => ({
...verificationResults, [hash]: result
}))
}
...
Note:
this.props.value
and this.props.setVerificationResult
is received from the Parent
which is a context Provider
, while
this.props.hash
and this.props.verify
are passed to the Child
component directly.
this.props.hash
grabs the portion of value
which this particular Child
needs to know about.
// MyComponent.js
render() {
return (
...
<Child
hash="someHash"
verify={(value, allValues) => {
return value === 42 && allValues["anotherHash"] === 42
}}
render={({ verificationResults }) => (
<pre>{JSON.stringify(verificationResults["someHash"], null, ' ')}</pre>
)}
/>
...
)
Solution 1 conforms to unidirectional data flow principle that react encourages. However I have 2 issues with this solution. Firstly, I'm having to store what is essentially state that can be derived. Secondly, calling setVerificationResult()
will cause an unwanted re-render. Not to mention the additional logic in componentDidUpdate
Solution 2
The next options looks something like this. Note that I've tried show only the important bits:
// Child.js
...
componentDidMount() {
const { register } = this.props
register(this.verify)
}
verify(values) {
const { hash, verify } = this.props
return { [hash]: verify(values[hash], values) }
}
...
// Parent.js
constructor(props)
super(props)
this.tests = []
this.state = {
value: null,
// other initial state
}
...
}
register(verifyFunc) {
this.tests.push(verifyFunc)
}
render() {
const { value } = this.sate
let result = {}
this.tests.forEach(test => result = { ...result, ...test(value) })
return (
<Provider
value={{
...this.state,
verificationResults: result,
register: this.register,
// other things...
}}
>
{this.props.children}
</Provider>
)
}
Notice that in the second solution I'm not storing any additional state since its just calculated on the fly. However I am storing references to a function on a child component.
Can anyone tell me why NOT to use solution 2? Any alternative suggestions on how to make this work?
Additional notes:
- Why not pass the verify function to the Parent directly? I could, but it wouldn't make for a very clean API.
- You can assume that I'm performing the necessary clean up on unmount, etc.
- The problem is more complex than shown here - there are in fact deeply nested Consumers that communicate with a "local" Provider which "splits" values and logic until they reach so called leaf Components, while a "Master" Provider is used as the root node containing all component state and logic in the hierarchy of components.
See Question&Answers more detail:
os