mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-09 00:03:12 +00:00
8.5 KiB
8.5 KiB
Hook Migration Guide
Overview
This guide explains how to migrate your existing components from direct context usage to the new reactive hook system. The new hooks eliminate business logic from components and provide better performance through selective re-renders.
Migration Strategy
Phase 1: Install New Hooks (✅ Complete)
- Core data hooks:
useForumData,useAuth,useUserDisplay - Derived hooks:
useCell,usePost,useCellPosts,usePostComments,useUserVotes - Action hooks:
useForumActions,useUserActions,useAuthActions - Utility hooks:
usePermissions,useNetworkStatus,useForumSelectors
Phase 2: Component Migration (Next Steps)
Before and After Examples
PostCard Component Migration
❌ Before (Business Logic in Component)
const PostCard: React.FC<PostCardProps> = ({ post }) => {
const { getCellById, votePost, isVoting } = useForum();
const { isAuthenticated, currentUser } = useAuth();
const cell = getCellById(post.cellId);
// ❌ Business logic in component
const score = post.upvotes.length - post.downvotes.length;
const userUpvoted = currentUser
? post.upvotes.some(vote => vote.author === currentUser.address)
: false;
const userDownvoted = currentUser
? post.downvotes.some(vote => vote.author === currentUser.address)
: false;
const handleVote = async (e: React.MouseEvent, isUpvote: boolean) => {
e.preventDefault();
if (!isAuthenticated) return;
await votePost(post.id, isUpvote);
};
return (
// JSX with manual calculations
);
};
✅ After (Pure Presentation)
const PostCard: React.FC<PostCardProps> = ({ post }) => {
// ✅ All data comes pre-computed from hooks
const { forumActions } = useForumActions();
const permissions = usePermissions();
const userVotes = useUserVotes();
// ✅ No business logic - just pure data access
const userVoteType = userVotes.getPostVoteType(post.id);
const canVote = permissions.canVote;
const handleVote = async (e: React.MouseEvent, isUpvote: boolean) => {
e.preventDefault();
// ✅ All validation and logic handled in hook
await forumActions.votePost(post.id, isUpvote);
};
return (
// ✅ JSX uses pre-computed data
<div>
<span>{post.voteScore}</span> {/* Already computed */}
<button
disabled={!canVote || forumActions.isVoting}
className={userVoteType === 'upvote' ? 'active' : ''}
>
Upvote
</button>
</div>
);
};
PostDetail Component Migration
❌ Before
const PostDetail = () => {
const { posts, getCommentsByPost, votePost, voteComment } = useForum();
const { currentUser, verificationStatus } = useAuth();
// ❌ Manual data fetching and filtering
const post = posts.find(p => p.id === postId);
const postComments = getCommentsByPost(post.id);
const visibleComments = postComments.filter(comment => !comment.moderated);
// ❌ Permission checking in component
const canVote =
verificationStatus === 'verified-owner' ||
currentUser?.ensDetails ||
currentUser?.ordinalDetails;
// ❌ Vote status checking
const isPostUpvoted =
currentUser &&
post.upvotes.some(vote => vote.author === currentUser.address);
};
✅ After
const PostDetail = () => {
// ✅ Get pre-computed post data
const post = usePost(postId);
const comments = usePostComments(postId, { includeModerated: false });
const permissions = usePermissions();
const forumActions = useForumActions();
if (!post) return <div>Post not found</div>;
return (
<div>
<h1>{post.title}</h1>
<p>Score: {post.voteScore}</p> {/* Pre-computed */}
<button
disabled={!permissions.canVote}
className={post.userUpvoted ? 'active' : ''}
onClick={() => forumActions.votePost(post.id, true)}
>
Upvote ({post.upvotes.length})
</button>
{comments.comments.map(comment => (
<CommentCard key={comment.id} comment={comment} />
))}
</div>
);
};
Migration Checklist
For Each Component:
-
Identify Current Context Usage
- Replace
useForum()with specific hooks - Replace
useAuth()with enhanceduseAuth() - Replace
useUserDisplay()with enhanced version
- Replace
-
Extract Business Logic
- Remove vote calculations → use
post.voteScore,post.userUpvoted - Remove permission checks → use
usePermissions() - Remove data filtering → use hook options
- Remove user display logic → use
useUserDisplay()
- Remove vote calculations → use
-
Use Appropriate Hooks
- Single items:
useCell(),usePost() - Collections:
useCellPosts(),usePostComments() - Actions:
useForumActions(),useUserActions() - Utilities:
usePermissions(),useNetworkStatus()
- Single items:
-
Update Action Handlers
- Replace direct context methods with hook actions
- Remove manual loading states (hooks provide them)
- Remove manual error handling (hooks handle it)
Common Patterns
Data Access
// ❌ Before
const { posts, getCellById } = useForum();
const cellPosts = posts.filter(p => p.cellId === cellId);
const cell = getCellById(cellId);
// ✅ After
const cell = useCell(cellId);
const cellPosts = useCellPosts(cellId, { sortBy: 'relevance' });
Permission Checking
// ❌ Before
const canVote =
verificationStatus === 'verified-owner' && currentUser?.ordinalDetails;
// ✅ After
const { canVote, voteReason } = usePermissions();
Vote Status
// ❌ Before
const userUpvoted =
currentUser && post.upvotes.some(vote => vote.author === currentUser.address);
// ✅ After
const userVotes = useUserVotes();
const userUpvoted = userVotes.getPostVoteType(post.id) === 'upvote';
Actions with Loading States
// ❌ Before
const { votePost, isVoting } = useForum();
// ✅ After
const { votePost, isVoting } = useForumActions();
Benefits After Migration
Performance
- ✅ Selective re-renders (only affected components update)
- ✅ Memoized computations (vote scores, user status, etc.)
- ✅ Efficient data access patterns
Developer Experience
- ✅ Type-safe hook interfaces
- ✅ Built-in loading states and error handling
- ✅ Consistent permission checking
- ✅ No business logic in components
Maintainability
- ✅ Centralized business logic in hooks
- ✅ Reusable data transformations
- ✅ Easier testing (hooks can be tested independently)
- ✅ Clear separation of concerns
Testing Strategy
Hook Testing
// Test hooks independently
import { renderHook } from '@testing-library/react';
import { useForumData } from '@/hooks';
test('useForumData returns computed vote scores', () => {
const { result } = renderHook(() => useForumData());
expect(result.current.postsWithVoteStatus[0].voteScore).toBeDefined();
});
Component Testing
// Components become easier to test (pure presentation)
import { render } from '@testing-library/react';
import PostCard from './PostCard';
test('PostCard displays vote score', () => {
const mockPost = { id: '1', voteScore: 5, userUpvoted: true };
render(<PostCard post={mockPost} />);
expect(screen.getByText('5')).toBeInTheDocument();
});
Rollback Plan
If issues arise during migration:
-
Immediate Rollback: Import legacy hooks
import { useForum as useLegacyForum } from '@/contexts/useForum'; -
Gradual Migration: Use both systems temporarily
const legacyData = useLegacyForum(); const newData = useForumData(); const data = newData.isInitialLoading ? legacyData : newData; -
Component-by-Component: Migrate one component at a time
Next Steps
- Start with Simple Components: Begin with components that have minimal business logic
- Test Thoroughly: Ensure reactive updates work correctly
- Monitor Performance: Verify improved render performance
- Update Documentation: Keep component documentation current
- Remove Legacy Code: After full migration, remove old context dependencies
Example Migration Order
- ✅ PostCard - Simple display component
- ✅ CommentCard - Similar patterns to PostCard
- CellList - Collection display
- PostDetail - Complex component with multiple data sources
- PostList - Full CRUD operations
- Header - Authentication and user display
- ActivityFeed - Complex data aggregation
This migration will transform your codebase into a clean, reactive system where components are purely presentational and all business logic is centralized in reusable hooks.