-
Notifications
You must be signed in to change notification settings - Fork 0
Exploring options for bidirectional state management
The goal is to minimize as much template code as possible. The ideal scenario is only having to declare state variables once, leaving our code handle the rest. I.e.
const [total, setTotal] = useState<number>(0); // ts
↑ ↓
@State var total: Double // swift
We have a variable, total
, that we want to keep synchronized with Swift variable, @State var total: Double
.
Let's start with introducing a basic setup for this functionality:
Using the following code, we will update the Swift state variable upon changes made to the React state variable.
Declare total
WKScriptMessageHandler at global scope.
declare global {
interface Window {
webkit: {
messageHandlers: {
total: {
postMessage: (value: number) => void;
};
};
};
}
}
export {};
Notify WKScriptMessageHandler of update made to React state variable upon value change of total
.
import React, { useEffect, useState } from 'react';
const MyComponent: React.FC = () => {
const [total, setTotal] = useState<number>(0);
// on change of total, notify Swift of new value
useEffect(() => {
notifySwiftOfChange(total)
}, [total]);
const updateTotal = (newTotal: number) => {
setTotal(newTotal);
};
// notify Swift of new value
const notifySwiftOfChange = (value: number) => {
if (window.webkit?.messageHandlers?.total) {
window.webkit.messageHandlers.total.postMessage(value);
} else {
console.warn("Message handler 'total' is not available.");
}
};
return (
<div>
<p>Total: {total}</p>
<button onClick={() => updateTotal(total + 1)}>Increment</button>
</div>
);
};
export default MyComponent;
Shrink this boilerplate code down to:
const [total, setTotal] = useCustomState<number>(0);
Using the following code, we will update the React state variable upon changes made to the Swift state variable.
import React, { useEffect, useState } from 'react';
// declare exposed type
declare global {
interface Window {
updateTotal: (value: number) => void;
}
}
const MyComponent: React.FC = () => {
const [total, setTotal] = useState<number>(0);
const updateTotal = (newTotal: number) => {
setTotal(newTotal);
};
// expose function to browser
useEffect(() => {
window.updateTotal = updateTotal;
}, []);
return (
<div>
<p>Total: {total}</p>
<button onClick={() => updateTotal(total + 1)}>Increment</button>
</div>
);
};
export default MyComponent;
Shrink this boilerplate code down to a custom hook:
useExpose(updateTotal);
- Completed: see useExpose.ts
Not sure if any of this is possible; ideally, we're looking to satisfy at least one of two requirements:
- Solutions that add a layer to vanilla React without affecting the underlying functionality
- Solutions that do not add any new lines of code
Potential solutions could include:
Note: Decorators are limited in tsx.
@expose
const [total, setTotal] = useState<number>(0);
@expose
const updateTotal = (newTotal: number) => {
setTotal(newTotal);
};
Point to updateTotal
in the useCustomState
hook declaration.
const [total, setTotal] = useCustomState<number>(0, updateTotal);
const updateTotal = (newTotal: number) => {
setTotal(newTotal);
};
expose
would be equivalent to const
, but with the functionality described above.
expose [total, setTotal] = useState<number>(0);
expose updateTotal = (newTotal: number) => {
setTotal(newTotal);
};