Skip to content

Commit 3a1329d

Browse files
authored
chore: clean up code and add ttl based component caching (#6)
* chore: link library * chore: update mock component * chore: upgrade react navigation library version * chore: link server component library * chore: change fallback, loading and error component type * chore: remove commented code * chore: add ttl based component caching
1 parent dc873f6 commit 3a1329d

File tree

11 files changed

+322
-27
lines changed

11 files changed

+322
-27
lines changed

example/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"react": "18.2.0",
1616
"react-native": "0.73.6",
1717
"react-native-safe-area-context": "^4.9.0",
18-
"react-native-screens": "^3.29.0"
18+
"react-native-screens": "^3.29.0",
19+
"react-native-server-component": "link:../src"
1920
},
2021
"devDependencies": {
2122
"@babel/core": "^7.20.0",

example/src/screens/HomeScreen.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback } from 'react';
2-
import { View, StyleSheet, Text, Pressable } from 'react-native';
2+
import { View, StyleSheet, Text } from 'react-native';
33
import { ServerComponent } from 'react-native-server-component';
44

55
export default function HomeScreen({ navigation }) {
@@ -14,10 +14,18 @@ export default function HomeScreen({ navigation }) {
1414
[navigation]
1515
);
1616

17+
const FallbackComponent = () => {
18+
return (
19+
<View>
20+
<Text> Fallback Component </Text>
21+
</View>
22+
);
23+
};
1724
return (
1825
<View style={styles.container}>
1926
<ServerComponent
2027
source={{ uri: 'http://10.0.2.2:8080' }}
28+
fallbackComponent={<FallbackComponent />}
2129
onAction={handleAction}
2230
/>
2331
{/* <Pressable onPress={onPress}>

example/yarn.lock

+7
Original file line numberDiff line numberDiff line change
@@ -5377,9 +5377,16 @@ __metadata:
53775377
react-native: 0.73.6
53785378
react-native-safe-area-context: ^4.9.0
53795379
react-native-screens: ^3.29.0
5380+
react-native-server-component: "link:../src"
53805381
languageName: unknown
53815382
linkType: soft
53825383

5384+
"react-native-server-component@link:../src::locator=react-native-server-component-example%40workspace%3A.":
5385+
version: 0.0.0-use.local
5386+
resolution: "react-native-server-component@link:../src::locator=react-native-server-component-example%40workspace%3A."
5387+
languageName: node
5388+
linkType: soft
5389+
53835390
"react-native@npm:0.73.6":
53845391
version: 0.73.6
53855392
resolution: "react-native@npm:0.73.6"

server/Mocks/TranspiledExample.js

+154-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,154 @@
1-
"use strict";var _interopRequireDefault=require("@babel/runtime/helpers/interopRequireDefault");var _typeof=require("@babel/runtime/helpers/typeof");Object.defineProperty(exports,"__esModule",{value:true});exports["default"]=void 0;var _slicedToArray2=_interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));var _react=_interopRequireWildcard(require("react"));var _reactNative=require("react-native");var _this=void 0,_jsxFileName="/Users/kunal.chavhan/workplace/react-native-server-component/server/Mocks/ExampleServerComponent.tsx";function _getRequireWildcardCache(e){if("function"!=typeof WeakMap)return null;var r=new WeakMap(),t=new WeakMap();return(_getRequireWildcardCache=function _getRequireWildcardCache(e){return e?t:r;})(e);}function _interopRequireWildcard(e,r){if(!r&&e&&e.__esModule)return e;if(null===e||"object"!=_typeof(e)&&"function"!=typeof e)return{"default":e};var t=_getRequireWildcardCache(r);if(t&&t.has(e))return t.get(e);var n={__proto__:null},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var u in e)if("default"!==u&&{}.hasOwnProperty.call(e,u)){var i=a?Object.getOwnPropertyDescriptor(e,u):null;i&&(i.get||i.set)?Object.defineProperty(n,u,i):n[u]=e[u];}return n["default"]=e,t&&t.set(e,n),n;}var ExampleServerComponent=function ExampleServerComponent(_ref){var onAction=_ref.onAction;var _useState=(0,_react.useState)(''),_useState2=(0,_slicedToArray2["default"])(_useState,2),catFact=_useState2[0],setCatFact=_useState2[1];var onPress=(0,_react.useCallback)(function(){if(onAction){onAction('NAVIGATE',{route:'DetailsScreen'});}},[onAction]);(0,_react.useEffect)(function(){fetch('https://catfact.ninja/fact').then(function(resp){return resp.json();}).then(function(json){return json.fact;}).then(function(fact){return setCatFact(fact);});},[]);return _react["default"].createElement(_reactNative.View,{style:styles.container,__self:_this,__source:{fileName:_jsxFileName,lineNumber:24,columnNumber:5}},_react["default"].createElement(_reactNative.Text,{style:styles.hello,__self:_this,__source:{fileName:_jsxFileName,lineNumber:25,columnNumber:7}}," Hello Server Component"),_react["default"].createElement(_reactNative.Text,{style:styles.catFactsTitle,__self:_this,__source:{fileName:_jsxFileName,lineNumber:26,columnNumber:7}}," Cat Facts "),_react["default"].createElement(_reactNative.Text,{style:styles.facts,__self:_this,__source:{fileName:_jsxFileName,lineNumber:27,columnNumber:7}}," ",catFact," "),_react["default"].createElement(_reactNative.Pressable,{onPress:onPress,__self:_this,__source:{fileName:_jsxFileName,lineNumber:28,columnNumber:7}},_react["default"].createElement(_reactNative.View,{style:styles.button,__self:_this,__source:{fileName:_jsxFileName,lineNumber:29,columnNumber:9}},_react["default"].createElement(_reactNative.Text,{style:styles.text,__self:_this,__source:{fileName:_jsxFileName,lineNumber:30,columnNumber:11}}," ","Navigation"," "))));};var styles=_reactNative.StyleSheet.create({container:{flex:1,width:'100%',justifyContent:'center',padding:20},hello:{color:'red',fontWeight:'bold'},catFactsTitle:{marginTop:16,color:'blue',fontWeight:'bold'},facts:{marginTop:10,color:'black',fontWeight:'400'},text:{color:'black',fontWeight:'400',alignContent:'center',textAlign:'center'},button:{height:30,width:100,marginTop:20,borderRadius:3,backgroundColor:'#65A765',justifyContent:'center',alignContent:'center',alignSelf:'center'}});var _default=exports["default"]=ExampleServerComponent;
1+
'use strict';
2+
var _interopRequireDefault = require('@babel/runtime/helpers/interopRequireDefault');
3+
var _typeof = require('@babel/runtime/helpers/typeof');
4+
Object.defineProperty(exports, '__esModule', { value: true });
5+
exports['default'] = void 0;
6+
var _slicedToArray2 = _interopRequireDefault(
7+
require('@babel/runtime/helpers/slicedToArray')
8+
);
9+
var _react = _interopRequireWildcard(require('react'));
10+
var _reactNative = require('react-native');
11+
var _this = void 0,
12+
_jsxFileName =
13+
'/Users/kunal.chavhan/workplace/react-native-server-component/server/Mocks/ExampleServerComponent.tsx';
14+
function _getRequireWildcardCache(e) {
15+
if ('function' != typeof WeakMap) return null;
16+
var r = new WeakMap(),
17+
t = new WeakMap();
18+
return (_getRequireWildcardCache = function _getRequireWildcardCache(e) {
19+
return e ? t : r;
20+
})(e);
21+
}
22+
function _interopRequireWildcard(e, r) {
23+
if (!r && e && e.__esModule) return e;
24+
if (null === e || ('object' != _typeof(e) && 'function' != typeof e))
25+
return { default: e };
26+
var t = _getRequireWildcardCache(r);
27+
if (t && t.has(e)) return t.get(e);
28+
var n = { __proto__: null },
29+
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
30+
for (var u in e)
31+
if ('default' !== u && {}.hasOwnProperty.call(e, u)) {
32+
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
33+
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
34+
}
35+
return (n['default'] = e), t && t.set(e, n), n;
36+
}
37+
var ExampleServerComponent = function ExampleServerComponent(_ref) {
38+
var onAction = _ref.onAction;
39+
var _useState = (0, _react.useState)(''),
40+
_useState2 = (0, _slicedToArray2['default'])(_useState, 2),
41+
catFact = _useState2[0],
42+
setCatFact = _useState2[1];
43+
var onPress = (0, _react.useCallback)(
44+
function () {
45+
if (onAction) {
46+
onAction('NAVIGATE', { route: 'DetailsScreen' });
47+
}
48+
},
49+
[onAction]
50+
);
51+
(0, _react.useEffect)(function () {
52+
fetch('https://catfact.ninja/fact')
53+
.then(function (resp) {
54+
return resp.json();
55+
})
56+
.then(function (json) {
57+
return json.fact;
58+
})
59+
.then(function (fact) {
60+
return setCatFact(fact);
61+
});
62+
}, []);
63+
return _react['default'].createElement(
64+
_reactNative.View,
65+
{
66+
style: styles.container,
67+
__self: _this,
68+
__source: { fileName: _jsxFileName, lineNumber: 24, columnNumber: 5 },
69+
},
70+
_react['default'].createElement(
71+
_reactNative.Text,
72+
{
73+
style: styles.hello,
74+
__self: _this,
75+
__source: { fileName: _jsxFileName, lineNumber: 25, columnNumber: 7 },
76+
},
77+
' Hello Server Component'
78+
),
79+
_react['default'].createElement(
80+
_reactNative.Text,
81+
{
82+
style: styles.catFactsTitle,
83+
__self: _this,
84+
__source: { fileName: _jsxFileName, lineNumber: 26, columnNumber: 7 },
85+
},
86+
' Cat Facts '
87+
),
88+
_react['default'].createElement(
89+
_reactNative.Text,
90+
{
91+
style: styles.facts,
92+
__self: _this,
93+
__source: { fileName: _jsxFileName, lineNumber: 27, columnNumber: 7 },
94+
},
95+
' ',
96+
catFact,
97+
' '
98+
),
99+
_react['default'].createElement(
100+
_reactNative.Pressable,
101+
{
102+
onPress: onPress,
103+
__self: _this,
104+
__source: { fileName: _jsxFileName, lineNumber: 28, columnNumber: 7 },
105+
},
106+
_react['default'].createElement(
107+
_reactNative.View,
108+
{
109+
style: styles.button,
110+
__self: _this,
111+
__source: { fileName: _jsxFileName, lineNumber: 29, columnNumber: 9 },
112+
},
113+
_react['default'].createElement(
114+
_reactNative.Text,
115+
{
116+
style: styles.text,
117+
__self: _this,
118+
__source: {
119+
fileName: _jsxFileName,
120+
lineNumber: 30,
121+
columnNumber: 11,
122+
},
123+
},
124+
' ',
125+
'Navigation',
126+
' '
127+
)
128+
)
129+
)
130+
);
131+
};
132+
var styles = _reactNative.StyleSheet.create({
133+
container: { flex: 1, width: '100%', justifyContent: 'center', padding: 20 },
134+
hello: { color: 'red', fontWeight: 'bold' },
135+
catFactsTitle: { marginTop: 16, color: 'blue', fontWeight: 'bold' },
136+
facts: { marginTop: 10, color: 'black', fontWeight: '400' },
137+
text: {
138+
color: 'black',
139+
fontWeight: '400',
140+
alignContent: 'center',
141+
textAlign: 'center',
142+
},
143+
button: {
144+
height: 30,
145+
width: 100,
146+
marginTop: 20,
147+
borderRadius: 3,
148+
backgroundColor: '#65A765',
149+
justifyContent: 'center',
150+
alignContent: 'center',
151+
alignSelf: 'center',
152+
},
153+
});
154+
var _default = (exports['default'] = ExampleServerComponent);

src/@types/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ export type RSCActions = 'NAVIGATE' | 'IO' | 'STATE_CHANGE';
1313
export type RSCProps = {
1414
readonly global?: any;
1515
readonly source: RSCSource;
16-
readonly fallbackComponent?: () => JSX.Element;
17-
readonly loadingComponent?: () => JSX.Element;
18-
readonly errorComponent?: () => JSX.Element;
16+
readonly fallbackComponent?: JSX.Element;
17+
readonly loadingComponent?: JSX.Element;
18+
readonly errorComponent?: JSX.Element;
1919
readonly onError?: (error: Error) => void;
2020
readonly navigationRef?: React.Ref<any>;
2121
readonly onAction?: (

src/cache/componentCache.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1+
export interface CacheItem<T> {
2+
// React component
3+
value: T;
4+
// TTL in milliseconds
5+
// Default is -1, which means cache will persist in app session
6+
// Cache will be purged for any new requests, based on ttl and timestamp values
7+
ttl: number;
8+
// Timestamp at which cache was created
9+
timestamp: number;
10+
}
11+
112
class ComponentCache<T> {
2-
// TODO implement TTL based cache bursting
313
private static instance: ComponentCache<any>;
4-
private cache: Map<string, T | null>;
14+
private cache: Map<string, CacheItem<T> | null>;
515

616
constructor() {
7-
this.cache = new Map<string, T>();
17+
this.cache = new Map<string, CacheItem<T>>();
818
}
919

1020
public static getInstance<T>(): ComponentCache<T> {
@@ -14,18 +24,34 @@ class ComponentCache<T> {
1424
return ComponentCache.instance;
1525
}
1626

17-
set(key: string, value: T | null): void {
27+
set(key: string, value: CacheItem<T> | null): void {
1828
this.cache.set(key, value);
1929
}
2030

2131
get(key: string): T | null {
2232
const component = this.cache.get(key);
2333
if (component) {
24-
return component;
34+
return component.value;
2535
}
2636
return null;
2737
}
2838

39+
getTTL(key: string): number {
40+
const value = this.cache.get(key);
41+
if (value && value.ttl) {
42+
return value.ttl;
43+
}
44+
return -1;
45+
}
46+
47+
getTimeStamp(key: string): number {
48+
const value = this.cache.get(key);
49+
if (value && value.timestamp) {
50+
return value.timestamp;
51+
}
52+
return -1;
53+
}
54+
2955
delete(key: string): void {
3056
this.cache.delete(key);
3157
}

src/component/ServerComponent.tsx

+19-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ export default function RSC({
55
source,
66
openRSC,
77
fallbackComponent,
8-
loadingComponent = () => <React.Fragment />,
9-
errorComponent = () => <React.Fragment />,
8+
loadingComponent = <React.Fragment />,
9+
errorComponent = <React.Fragment />,
1010
...extras
1111
}: RSCProps): JSX.Element {
1212
const [ServerComponent, setServerComponent] =
@@ -31,11 +31,25 @@ export default function RSC({
3131

3232
const FallbackComponent = React.useCallback((): JSX.Element => {
3333
if (fallbackComponent) {
34-
return fallbackComponent();
34+
return fallbackComponent;
3535
}
3636
return <></>;
3737
}, [fallbackComponent]);
3838

39+
const ErrorComponent = React.useCallback((): JSX.Element => {
40+
if (errorComponent) {
41+
return errorComponent;
42+
}
43+
return <></>;
44+
}, [errorComponent]);
45+
46+
const LoadingComponent = React.useCallback((): JSX.Element => {
47+
if (loadingComponent) {
48+
return loadingComponent;
49+
}
50+
return <></>;
51+
}, [loadingComponent]);
52+
3953
if (typeof ServerComponent === 'function') {
4054
return (
4155
<React.Fragment>
@@ -45,7 +59,7 @@ export default function RSC({
4559
</React.Fragment>
4660
);
4761
} else if (error) {
48-
return errorComponent();
62+
return <ErrorComponent />;
4963
}
50-
return loadingComponent();
64+
return <LoadingComponent />;
5165
}

0 commit comments

Comments
 (0)