Most React Native projects don't start messy.
They become messy over time.
A screen that once handled simple UI gradually starts doing everything:
- API calls
- state management
- data transformation
- navigation logic
I recently refactored one such screen, and the difference was bigger than I expected.
The Problem: Everything in One Place
The original screen looked something like this:
- API calls inside useEffect
- Multiple useState hooks
- Inline business logic
- Conditional rendering everywhere
It worked - but it wasn't scalable.
The issues were clear:
- Hard to read
- Hard to test
- Hard to reuse logic
Before: A Typical "Messy" Structure
const Screen = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
setLoading(true);
const res = await apiCall();
const filtered = res.filter(item => item.active);
setData(filtered);
setLoading(false);
};
return (
<>
{loading ? <Loader /> : data.map(item => <Item key={item.id} />)}
</>
);
};
Everything is tightly coupled.
The Goal
I wanted to:
- Separate UI from logic
- Make the code reusable
- Improve readability
- Prepare for scaling
After: Introducing MVVM
I split the logic into:
- View (UI)
- ViewModel (logic)
- Service (API layer)
ViewModel (logic layer)
export const useScreenViewModel = () => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true);
const res = await apiCall();
const filtered = res.filter(item => item.active);
setData(filtered);
setLoading(false);
};
return {
data,
loading,
fetchData,
};
};
View (clean UI)
const Screen = () => {
const { data, loading, fetchData } = useScreenViewModel();
useEffect(() => {
fetchData();
}, []);
if (loading) return <Loader />;
return data.map(item => <Item key={item.id} />);
};
What Changed
- Cleaner Components UI is now focused only on rendering.
- Reusable Logic ViewModel can be reused or tested independently.
- Easier Debugging Logic is centralized and predictable.
- Better Team Collaboration Different developers can work on UI and logic separately.
What Didn't Change
The functionality stayed exactly the same.
This is important:
Refactoring is about improving structure - not changing behavior.
Lessons Learned
- Messy code is usually a result of growth, not bad intentions
- Small architectural decisions compound over time
- Separation of concerns is not optional in scaling apps
When to Use This Approach
You don't need MVVM for every screen.
Use it when:
- Logic starts growing
- State becomes complex
- Multiple developers are involved
Final Thoughts
React Native gives you flexibility but that flexibility can become chaos if you don't define structure.
Refactoring this screen reminded me of one thing:
Clean architecture is not about perfection it's about making future changes easier.
Top comments (0)