-
Notifications
You must be signed in to change notification settings - Fork 47.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(ReactNative): prioritize attribute config process
function to allow processing function props
#32119
base: main
Are you sure you want to change the base?
feat(ReactNative): prioritize attribute config process
function to allow processing function props
#32119
Conversation
process
function to allow processing function props
This is an interesting direction, and likely we can support this without major changes. It does mean that these function props completely bypass React's event dispatching model, which I think warrants further discussion. Maybe all direct events should be passed through like this? Or maybe we can should change the event emitter API so you can emit direct callbacks as if they were a function (with a return value)? In the short-term, please add unit tests here to demonstrate the changed behaviour. |
} else if (typeof attributeConfig.process === 'function') { | ||
// An atomic prop with custom processing. | ||
newValue = attributeConfig.process(prop); | ||
} else if (typeof prop === 'function') { | ||
// A function prop. It represents an event handler. Pass it to native as 'true'. | ||
newValue = true; | ||
} else if (typeof attributeConfig !== 'object') { | ||
// An atomic prop. Doesn't need to be flattened. | ||
newValue = prop; | ||
} else if (typeof attributeConfig.process === 'function') { | ||
// An atomic prop with custom processing. | ||
newValue = attributeConfig.process(prop); | ||
} else if (typeof attributeConfig.diff === 'function') { | ||
// An atomic prop with custom diffing. We don't need to do diffing when adding props. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is correct. You're assuming now that attributeConfig
is always going to be an object which you can read process
function of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, good catch tanks. Added an additional check for object. Will also add unit tests in a moment
a2cc692
to
7adf510
Compare
This would probably require quite a few changes on the native side. For reference, this is how Nitro converts a But I can definitely look into this as a future improvement for react-native core/TurboModules once this PR is merged - this is one of the places where Nitro has safer memory management and better performance as well - so maybe I can upstream this somehow :) |
Could you please share more details about the motivation for this change? What's your use case? |
We have a custom framework (Nitro) that allows you to build native modules and soon also native components for React Native. Instead of going through React Native's default prop converter (JS value ( We built this, and it works for every prop so far (e.g. We dug around the code and found out that this is because RN handles functions/callbacks differently - they are not retained on native, but instead built as a event dispatcher system on the JS side, loosely called from native. This PR allows to change this behaviour if needed - so no breaking change for existing code, but with a custom view config we can adjust it for our cases. |
Added the unit test @javache . One question though, I opened another PR in react native for updating the Is it the right place to do it in /react-native or should it be added to /react (like this change) ? |
Following up with @rubennorte on this - seems like this fork is only used for |
You've only changed |
Also, could this work as a workaround? Instead of passing the function through directly, wrap it in another object. On the native side, it just means you have an extra jsi::Object to unwrap. |
That's a good idea - we could wrap callbacks as in objects, I'll try to see if this works. If it isn't recursively parsing types it should work just fine. But still I think we can avoid that workaround and just parse functions directly if possible :) |
2313318
to
cf9168f
Compare
I brought the changes over for |
packages/react-native-renderer/src/ReactNativeAttributePayloadFabric.js
Outdated
Show resolved
Hide resolved
// we don't assume its a regular event handler, but use the process method: | ||
nextProp = attributeConfig.process(nextProp); | ||
if (typeof prevProp === 'function') { | ||
prevProp = attributeConfig.process(prevProp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't seem right that we process here before diffing - for other properties we first diff and then we process. Assuming that we have we have a process
method here also doesn't match the strict validation we do in the rest of the method.
This whole method seems to invoke process
at multiple different points though, so it seem hard to keep the default for functions.
Could you try something like this?
const attributeConfigHasProcess = typeof attributeConfig === 'object' && typeof attributeConfig.process === 'function';
if (typeof nextProp === 'function' && !attributeConfigHasProcess) {
// keep as before
}
/* rely on process being invoked as it does for other methods */
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I see, that makes much more sense!
…we have a process function
11eb8f8
to
9e74020
Compare
Good again for review (thanks for taking the time and effort!) |
Summary
In react-native props that are passed as function get converted to a boolean (
true
). This is the default pattern for event handlers in react-native.However, there are reasons for why you might want to opt-out of this behavior, and instead, pass along the actual function as the prop.
Right now, there is no way to do this, and props that are functions always get set to
true
.The
ViewConfig
attributes already have the API for aprocess
function. I simply moved the check for the process function up, so if a ViewConfig's prop attribute configured a process function this is always called first.This provides an API to opt out of the default behavior.
This is the accompanied PR for react-native:
How did you test this change?
I modified the code manually in a template react-native app and confirmed its working. This is a code path you only need in very special cases, thus it's a bit hard to provide a test for this. I recorded a video where you can see that the changes are active and the prop is being passed as native value.
For this I created a custom native component with a view config that looked like this:
compressed.mp4
Additionally I made sure that this doesn't conflict with any existing view configs in react native. In general, this shouldn't be a breaking change, as for existing view configs it didn't made a difference if you simply set
myProp: true
ormyProp: { process: () => {...} }
because as soon as it was detected that the prop is a function the config wouldn't be used (which is what this PR fixes).Probably everyone, including the react-native core components use
myProp: true
for callback props, so this change should be fine.