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 Providercomponent called<Parent />contains logic and state that is passed to all the<Child />components which are theConsumers.
- Childcomponents receive a- verifyprop that is essentially a validator function of sorts for that particular- Child. Each call to- verifyproduces a- verificationResultbased on incoming state changes from the the- Parent.
- Importantly, all the other Childcomponents must be notified or be aware of the result of eachverificationResultproduced by its siblings.
- To make things extra interesting, I'd prefer not to store the so called verificationResultof each child in the parent's state if I dont have to since it is essentially derived state and can be computed inParentrender().
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.valueand- this.props.setVerificationResultis received from the- Parentwhich is a context- Provider, while
- this.props.hashand- this.props.verifyare passed to the- Childcomponent 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.
