Skip to content

Commit 11317bf

Browse files
committed
Implement Question Details Page
1 parent 281e584 commit 11317bf

File tree

8 files changed

+937
-10
lines changed

8 files changed

+937
-10
lines changed

app/(root)/questions/[id]/page.tsx

Lines changed: 156 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,164 @@
1+
import TagCard from "@/components/cards/TagCard";
2+
import { Preview } from "@/components/editor/Preview";
3+
import Metric from "@/components/Metric";
4+
import UserAvatar from "@/components/UserAvatar";
5+
import ROUTES from "@/constants/routes";
6+
import { formatNumber, getTimeStamp } from "@/lib/utils";
7+
import Link from "next/link";
18
import React from "react";
29

10+
const sampleQuestion = {
11+
id: "q123",
12+
title: "How to improve React app performance?",
13+
content: `### Question
14+
15+
I'm looking for tips and best practices to enhance the performance of a React application. I have a moderately complex app with multiple components, and I've noticed some performance bottlenecks. What should I focus on?
16+
17+
#### What I've Tried:
18+
- Lazy loading components
19+
- Using React.memo on some components
20+
- Managing state with React Context API
21+
22+
#### Issues:
23+
- The app still lags when rendering large lists.
24+
- Switching between pages feels sluggish.
25+
- Sometimes, re-renders happen unexpectedly.
26+
27+
#### Key Areas I Need Help With:
28+
1. Efficiently handling large datasets.
29+
2. Reducing unnecessary re-renders.
30+
3. Optimizing state management.
31+
32+
Here is a snippet of my code that renders a large list. Maybe I'm doing something wrong here:
33+
34+
\`\`\`js
35+
import React, { useState, useMemo } from "react";
36+
37+
const LargeList = ({ items }) => {
38+
const [filter, setFilter] = useState("");
39+
40+
// Filtering items dynamically
41+
const filteredItems = useMemo(() => {
42+
return items.filter((item) => item.includes(filter));
43+
}, [items, filter]);
44+
45+
return (
46+
<div>
47+
<input
48+
type="text"
49+
value={filter}
50+
onChange={(e) => setFilter(e.target.value)}
51+
placeholder="Filter items"
52+
/>
53+
<ul>
54+
{filteredItems.map((item, index) => (
55+
<li key={index}>{item}</li>
56+
))}
57+
</ul>
58+
</div>
59+
);
60+
};
61+
62+
export default LargeList;
63+
\`\`\`
64+
65+
#### Questions:
66+
1. Is using \`useMemo\` the right approach here, or is there a better alternative?
67+
2. Should I implement virtualization for the list? If yes, which library would you recommend?
68+
3. Are there better ways to optimize state changes when dealing with user input and dynamic data?
69+
70+
Looking forward to your suggestions and examples!
71+
72+
**Tags:** React, Performance, State Management
73+
`,
74+
createdAt: "2025-01-15T12:34:56.789Z",
75+
upvotes: 42,
76+
downvotes: 3,
77+
views: 1234,
78+
answers: 5,
79+
tags: [
80+
{ _id: "tag1", name: "React" },
81+
{ _id: "tag2", name: "Node" },
82+
{ _id: "tag3", name: "PostgreSQL" },
83+
],
84+
author: {
85+
_id: "u456",
86+
name: "Jane Doe",
87+
image: "/avatars/jane-doe.png",
88+
},
89+
};
90+
391
const QuestionDetails = async ({ params }: RouteParams) => {
492
const { id } = await params;
593

6-
return <div>Question Page: {id}</div>;
94+
const { author, createdAt, answers, views, tags, content } = sampleQuestion;
95+
96+
return (
97+
<>
98+
<div className="flex-start w-full flex-col">
99+
<div className="flex w-full flex-col-reverse justify-between">
100+
<div className="flex items-center justify-start gap-1">
101+
<UserAvatar
102+
id={author._id}
103+
name={author.name}
104+
className="size-[22px]"
105+
fallbackClassName="text-[10px]"
106+
/>
107+
<Link href={ROUTES.PROFILE(author._id)}>
108+
<p className="paragraph-semibold text-dark300_light700">
109+
{author.name}
110+
</p>
111+
</Link>
112+
</div>
113+
114+
<div className="flex justify-end">
115+
<p>Votes</p>
116+
</div>
117+
</div>
118+
119+
<h2 className="h2-semibold text-dark200_light900 mt-3.5 w-full">
120+
{sampleQuestion.title}
121+
</h2>
122+
</div>
123+
124+
<div className="mb-8 mt-5 flex flex-wrap gap-4">
125+
<Metric
126+
imgUrl="/icons/clock.svg"
127+
alt="clock icon"
128+
value={` asked ${getTimeStamp(new Date(createdAt))}`}
129+
title=""
130+
textStyles="small-regular text-dark400_light700"
131+
/>
132+
<Metric
133+
imgUrl="/icons/message.svg"
134+
alt="message icon"
135+
value={answers}
136+
title=""
137+
textStyles="small-regular text-dark400_light700"
138+
/>
139+
<Metric
140+
imgUrl="/icons/eye.svg"
141+
alt="eye icon"
142+
value={formatNumber(views)}
143+
title=""
144+
textStyles="small-regular text-dark400_light700"
145+
/>
146+
</div>
147+
148+
<Preview content={content} />
149+
150+
<div className="mt-8 flex flex-wrap gap-2">
151+
{tags.map((tag: Tag) => (
152+
<TagCard
153+
key={tag._id}
154+
_id={tag._id as string}
155+
name={tag.name}
156+
compact
157+
/>
158+
))}
159+
</div>
160+
</>
161+
);
7162
};
8163

