Caching with teamplay
CSSX can cache style computations to improve rendering performance. This is particularly useful when components re-render frequently but their styles don't change.
Note: Caching currently requires the teamplay library. In future versions, CSSX may include built-in caching that works independently.
How It Works
Without caching, CSSX computes styles on every render:
import { View, Text } from 'react-native'
function Card({ title }) {
// Style computation runs on EVERY render
return (
<View styleName="card">
<Text>{title}</Text>
</View>
)
styl`
.card
padding 16px
background white
`
}
With caching enabled, CSSX memoizes the results:
- First render: computes and caches the style object
- Subsequent renders: returns the cached result instantly
- Cache invalidates automatically when:
- CSS variable values change
- Screen dimensions change (for media queries)
- The
styleName value changes
Setup
Step 1: Install teamplay
Step 2: Wrap Components with observer
For caching to work, components using styleName must be wrapped with observer:
import { observer } from 'teamplay'
import { styl } from 'cssxjs'
import { View, Text } from 'react-native'
const Card = observer(function Card({ title, children }) {
return (
<View styleName="card">
<Text styleName="title">{title}</Text>
<View styleName="content">{children}</View>
</View>
)
styl`
.card
padding 16px
background white
border-radius 8px
.title
font-size 18px
margin-bottom 12px
.content
color #666
`
})
That's it! The Babel transform automatically detects observer and enables the cached runtime.
Automatic Detection
CSSX automatically enables caching when it detects observer imported from:
No additional configuration is needed.
Manual Configuration
You can force caching behavior in your Babel config:
// babel.config.js
module.exports = {
presets: [
['cssxjs/babel', {
cache: 'teamplay' // Always use teamplay caching
}]
]
}
What Gets Cached
The caching system stores:
- Computed style objects for each unique
styleName combination
- Results of CSS variable substitutions
- Media query evaluations
Cache Key Components
Each cache entry is keyed by:
- The
styleName value
- Current CSS variable values (if styles use
var())
- Current screen dimensions (if styles use media queries)
- Any inline style props
Automatic Invalidation
The cache invalidates when reactive dependencies change:
import { variables } from 'cssxjs'
import { observer } from 'teamplay'
import { View, Text } from 'react-native'
const ThemedCard = observer(function ThemedCard() {
// Cache invalidates when --card-bg changes
return (
<View styleName="card">
<Text>Themed content</Text>
</View>
)
styl`
.card
background var(--card-bg, white)
`
})
// Later: changing this automatically re-renders affected components
variables['--card-bg'] = '#f0f0f0'
Caching is most beneficial when:
- Components re-render frequently (lists, animations, form inputs)
- Styles are complex (many classes, nested selectors)
- Multiple components share the same styles
Example with a list:
import { observer } from 'teamplay'
import { styl } from 'cssxjs'
import { View, Text } from 'react-native'
const ListItem = observer(function ListItem({ item, isSelected }) {
return (
<View styleName={['item', { selected: isSelected }]}>
<Text styleName="name">{item.name}</Text>
<Text styleName="price">{item.price}</Text>
</View>
)
styl`
.item
flex-direction row
justify-content space-between
padding 12px 16px
border-bottom-width 1px
border-bottom-color #eee
&.selected
background #e3f2fd
.name
font-weight 500
.price
color #666
`
})
// Rendering 1000 items benefits significantly from caching
function ProductList({ products, selectedId }) {
return (
<View>
{products.map(item => (
<ListItem
key={item.id}
item={item}
isSelected={item.id === selectedId}
/>
))}
</View>
)
}
Using with startupjs
If you're using the startupjs framework, caching is automatically configured. Just import observer from startupjs:
import { observer, styl } from 'startupjs'
import { View, Text } from 'react-native'
export default observer(function MyComponent() {
return (
<View styleName="box">
<Text>Content</Text>
</View>
)
styl`
.box
padding 16px
`
})
Best Practices
Wrap All Styled Components
For consistent behavior, wrap any component that uses styleName:
import { Pressable, Text } from 'react-native'
// Good: observer wrapper enables caching
const Button = observer(function Button({ children }) {
return (
<Pressable styleName="button">
<Text styleName="text">{children}</Text>
</Pressable>
)
styl`.button { padding 12px 24px } .text { color white }`
})
// Without observer: no caching, styles compute every render
function Button({ children }) {
return (
<Pressable styleName="button">
<Text styleName="text">{children}</Text>
</Pressable>
)
styl`.button { padding 12px 24px } .text { color white }`
}
Debugging
To verify caching is working, you can check if components are using the teamplay runtime. In development, the imported runtime path will be one of:
cssxjs/runtime/react-native-teamplay (React Native with caching)
cssxjs/runtime/web-teamplay (Web with caching)
cssxjs/runtime/react-native (React Native without caching)
cssxjs/runtime/web (Web without caching)
Next Steps