How to Expose macOS Native Modules to React JavaScript
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the Building React Native Apps for Mac course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Building React Native Apps for Mac, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
[00:00 - 00:09] In a previous lesson, we created our own native functionality, but we haven't been able to call it from the JavaScript side yet. So that's what we're gonna explore in this lesson.
[00:10 - 00:24] So I'm gonna go back into VS Code, and we could call our native functions directly, right? We could import a special module from React Native and just start using our functions.
[00:25 - 00:47] However, we're gonna be a little bit smarter about it, and we're actually gonna create a wrapper for our code. So I'm gonna go to my leaves folder within my source folder, and I'm gonna create building apps.native.ts file.
[00:48 - 00:56] I'm just gonna take the code from the course. And let's go over it.
[00:57 - 01:07] So first of all, I'm importing a module from React Native, the native modules. So the native modules exposes a lot of the native functionality in React Native .
[01:08 - 01:26] It will also expose our custom class that we created, which is this one. And right from the start, the ideas that we need to introduce or we want to introduce type safety into our code.
[01:27 - 01:33] So we're gonna create an interface. I'm just gonna call it the interface for building apps native.
[01:34 - 01:43] And I'm gonna type my two methods that I created before. My keychain write, which takes a string and a payload, and returns a promise.
[01:44 - 01:58] And my keychain read, which just takes a key and returns again a promise. Then I'm gonna create, I'm gonna use the same pattern that I used to create my stores, which is a factory function.
[01:59 - 02:14] It's gonna be called create building apps native. And it's gonna take the module, a native module, and it's gonna have an any type anyways, because all the dispos native modules from React Native, they don't have any types by default.
[02:15 - 02:26] And it's gonna return an object with the same interface. So on the body of my function, I can just return an object with the keychain read and keychain write.
[02:27 - 02:38] And I'm just gonna directly bind the expose methods from my native module. Now, how do I get the native module is quite easy.
[02:39 - 02:58] Here you can see I'm gonna export an instance and calling my create building apps native function. And from the native modules object itself from React Native, I'm extracting the same class name that I did on my native code, my objective C code.
[02:59 - 03:19] Now, there's a good thing about this pattern, because it encapsulates certain things that you might not able to do from the native side. So let's say for example, the keychain is only available on macOS and iOS platforms, right?
[03:20 - 03:37] So I shouldn't be able to call this from Android for example, or a Web TV device or something like that. So here I can just use any other module or any other code, even from React Native or from my own application, to disable functionality, right?
[03:38 - 03:56] So I can go something like platform.os is, I just want to make sure that this doesn't crash on the native runtime or something, right? So I can just disable functions as I go or I say please.
[03:57 - 04:11] Great, so the next thing I need to do is I'm gonna one more time add this to my index. I'm really not just native.
[04:12 - 04:27] And finally, I can go into my UI store, because this is the store, if you remember that's holding the actual data, that I'm getting from my server. So this is the store that I want to persist.
[04:28 - 04:51] If you remember the basic idea, why we started exposing the functionality was that we wanted to add persistence to our app. So in order to do this, I'm gonna import first of all my instance of the native code that I created, which is the building apps native from my libster.
[04:52 - 05:10] And in order to do this, we need to talk a little bit about the Mobx store serialization, or how do you serialize Mobx observables? Maybe it would be better if we take a look into the persist function first.
[05:11 - 05:25] So let me just take the code from the course, and I'm gonna create the function out here. So this is my persist function.
[05:26 - 05:42] Now it's an async function, because the keychain right again is async. So if at some point I wanted to change or chain calls, I should be able to await for this function.
[05:43 - 05:55] But actually let me just create it inside of here. Yeah, that works better. Okay, so basically I have a persist function.
[05:56 - 06:02] And inside of this, I'm gonna get a plain state. What does this plain state is supposed to be?
[06:03 - 06:21] It's supposed to be the state of my observable of my entire store, but not as observers or observables or this enhanced object, right? Because you cannot serialize those. If you want to serialize, you want to convert everything into simple objects, right?
[06:22 - 06:31] You want to convert them into JavaScript objects, into arrays, into strings, into numbers. So basically that's what I want, a very simple representation of my store.
[06:32 - 06:40] Now the question is how do I get that? And Mobx provides this helper, which is 2.js, right?
[06:41 - 06:56] It takes Mobx object, it could be an observable, it could be a plain object, it could be anything. And it tries to extract all the red values out of it, you know, and return simple JavaScript.
[06:57 - 07:18] So I'm gonna pass my store into the 2.js function. And once I have my state, I can just write it into my keychain, right? I'm just gonna name it a state, it's quite simple because I'm using the keychain value from my native code, so I don't have to add any special key to it.
[07:19 - 07:43] And then I'm just gonna convert it to JSON, to a plain JSON string, and I'm gonna write that to the database, or to the keychain. Now, the good thing about this, and we will see this in a little bit, is that this 2.js store is recursive.
[07:44 - 08:01] And because it's recursive, it means that if it's registered as an observer, any time, any value of my store, it could be, you know, nested value, right? It could be a property inside of a book, inside of my store.
[08:02 - 08:27] If that changes, this is gonna get picked up, and therefore, the persist function will run again on its own, without me having to do anything to detect the changes. Right, that is one of the great things about observers, again, you don't need to worry how they're being updated, when they're being updated, you just need to access them.
[08:28 - 08:39] And as long as you access them, your code will react to any changes. All right, great, so now we have our persist function.
[08:40 - 08:48] We're gonna move to our hydrate function. So, just put the same code that's on the lesson.
[08:49 - 08:55] Again, hydrate is an async function. And this time is the reverse operation, right?
[08:56 - 09:08] I'm gonna take from my native module, a keychain read, gonna read the same key as before. I have to await it, because it's an async operation, and then I'm gonna get a string state.
[09:09 - 09:14] This is some defensive programming, right? The first time we start the application, nothing will be there.
[09:15 - 09:26] So, if we try to parse it, it's gonna crash, which is not something we want. So, I just check if it exists, then I'm gonna parse it, I'm gonna convert it from JSON into a pure JavaScript object.
[09:27 - 09:41] And then I'm gonna have a plain JavaScript object. We have seen before, anytime that I await any function, and I want to modify my mobic state, I have to run it in an action.
[09:42 - 09:51] So, running action. And then, from my store, I'm gonna take the books, right?
[09:52 - 10:05] So, I'm gonna walk over from my parse store, the books array, gonna map over them. And then I'm just gonna return plain object.
[10:06 - 10:22] And I'm gonna put that array that I iterated upon into my store books. This is a little bit cumbersome, but that's what you need to do, because some of the data, it's in a different format, right?
[10:23 - 10:37] You could put everything, everything, everything in your store, just plain, plain strings, numbers, and everything. And then just generate, use it, compute the property, something like data objects, for example, right?
[10:38 - 10:45] That's also doable, right? This is something that you will need to decide on your application, if it makes sense or not.
[10:46 - 10:50] Great. So, now we have our persist and our hydrate function.
[10:51 - 11:15] We should be able now to connect it to our API store. So, basically, whenever I create my UI store, this is gonna run, I'm gonna create the instance, and after I create the instance, what I want to do is I want to call the hyd rate function.
[11:16 - 11:46] Once hydrate has done its job, because it's an async function, right? I am going to chain another promise, or I'm gonna do something about the state or the new state of the application, and then I'm gonna use another utility, which is auto-run, again, from Mobx, and I'm gonna say persist.
[11:47 - 12:13] So, auto-run is what takes a function that uses observables and turns it into an observer. Basically, if we go back into our Book's container, this observer function is very, very, very similar to this auto-run, if you think about it, right?
[12:14 - 12:30] This observer function turns a regular React component to an observer, right? It tells it whenever any of the observables inside of your body changes, you're gonna re-render.
[12:31 - 12:42] The auto-run function does the same for any generic function, right? It says this function whenever any of the observables you're using changes, you 're gonna re-run yourself.
[12:43 - 12:52] And that's basically it, right? We tell our server to hydrate. After it's hydrated, we can start persisting our state on its own.
[12:53 - 12:57] So, I'm gonna save this. Here is our application.
[12:58 - 13:08] I am going to reload this, and let me think. I should kill my server.
[13:09 - 13:24] And now if I reload it, I have a persisted state, right? Right now it's on handling, and there's an on-handed promise rejection, because it's trying to reach the server, but it doesn't matter.