An OO Backend Dev's Crash Course To React Native
At some point in your career, you're going to get asked if you can fix that React thing. Or put together an app quickly for a church or charity. If you're someone who works on the backend, it's slightly distasteful peering into the world of front- end people. JavaScript developers can be right at home with chaos and often dogma about their craft.
If you've never touched this stuff before and need to be up and running quickly, this quick-read will get you moving.
And the first thing you're going to notice is this is easy. So easy, they have to invent twenty layers of bullshit for it to make it seem clever.
Before anything: these apps can be built in either/or two programming styles
This is extremely confusing. Really confusing. ECMAScript 6 (ES6) introduced classes in JavaScript. So if you've only done the old style, time to brush up on the latest version.
You can write JavaScript classes which extend React itself. This is known in the React world as Inheritance and it uses Pure Components. It's deprecated.
It's what we all recognise in the backend world as standard object-orientated programming.
import * as React from 'react';
export default class Something extends React.Component
{
}
export Something
But all the examples given use functional programming which uses Functional Components and is known as Composition.
What the hell is Functional Programming? Well, it's fashionable.
You don't use classes. You use functions instead. Everything works in function scope and no global vars are ever changed. It's Perl or bash scripting, basically.
import * as React from 'react';
import { View, StyleSheet, Button } from 'react-native';
import * as Speech from 'expo-speech';
export default function App() {
const speak = () => {
const thingToSay = '1';
Speech.speak(thingToSay);
};
}
So first things first. Are you using classes (pure components and inheritance) or functional?
Confused?
JavaScript? How the hell does this even work?
Watch this video from 2015 to understand where this all came from.
The nerds at Facebook wanted to run Javascript as a native component on a Mac or within iOS. It was a experiment to see if they could directly run Javascript in the OS and make it do something in the GUI.
Android apps are built in Java. IoS apps are built in Objective C. They each have easier methodologies now: namely Kotlin, and Swift.
The idea is it's "native" because it has a "bridge" in there which runs 2 Javascript threads. One for the app, and another in the background. The "bridge" translates code to actual native OS components.
Or something.
How the hell do you run this thing?
Expo.
What on earth is Expo? It's a SaaS company which provide workflow tools for publishing apps. Most of them are cross-platform development framework/environment binaries.
The idea is pretty simple. Your devices all sit on the same Wifi network. The app runs as a website on your laptop or LAN, and an app on a phone loads it into the UI.
Or, as they put it:
Expo is an open-source platform for making universal native apps that run on Android, iOS, and the web. It includes a universal runtime and libraries that let you build native apps by writing React and JavaScript. This repository is where the Expo client software is developed, and includes the client apps, modules, apps, CLI, and more. Expo Application Services (EAS) is a platform of hosted services that are deeply integrated with Expo open source tools. EAS helps you build, ship, and iterate on your app as an individual or a team.
More:
The quick way to do this if your pre-existing app has an app.json
file in it, is like so:
- Install the Expo Go app on your phone or tablet.
- Connect all the devices onto the same LAN or Wifi.
- Run the development server from the project folder using Expo CLI.
- Load the app in the Expo Go app to see it run live.
cd /path/to/project
nvm use 16
npm install --global expo-cli
expo start
Next you'll get a QR code to scan with the Go app, and two windows (usually on ports 19001 and 19002): the UI to be called through the Go App, and a Chrome DevTools window.
Use v16 of Node. 17 has problems with Expo.
If you need to use a different IP (for running a LAN server), run this in the shell before you call start:
export REACT_NATIVE_PACKAGER_HOSTNAME=192.168.1.76
Entrypoints and where it all begins
A React component is a visual thing which is booted as a class ("mounted"), and then displays ("renders") as a visual something on the screen. It's a weird static class which sits there and internally polls itself. A bit like an Instagram model really, constant checking their appearance in front of the camera.
Your packages are stored in package.json
as with all NPM-based apps. Your Expo project settings are in app.json
.
If you work with C or Python, you'll be looking for a __main()__
function. If you work with PHP, you'll be looking for index.php
or such. If you work with Node, you'll be looking for app.js
or index.js
specifically in the package file.
Expo/RH has a simple function file named app.js
. When the app starts or reloads, it goes here.
This is a file designed to use functional programming (see above). so it is not a class. This is obviously peak Facebook: mixing function entrypoints with returned classes and other mess.
The entrypoint is a function named App()
in app.js
:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App()
{
return
(
<SomeComponentIMadeWhichLoadsInitially />
);
}
export App
A <View>
is a <div>
element in html. You can't use these as siblings. It has to be a parent.
A <Text>
is a <span>
element in html.
Where this gets confusing - as with everything Facebook does - is how components can be nested almost endlessly as grandparent, parent, child, grandchild, great-grandchild, and so on. The whole display gets ridiculously complex, with components embedding themselves and replacing others.
For now, all you need to do know is you should return a <ComponentSomething /> from the render() function, somehow.
Layout Of A React Class
React has a peculiar way of doing things which could take 4 books to unpack. But for now, we're going to lay out what a class looks like so you can get moving quickly.
The visual side of React updates 60 times a second, in batches. Yes, that's correct. 60 times a second. This is called it's "lifecycle": https://www.w3schools.com/react/react_lifecycle.asp and more detailed: https://reactjs.org/docs/react-component.html
The key thing to understand if you are a backend dev and/or someone who works with code which is executed procedural from top to bottom is React loads the class, and then internally polls itself 60 times a second. It boots, and then is constantly re-rendering all the time, which is what gives it the sense of being "live".
It loads the component, builds it on screen ("mounts"), then invisibly cycles endlessly checking for state changes (i.e. if this.state
looks different) to update what's on screen. It's a bit like leaving an open infinite bash loop going on the command line to print out a socket or log a task to stdout.
It's a lot less sophisticated than people try to make it out to be.
Here's the tl;dr....
import * as React from 'react';
export default class Something extends React.Component
{
constructor(props)
{
super (props)
this.state =
{
foo: "bar".
fonts_loaded: false
}
}
componentDidMount()
{
// this is called once, when the UI builds the thing on screen.
// Like a boot() function
}
componentDidUpdate (prevState, newState)
{
// this is called when the thing's state changes somewhow.
// Could be 60x in 1 second
}
componentWillUnmount()
{
// this is a deconstructor
}
render()
{
// this is called whenever there is a change.
// It could be 60x in 1 second.
if (this.state.fonts_loaded)
{
if (something_else)
{
return <SomeOtherComponentClass foo="bar" />
}
return <Text>Done</Text>
}
return null;
}
}
export Something
You can think of these methods like so:
constructor()
-->__construct()
or__init__
componentDidMount()
-->boot()
componentDidUpdate()
-->changed()
componentWillUnmount()
-->__destruct()
or__del__
render()
-->display()
The naming is horrible. But what did you expect? It's millennial kiddies. Wouldn't this be so much easier?
export default class Something extends React.Component
{
constructor(props)
boot()
changed(before, after)
display()
}
Let's take a walk through this basic minimum.
- We call the class using an XML-style tag, and feed it constructor arguments as attributes.
return <SuperAmazingClass something="blah" another="meh" />
In our constructor for SuperAmazingClass.js, these will become:
constructor (props)
{
this.arg1 = props.something // resolves to "blah"
this.arg2 = props.another // resolves to "meh"
}
2. We need to set up an initial "state" (more on this later). Use anything you like for now.
3. React is going to immediately call render()
to try to build something on screen. The default should be to return null
if nothing's ready yet.
4. If all goes well, it will call componentDidMount()
.
5., Up to 60x per second, it will check to see if it needs to run render()
again because something has changed somewhere, like in the state
object.
6. If it does, it will call componentDidUpdate()
with a copy of the previous state and the new one, for comparison.
7. After losing focus or a parent state nukes the component, it will call componentWillUnmount()
to do garbage disposal.
So in order, it looks like:
- constructor()
- render()
- componentDidMount() (end of instantiation)
- render()
- componentDidUpdate()
- render()
- render()
- componentDidUpdate()
- render()
- etc
- componentWillUnmount()
At a minimum:
- You need a simple
state
object for storing data. - Something has to come back out of
render()
, even if it'snull
. - Assume everything, even setting state, is async. This will get you.
You should, by now, be realising this is a bit of a shitshow. It is. Just you wait for the bizarre error messages.
Hooks: State, Effect, Ref, Context... What?
Possibly the least sane part of React is trying to understand what "hooks" are. You might think they are software hooks. They're not. Hooks take a while to get your head round, because they're badly named.
Hooks are only for functional programming. Such as in App.js.
If you're using classes, ignore them. You're welcome.
Hooks are there to solve a problem in functional programming: when you're working only in function scope, you don't have access to the global application data (obviously). So "Hooks" are there to allow you to "hook in" to the application's state where it is at that moment. A hook isn't a "plugin" or anything like that.
From the docs:
Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks don’t work inside classes — they let you use React without classes.
https://reactjs.org/docs/hooks-overview.html
So "hooks" are a way of doing the same stuff you had been before (inherited classes), in the new trendy way. They solve problems the switch created.
Again, naming is a problem. They should really be named "bridges" or "pipelines". They bridge between what's inside the function, and what's in larger app.
Let's take the State example. State is just a standard JS object, like a Python dictionary or PHP array.
When we have 20 individual functions which need to talk to a central or parent stored state, they can use the "hook" (or "bridge") this way:
function some_blah ()
{
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Notice the function setCount()
is automatically created for you, the state variable is already set up, and the initial values are the arguments to the hook.
This is functionally equivalent to:
class Something
{
constructor()
{
this.state = {
count: 0
}
}
some_blah()
{
this.setState ({count: this.state.count+1);
}
}
Most are self-explanatory (ref = "reference" to another component, for example), but where it gets weird is useEffect.
Eh? What the? No, it's not a magic effect, or visual FX.
Again, more naming issues. This refers to terminology from functional programming (and computer science more broadly): side effects. The documentation is dreadful.
The Effect Hook lets you perform side effects in function components. If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
FFS.
Put simply, a side effect is when the internal state of an app is modified by contact with the outside world. It's not the same as a medicine side effect, for instance. Read more here: https://en.wikipedia.org/wiki/Side_effect_(computer_science) .
This is confusing because it's a bunch of university campus crap, on top of a lot of religious zealous other crap about the "purity" of functional programming.
FarLeftipedia actually does a good job here:
An operation, function or expression is said to have a side effect if it modifies some state variable value(s) outside its local environment, which is to say if it has any observable effect other than its primary effect of returning a value to the invoker of the operation.
Broadly speaking, when you see useEffect being used, what it means is a function is trying to adjust or message to other components outside itself.
Redux: God help us
If you're astute, you will have quickly worked out React has a serious problem: different components need to share data.
React solves this problem with Context.
And as you quickly realise, Javascript developers really are full of shit and the most pretentious tribe of any of us. What they could solve with simple language, they have to apply another layer of obscurantist, abstract bullshit onto.
Let's say we have 10 screens. Each one of them needs the same API token for making REST calls (which are nightmare unto themselves). Approach #1 is simply to pass-the-parcel using constructor arguments.
- Screen A (somedir/Cars.js)
- Screen B (somedir/Roads.js)
- Screen C (somedir/Stores.js)
- etc
- etc
Enter Redux. See: https://redux.js.org/
The world's most over-engineered, mindlessly complicated, over-the-top bullshit package for doing basic things the hard way.
To use it, you need to create a "store", and the a set of configuration specifying what to do to it called a "slice". Then, you wrap all the screens in it, HTML-style.
import { Provider } from 'react-redux';
import { NavigationContainer } from '@react-navigation/native';
import { Vault } from '../engine/Vault'; // Redux store object
// snip
render()
{
return (
<Provider store={Vault}>
<NavigationContainer theme={Mirror}>
// screens go here
</NavigationContainer>
</Provider>
);
}
To each child screen, you add the following to automatically load the data from the store into the object. Redux calls this "mapping".
const mapStateToProps = (state) =>
{
return { something: state.foo, another: state.bar };
}
function mapDispatchToProps(dispatch)
{
return {
addSomething: (param1, ...) => dispatch(addComment(param1, ...))
}
}
export default connect (mapStateToProps, mapDispatchToProps) (WhateverClassThisIs)
"Actions" need to be "dispatched" to "reducers", etc etc ad nauseam. Honestly, this is the worst of computer science. Bloated, pretentious abstraction to perform the simplest of operations.
It's a problem where the solution always make it worse.
Passing data parent to child and child to parent
Then comes the big issue. Let's say in the example above, you are listening in on a Websocket for events. And when you receive one, you need to pass it to a child component on the screen so it can do something. Why? Because you don't want 14 Websocket connections to the same host from 14 components.
React purists will tell you this is a job for Redux. Maybe, if you're feeling masochistic.
Sending messages downwards
The easiest way is to do this is to define a reference to the downstream component, and call a child method on it.
First, define a method in the child it can receive data on:
class Child
{
some_function = (param1, param2, param3) => {
this.setState ({received: param1})
}
}
Then, define a reference to it in the parent and call it directly:
class Parent
{
constructor()
{
this.my_var = null;
}
send_to_child()
{
this.my_var.some_function ("some arg", 1, false)
}
render()
{
<View>
<Child ref={instance => {this.my_var = instance}} />
</View>
}
}
Sending messages upwards
Wouldn't it be nice to have an event system for this? Doing it the other way requires a callback function sent in the constructor of the child.
In this example, a number of child elements need to ask their parent element whether they should keep polling network requests.
First, define the callback in the parent:
class Parent
{
constructor()
{
this.state = {
children_should_poll: true
};
}
should_poll = () =>
{
return this.state.children_should_poll;
}
render()
{
return (<Child check_polling={this.should_poll} />);
}
}
In the child, you can simply access the callback as a prop.
class Child
{
componentDidMount()
{
if (this.props.check_polling() == TRUE)
{
// do something
}
}
}
Tips, Tricks, Annoying Bullshit
You're in for a ride, but just be thankful you don't have to do this the Android Studio way.
Important things which will make your life less hell:
- Understand the React lifecycle first.
This is crucial. Take the time to understand how a component works. For example, until you know it "updates" the rendering 60x a second, it's impossible to know when something will be updated or not. Remember the naming is bad.
2. Remember all the examples are for functional programming.
You will have to translate them into the previous class-based methodology.
3. setState() is asynchronous.
This is a real bastard. You'd think this would work:
some_method()
{
if (this.state.foo == 'bar')
{
// this IS NOT "bar" yet
}
}
render()
{
this.setState ({foo: "bar"});
this.some_method()
}
It doesn't.
If you want to wait until it has returned, the second argument is a callback.
this.setState({foo: "bar"}, () => {
// this will be called AFTER the state has set
})
4. Everything breaks when the framework is updated.
Facebook and Expo have minimum respect for third parties and the package maintainers are even worse. Expect it to break if you've left it a week.
5. Try to plan components as much as you can ahead of writing code.
This gets messy, fast.
6. Don't assume you're online.
You may not be.
import NetInfo from '@react-native-community/netinfo';
constructor()
{
NetInfo.addEventListener (state => {
if (this.state.online != state.isConnected)
{
this.state.online = state.isConnected;
}
});
}
7. Don't assume network requests are successful.
APIs break all the time.
(async () => {
this.req_sent = moment();
fetch ('https://example.com/entities',
{
headers: {Accept:'application/json'},
})
.then ((response) =>
{
this.rsp_received = moment();
console.log (response.status + ' ('+this.rsp_received.diff(this.req_sent)+'ms)');
if ( !response.ok )
{
if (response.status > 400 && response.status < 500)
{
// client error
}
if (response.status >= 500)
{
// server error
}
throw Error (response.status)
}
return response.json();
})
.then ((data) =>
{
this.setState ({results: data});
})
.catch ((error) =>
{
console.log (error);
})
})();
8. Don't assume the user gave you permission.
They may not have.
choose_image ()
{
(async () => {
let permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (permission.granted === false)
{
// you got shut down
}
let result = await ImagePicker.launchImageLibraryAsync({
base64: true,
mediaTypes: ImagePicker.MediaTypeOptions.Images,
});
if (! result.cancelled )
{
this.setState ({image: result.uri, image_content: result.base64});
}
})();
}
9. Use UI libraries.
Like Bootstrap, React Native has pre-written components you can buy.
- Paper: https://reactnativepaper.com/
- Elements: https://reactnativeelements.com/
- NativeBase: https://nativebase.io/
10. Expo uses the device's timezone.
Have a strategy for dealing with dates and times. It's not as simple as it looks.