Skip to content

Commit

Permalink
chore: clean up code and add ttl based component caching (#6)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kunalchavhan authored Apr 15, 2024
1 parent dc873f6 commit 3a1329d
Show file tree
Hide file tree
Showing 11 changed files with 322 additions and 27 deletions.
3 changes: 2 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"react": "18.2.0",
"react-native": "0.73.6",
"react-native-safe-area-context": "^4.9.0",
"react-native-screens": "^3.29.0"
"react-native-screens": "^3.29.0",
"react-native-server-component": "link:../src"
},
"devDependencies": {
"@babel/core": "^7.20.0",
Expand Down
10 changes: 9 additions & 1 deletion example/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { View, StyleSheet, Text, Pressable } from 'react-native';
import { View, StyleSheet, Text } from 'react-native';
import { ServerComponent } from 'react-native-server-component';

export default function HomeScreen({ navigation }) {
Expand All @@ -14,10 +14,18 @@ export default function HomeScreen({ navigation }) {
[navigation]
);

const FallbackComponent = () => {
return (
<View>
<Text> Fallback Component </Text>
</View>
);
};
return (
<View style={styles.container}>
<ServerComponent
source={{ uri: 'http://10.0.2.2:8080' }}
fallbackComponent={<FallbackComponent />}
onAction={handleAction}
/>
{/* <Pressable onPress={onPress}>
Expand Down
7 changes: 7 additions & 0 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5377,9 +5377,16 @@ __metadata:
react-native: 0.73.6
react-native-safe-area-context: ^4.9.0
react-native-screens: ^3.29.0
react-native-server-component: "link:../src"
languageName: unknown
linkType: soft

"react-native-server-component@link:../src::locator=react-native-server-component-example%40workspace%3A.":
version: 0.0.0-use.local
resolution: "react-native-server-component@link:../src::locator=react-native-server-component-example%40workspace%3A."
languageName: node
linkType: soft

"react-native@npm:0.73.6":
version: 0.73.6
resolution: "react-native@npm:0.73.6"
Expand Down
155 changes: 154 additions & 1 deletion server/Mocks/TranspiledExample.js
Original file line number Diff line number Diff line change
@@ -1 +1,154 @@
"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;
'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);
6 changes: 3 additions & 3 deletions src/@types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export type RSCActions = 'NAVIGATE' | 'IO' | 'STATE_CHANGE';
export type RSCProps = {
readonly global?: any;
readonly source: RSCSource;
readonly fallbackComponent?: () => JSX.Element;
readonly loadingComponent?: () => JSX.Element;
readonly errorComponent?: () => JSX.Element;
readonly fallbackComponent?: JSX.Element;
readonly loadingComponent?: JSX.Element;
readonly errorComponent?: JSX.Element;
readonly onError?: (error: Error) => void;
readonly navigationRef?: React.Ref<any>;
readonly onAction?: (
Expand Down
36 changes: 31 additions & 5 deletions src/cache/componentCache.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
export interface CacheItem<T> {
// React component
value: T;
// TTL in milliseconds
// Default is -1, which means cache will persist in app session
// Cache will be purged for any new requests, based on ttl and timestamp values
ttl: number;
// Timestamp at which cache was created
timestamp: number;
}

class ComponentCache<T> {
// TODO implement TTL based cache bursting
private static instance: ComponentCache<any>;
private cache: Map<string, T | null>;
private cache: Map<string, CacheItem<T> | null>;

constructor() {
this.cache = new Map<string, T>();
this.cache = new Map<string, CacheItem<T>>();
}

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

set(key: string, value: T | null): void {
set(key: string, value: CacheItem<T> | null): void {
this.cache.set(key, value);
}

get(key: string): T | null {
const component = this.cache.get(key);
if (component) {
return component;
return component.value;
}
return null;
}

getTTL(key: string): number {
const value = this.cache.get(key);
if (value && value.ttl) {
return value.ttl;
}
return -1;
}

getTimeStamp(key: string): number {
const value = this.cache.get(key);
if (value && value.timestamp) {
return value.timestamp;
}
return -1;
}

delete(key: string): void {
this.cache.delete(key);
}
Expand Down
24 changes: 19 additions & 5 deletions src/component/ServerComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export default function RSC({
source,
openRSC,
fallbackComponent,
loadingComponent = () => <React.Fragment />,
errorComponent = () => <React.Fragment />,
loadingComponent = <React.Fragment />,
errorComponent = <React.Fragment />,
...extras
}: RSCProps): JSX.Element {
const [ServerComponent, setServerComponent] =
Expand All @@ -31,11 +31,25 @@ export default function RSC({

const FallbackComponent = React.useCallback((): JSX.Element => {
if (fallbackComponent) {
return fallbackComponent();
return fallbackComponent;
}
return <></>;
}, [fallbackComponent]);

const ErrorComponent = React.useCallback((): JSX.Element => {
if (errorComponent) {
return errorComponent;
}
return <></>;
}, [errorComponent]);

const LoadingComponent = React.useCallback((): JSX.Element => {
if (loadingComponent) {
return loadingComponent;
}
return <></>;
}, [loadingComponent]);

if (typeof ServerComponent === 'function') {
return (
<React.Fragment>
Expand All @@ -45,7 +59,7 @@ export default function RSC({
</React.Fragment>
);
} else if (error) {
return errorComponent();
return <ErrorComponent />;
}
return loadingComponent();
return <LoadingComponent />;
}
Loading

0 comments on commit 3a1329d

Please sign in to comment.