9164
export default QuestionDetails;

components/Metric.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { cn } from "@/lib/utils";
12
import Image from "next/image";
23
import Link from "next/link";
34
import React from "react";
@@ -11,6 +12,7 @@ interface Props {
1112
textStyles: string;
1213
imgStyles?: string;
1314
isAuthor?: boolean;
15+
titleStyles?: string;
1416
}
1517

1618
const Metric = ({
@@ -22,6 +24,7 @@ const Metric = ({
2224
textStyles,
2325
imgStyles,
2426
isAuthor,
27+
titleStyles,
2528
}: Props) => {
2629
const metricContent = (
2730
<>
@@ -36,11 +39,11 @@ const Metric = ({
3639
<p className={`${textStyles} flex items-center gap-1`}>
3740
{value}
3841

39-
<span
40-
className={`small-regular line-clamp-1 ${isAuthor ? "max-sm:hidden" : ""}`}
41-
>
42-
{title}
43-
</span>
42+
{title ? (
43+
<span className={cn(`small-regular line-clamp-1`, titleStyles)}>
44+
{title}
45+
</span>
46+
) : null}
4447
</p>
4548
</>
4649
);

components/UserAvatar.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ import React from "react";
55
import ROUTES from "@/constants/routes";
66

77
import { Avatar, AvatarFallback } from "./ui/avatar";
8+
import { cn } from "@/lib/utils";
89

910
interface Props {
1011
id: string;
1112
name: string;
1213
imageUrl?: string | null;
1314
className?: string;
15+
fallbackClassName?: string;
1416
}
1517

16-
const UserAvatar = ({ id, name, imageUrl, className = "h-9 w-9" }: Props) => {
18+
const UserAvatar = ({
19+
id,
20+
name,
21+
imageUrl,
22+
className = "h-9 w-9",
23+
fallbackClassName,
24+
}: Props) => {
1725
const initials = name
1826
.split(" ")
1927
.map((word: string) => word[0])
@@ -34,7 +42,12 @@ const UserAvatar = ({ id, name, imageUrl, className = "h-9 w-9" }: Props) => {
3442
quality={100}
3543
/>
3644
) : (
37-
<AvatarFallback className="primary-gradient font-space-grotesk font-bold tracking-wider text-white">
45+
<AvatarFallback
46+
className={cn(
47+
"primary-gradient font-space-grotesk font-bold tracking-wider text-white",
48+
fallbackClassName
49+
)}
50+
>
3851
{initials}
3952
</AvatarFallback>
4053
)}

components/cards/QuestionCard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const QuestionCard = ({
4545
href={ROUTES.PROFILE(author._id)}
4646
textStyles="body-medium text-dark400_light700"
4747
isAuthor
48+
titleStyles="max-sm:hidden"
4849
/>
4950

5051
<div className="flex items-center gap-3 max-sm:flex-wrap max-sm:justify-start">

components/editor/Preview.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Code } from "bright";
2+
import { MDXRemote } from "next-mdx-remote/rsc";
3+
4+
Code.theme = {
5+
light: "github-light",
6+
dark: "github-dark",
7+
lightSelector: "html.light",
8+
};
9+
10+
export const Preview = ({ content }: { content: string }) => {
11+
const formattedContent = content.replace(/\\/g, "").replace(/&#x20;/g, "");
12+
13+
return (
14+
<section className="markdown prose grid break-words">
15+
<MDXRemote
16+
source={formattedContent}
17+
components={{
18+
pre: (props) => (
19+
<Code
20+
{...props}
21+
lineNumbers
22+
className="shadow-light-200 dark:shadow-dark-200"
23+
/>
24+
),
25+
}}
26+
/>
27+
</section>
28+
);
29+
};

lib/utils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,13 @@ export const getTimeStamp = (createdAt: Date) => {
7474
}
7575
return "just now";
7676
};
77+
78+
export const formatNumber = (number: number) => {
79+
if (number >= 1000000) {
80+
return (number / 1000000).toFixed(1) + "M";
81+
} else if (number >= 1000) {
82+
return (number / 1000).toFixed(1) + "K";
83+
} else {
84+
return number.toString();
85+
}
86+
};

0 commit comments

Comments
 (0)