react-reconciler questions
See original GitHub issueDo you want to request a feature or report a bug? No
I’m trying to build a custom renderer for react using react-reconciler
. Most of the documentation and examples I’ve been able to find support rendering to a target once, but not re-rendering. But this is what I’ve been able to discover and am looking for more info on.
prepareUpdate
allows you to generate a diff of the old props and new props. The examples I’ve found say this should return an array but I’m not sure what the items in this array should look like. So for now I’m just returning true from this method.
commitUpdate
is run after prepareUpdate
with old and new props, after which I can call the instance to tell it to update itself based on those props. But most examples I’ve found do not rely on the children
from props and rather rely on children
to come through methods such as appendChild
/removeChild
. It appears that when commitUpdate
is run, none of the various *Child
methods are called leading the newProps
in commitUpdate
to not reflect the children that are being tracked inside of the class. In this case I’m curious if perhaps I’m missing a *Child
method in my implementation that is necessary but I don’t know exists.
appendChild(child) {
this.children.push(child)
}
commitUpdate (oldProps, newProps) {
// this.children != newProps.children
}
I’ve also noticed that in order for commitUpdate
to be called you must also pass in supportsMutation
. Is this the correct property to get this working?
In some examples I see that projects also have a mutation
object in their host config that includes essentially copies of the *Child
and commitUpdate
methods from the host config but when I try to do this in my renderer, those methods never seem to be called.
I’m also curious what the correct paradigm for attaching the underlying instances to one another. Currently, each of my elements has a render method that will call the render method of all of it’s children and then add those children as sub views to the instance. But in cases where children might change, if I called render again I would end up with duplicate children in the parent. I’ve seen some examples where the elements attach themselves to their parents, rather than rendering all children and attaching the children to itself, which seems like it would resolve this issue, but this brings up another question.
For a custom element, when is the correct time to add/remove children from the underlying instance? Most examples I’ve seen use *Child
methods to keep track of the children internally and then have a method that renders all children and adds them as sub views. But there is a question in my mind as to whether or not the *Child
methods are where I should be adding the children as subviews to the instance. For example, when an instance’s appendChild
method is called, that’s when I should add the subview as a child to the element. When removeChild
is called, that is when I should remove that subview from the instance. Rather than simply using these methods to keep track of children to then render them at some other time.
appendChild(child) {
this.view.addSubview(child)
}
removeChild(child) {
this.view.removeSubview(child)
}
Rather than
children = new Set()
appendChild(child) {
this.children.add(child)
}
removeChild(child) {
this.children.delete(child)
}
render() {
this.children.forEach(child => {
this.view.addSubview(this.child.render())
})
return this.view
}
Issue Analytics
- State:
- Created 5 years ago
- Reactions:1
- Comments:17 (7 by maintainers)
It can return anything from
prepareUpdate
(we typically use arrays but it’s not required). Whatever you return will be passed as the second argument tocommitUpdate
.The first method lets you calculate a “diff” between props, and the second one is where you apply it. The methods are separated so that diffing can happen asynchronously, but the actual mutation is only performed after everything is ready.
Typically we use a form like
[propName1, propValue1, propName2, propValue2, ...]
for props that changed. But you can use any other format.This is right. To correctly “handle” children you’d need to reimplement React itself. So instead React does it for you, and calls the appropriate methods for adding, removing, and moving them.
I’m not 100% sure what you mean. A specific example would help. React should take care of calling append/insertBefore/removeChild when appropriate and you shouldn’t do anything special for that to work.
Maybe ReactDOM implementation would help? It’s here:
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOMHostConfig.js
Yes.
Those examples use an older reconciler version. The new one removes the mutation object and instead uses supportsMutation property, and puts those methods in the top level object. So your approach was correct.
I’m struggling to understand your description. You shouldn’t need to explicitly “manage” children of any nodes except implementing a few methods like appendChild. React will call them for you. You don’t need to attach anything or manage parent/child relationships yourself. Again, please refer to the DOM implementation above. Does this help?
I’m also struggling to understand what you’re asking exactly here. But your first code example after this paragraph looks more reasonable than the second one. I don’t understand what the second one is doing (reconciler doesn’t call any method called “render”).
Well, I guess it depends on whether there exists some API that manages underlying children or not.
If there is such an API (such as the case with DOM) then that’s what you should use. The whole premise of using the reconciler is it lets you translate declarative “render” operations into imperative platform-specific “append/insert/remove” calls. Doing the translation the other way around sounds like losing valuable information to me.
Overall I don’t recommend looking at those projects for the “intended” way to use these methods. Look at our own code. I already linked to the DOM implementation of these methods:
https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOMHostConfig.js
Does it help?
Calling the underlying library’s append/insert/remove methods is definitely the intended approach.
Yes, I linked above to the DOM renderer host config. Every export in it is a method you need to implement. There are several “sets” (common, mutation, persistence, hydration). You probably only need common+mutation.
You can also find the full list here:
https://github.com/facebook/react/blob/master/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js