Mecenate React-Native Feed App
Mecenate Mobile Feed is a scalable React Native application focused on real-time social interactions and modern mobile application architecture. The project was built using Expo, TypeScript, MobX, React Query, and WebSockets while emphasizing performance, responsive mobile UX, and maintainable state management patterns.
Screenshots

Features
- Real-time WebSocket updates
- Infinite scrolling feed
- Optimistic likes and interactions
- Paid/free post system
- Comment synchronization
- Skeleton loading states
- Pull-to-refresh
- Query prefetching
- Cached detail pages
- Reusable UI components
- Mobile-first architecture
Tech Stack
- React Native
- Expo
- TypeScript
- MobX
- TanStack Query
- Expo Router
- WebSockets
- Expo Blur
- React Native Reanimated
The application includes real-time feed updates, infinite scrolling, optimistic interactions, live comment synchronization, and a paid/free content system designed to simulate production-grade social media experiences.
A major focus of the project was combining server-state management with real-time WebSocket events while maintaining smooth mobile performance and predictable UI updates.
Code Highlight
One of the key focuses of this project was synchronizing WebSocket events with React Query cache updates for instant UI feedback.
import { makeAutoObservable } from "mobx";
import { QueryClient } from "@tanstack/react-query";
import { authStore } from "./authStore";
import { Posts } from "@/entities/Posts";
import { Post } from "@/entities/Post";
import { Comments } from "@/entities/Comments";
export class SocketStore {
ws?: WebSocket;
constructor(private queryClient: QueryClient) {
makeAutoObservable(this);
}
connect() {
if (this.ws) return;
this.ws = new WebSocket(
`wss://k8s.mectest.ru/test-app/ws?token=${authStore.token}`
);
this.ws.onopen = () => {
console.log("WS connected");
this.queryClient.invalidateQueries({ queryKey: ["posts"] });
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleEvent(data);
console.log("WS Event: ", event.data);
};
this.ws.onclose = () => {
console.log("WS disconnected");
this.ws = undefined;
};
this.ws.onerror = (error) => {
console.log("WS error", error);
};
}
disconnect() {
this.ws?.close();
this.ws = undefined;
}
handleEvent(data: any) {
switch (data.type) {
case "like_updated":
this.onLikeUpdated(data);
break;
case "comment_added":
this.onCommentAdded(data);
break;
}
}
onLikeUpdated(data: any) {
const { postId, likesCount } = data;
this.queryClient.setQueriesData(["posts", "all"], (oldData: any) => {
if (!oldData) return oldData;
return {
...oldData,
pages: oldData.pages.map((page: Posts) => ({
...page,
posts: page.posts.map((post: Post) =>
post.id === postId ? { ...post, likesCount } : post
),
})),
};
});
this.queryClient.setQueryData(["post", postId], (oldPost: Post | any) => {
if (!oldPost) return oldPost;
return {
...oldPost,
likesCount,
};
});
}
onCommentAdded(data: any) {
const { postId, comment } = data;
this.queryClient.setQueryData(["comments", postId], (oldData: any) => {
if (!oldData) return oldData;
const exists = oldData.pages.some((page: Comments) =>
page.comments.some((c) => c.id === comment.id)
);
if (exists) return oldData;
return {
...oldData,
pages: oldData.pages.map((page: Comments, index: number) =>
index === 0
? { ...page, comments: [comment, ...page.comments] }
: page
),
};
});
this.queryClient.setQueryData(["post", postId], (oldData: any) => {
if (!oldData) return oldData;
return {
...oldData,
commentsCount: oldData.commentsCount + 1,
};
});
this.queryClient.setQueriesData({ queryKey: ["posts"] }, (oldData: any) => {
if (!oldData) return oldData;
return {
...oldData,
pages: oldData.pages.map((page: Posts) => ({
...page,
posts: page.posts.map((post: Post) =>
post.id === postId
? {
...post,
commentsCount: post.commentsCount + 1,
}
: post
),
})),
};
});
}
}