The "best" way to do it is like you said, "core logic in a cross platform language and native gui code". The general path is this:
1. design things using an MVVM pattern and share all your business logic
2. write all your native gui code manually as you tease out the patterns
3. over time, you can start moving the UI generation into the shared code by writing a custom DSL
The problem with platforms like React.Native and Elecron is that they are based on having a JS runtime, which is a de-facto performance hit and forces an async communication flow to the "native" side (flutter also has this issue). Xamarin does not have that same restriction, and is my preferred framework for this. Xamarin.Native (aka Xamarin.iOS/Xamarin.Android aka Xamarin.Traditional aka Xamarin) has a very minimal performance overhead for what it gives you in terms of native bindings and a shared codebase. Xamarin.Forms is a higher level abstraction that sits on top of Xamarin.Native and lets you theoretically write 100% shared code (by essentially being a community developed UI DSL like I outline in step 3 above), but it is buggy and has much worse performance.
The hardest problems you will have to solve using this approach are navigation and lifecycle. For the most part these are well understood issues and you will find lots of postings with different strategies on how to approach them.
1. design things using an MVVM pattern and share all your business logic
2. write all your native gui code manually as you tease out the patterns
3. over time, you can start moving the UI generation into the shared code by writing a custom DSL
The problem with platforms like React.Native and Elecron is that they are based on having a JS runtime, which is a de-facto performance hit and forces an async communication flow to the "native" side (flutter also has this issue). Xamarin does not have that same restriction, and is my preferred framework for this. Xamarin.Native (aka Xamarin.iOS/Xamarin.Android aka Xamarin.Traditional aka Xamarin) has a very minimal performance overhead for what it gives you in terms of native bindings and a shared codebase. Xamarin.Forms is a higher level abstraction that sits on top of Xamarin.Native and lets you theoretically write 100% shared code (by essentially being a community developed UI DSL like I outline in step 3 above), but it is buggy and has much worse performance.
The hardest problems you will have to solve using this approach are navigation and lifecycle. For the most part these are well understood issues and you will find lots of postings with different strategies on how to approach them.