Managing Data Updates Wisely
How It Works Under the Hood
The Ray framework runs on the miniapp dual-thread architecture. When a React component's state changes, the underlying flow is:
Component calls setState / useState setter
↓
React executes reconciliation (diff computation)
↓
Ray sends changed data to the view layer via setData
↓
View layer updates the DOM and re-renders
Every React state update ultimately triggers an underlying setData call. Optimizing how state is updated means optimizing setData usage.
Optimization Principles
Data unrelated to rendering should not be placed in state or useState. Doing so triggers unnecessary component re-renders and underlying setData calls.
// ❌ Avoid: non-render data in state
import React, { useState } from 'react';
export default function Home() {
const [list, setList] = useState([]);
const [timer, setTimer] = useState(null); // non-render data
const [cache, setCache] = useState({}); // non-render data
const [requestId, setRequestId] = useState(''); // non-render data
}
// ✅ Recommended: use useRef for non-render data
import React, { useState, useRef } from 'react';
export default function Home() {
const [list, setList] = useState([]);
const timerRef = useRef(null); // does not trigger re-render
const cacheRef = useRef({}); // does not trigger re-render
const requestIdRef = useRef(''); // does not trigger re-render
}
2. Reduce Unnecessary Re-renders
State changes in a React component cause the component and its children to re-render. Avoid triggering unnecessary renders.
Use React.memo to memoize child components:
// ❌ Avoid: child re-renders every time parent updates
function ListItem({ item }) {
return <View><Text>{item.name}</Text></View>;
}
// ✅ Recommended: use memo to prevent unnecessary re-renders
const ListItem = React.memo(function ListItem({ item }) {
return <View><Text>{item.name}</Text></View>;
});
Use useMemo to cache computed values:
// ❌ Avoid: recomputes on every render
export default function Home() {
const [list, setList] = useState([]);
const sortedList = list.sort((a, b) => a.name.localeCompare(b.name));
return <View>...</View>;
}
// ✅ Recommended: cache with useMemo
import React, { useState, useMemo } from 'react';
export default function Home() {
const [list, setList] = useState([]);
const sortedList = useMemo(
() => [...list].sort((a, b) => a.name.localeCompare(b.name)),
[list] // only recompute when list changes
);
return <View>...</View>;
}
3. Throttle High-Frequency State Updates
High-frequency state updates (e.g., from scroll or input events) cause frequent re-renders and setData calls.
// ❌ Avoid: update state on every scroll event
export default function Home() {
const [scrollTop, setScrollTop] = useState(0);
const handleScroll = (e) => {
setScrollTop(e.detail.scrollTop);
};
return <ScrollView onScroll={handleScroll}>...</ScrollView>;
}
// ✅ Recommended: use throttle to limit update frequency
import React, { useState, useRef, useCallback } from 'react';
export default function Home() {
const [scrollTop, setScrollTop] = useState(0);
const throttleRef = useRef(null);
const handleScroll = useCallback((e) => {
if (throttleRef.current) return;
throttleRef.current = setTimeout(() => {
setScrollTop(e.detail.scrollTop);
throttleRef.current = null;
}, 100);
}, []);
return <ScrollView onScroll={handleScroll}>...</ScrollView>;
}
4. Batch Multiple State Updates
React 18+ automatically batches multiple state updates, but may not batch automatically in async callbacks:
// React auto-batches synchronous updates
const handleClick = () => {
setName('New name'); // does not render immediately
setAge(25); // does not render immediately
setStatus('active'); // all three merged into one render
};
// For closely related states, consider useReducer or merging into one state object
// ❌ Multiple separate related states
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
const [error, setError] = useState(null);
// ✅ Merge into one state object
const [state, setState] = useState({
loading: false,
data: null,
error: null,
});
const fetchData = async () => {
setState(prev => ({ ...prev, loading: true }));
try {
const res = await api.getData();
setState({ loading: false, data: res, error: null });
} catch (err) {
setState({ loading: false, data: null, error: err });
}
};
5. Stop Updates in Background Pages
Stop state updates when the page is not visible to avoid wasting resources:
import React, { useState, useEffect, useRef } from 'react';
import { usePageEvent } from '@ray-js/ray';
export default function Home() {
const [data, setData] = useState(null);
const isActiveRef = useRef(true);
const timerRef = useRef(null);
usePageEvent('onShow', () => {
isActiveRef.current = true;
startPolling();
});
usePageEvent('onHide', () => {
isActiveRef.current = false;
if (timerRef.current) {
clearInterval(timerRef.current);
}
});
const startPolling = () => {
timerRef.current = setInterval(async () => {
if (isActiveRef.current) {
const res = await fetchLatestData();
setData(res);
}
}, 3000);
};
useEffect(() => {
startPolling();
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
return <View>...</View>;
}
Checklist