0

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:

  1. A Provider component called <Parent /> contains logic and state that is passed to all the <Child /> components which are the Consumers.
  2. 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.
  3. Importantly, all the other Child components must be notified or be aware of the result of each verificationResult produced by its siblings.
  4. 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.
Stuart Bourhill
  • 603
  • 4
  • 10
  • Can you clarify on what you mean by "all the other Child components must be notified or be aware of the result of each verificationResult produced by its siblings". In a sense of, why do they need to be notified? And is there a way to use a some kind of service to notify them? (some kind of a flux type architecture) – Yftach May 30 '18 at 11:41
  • On your second question of why not to use solution number 2: in all honestly you can, however it is not recommended and not the react way. By that I mean, that react was created with a certain mindset, where all changes to the application are DATA driven. If everything is data driven, a parent component should not be able to independently make a child component do something, what it should do is change data and tell the child to see if any of those data changes applies to them. – Yftach May 30 '18 at 11:46
  • This is in order to make everything organized, all the child component logic and logic of when things happen are in the child component. – Yftach May 30 '18 at 11:46
  • 1. I want this to be pure react. As few dependencies as possible - that goes double for a state management library. 2. Perhaps notify is the wrong word. The problem at its core is that a prop on a child needs to make changes to state in a parent, and that bit of state needs to be available in all consumers. However when you think of validation (in this case I simply call it verification) validation is a product of the current state of some value or values. It need not be stored in state (this forms part of the reason for my preference for solution 2). – Stuart Bourhill May 30 '18 at 12:05
  • You can implement flux yourself, however it might be an overkill if you just use it because of this. Ultimately it's a matter of judgement that only you can make, because you have all the most information on the kind of project you are building. – Yftach May 30 '18 at 12:14

0 Answers0