Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

WIP

Searcher for HN whos hiring posts

HonoJs doesn't support render for async components so the style is off for the home page.

And the state changes don't register in html rendering so thinking of using react instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource https://esm.sh/react */
import React, { useCallback, useEffect, useReducer, useRef, useState } from "https://esm.sh/react";
import { hydrateRoot } from "https://esm.sh/react-dom/client";
import { renderToString } from "https://esm.sh/react-dom/server";
import { hnSearch } from "https://esm.town/v/stevekrouse/hnSearch";
import About from "https://esm.town/v/vawogbemi/whoIsHiringAbout";
function App() {
const tabs = { "/": "Home", "/about": "About" };
const [activeTab, setActiveTab] = useState("/");
const [showScrollTop, setShowScrollTop] = useState(false);
const [selectedComment, setSelectedComment] = useState(null);
const reducer = (state: {
query: string;
story: string;
page: number;
loading: boolean;
hasMore: boolean;
}, action: { type: string; value: string | boolean | number }) => {
switch (action.type) {
case "query":
return { ...state, query: action.value as string, page: 1 };
case "story":
return { ...state, story: action.value as string, page: 1 };
case "page":
return { ...state, page: action.value as number };
case "loading":
return { ...state, loading: action.value as boolean };
case "hasMore":
return { ...state, hasMore: action.value as boolean };
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, {
query: "",
story: "",
page: 1,
loading: true,
hasMore: true,
});
const [stories, setStories] = useState<{ date: string; objectID: string }[]>([]);
const [comments, setComments] = useState<{ author: string; comment_text: string; objectID: string }[]>([]);
const observer = useRef<IntersectionObserver>();
const lastCommentElementRef = useCallback((node: HTMLDivElement) => {
if (state.loading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && state.hasMore) {
dispatch({ type: "page", value: state.page + 1 });
}
});
if (node) observer.current.observe(node);
}, [state.loading, state.hasMore]);
const fetchStories = async () => {
try {
const response = await fetch("/api/stories");
if (!response.ok) throw new Error("Failed to fetch dates");
const data = await response.json();
setStories(data);
dispatch({ type: "story", value: data[0].objectID });
}
catch (err) {
console.error("Error fetching stories:", err);
}
};
const fetchComments = async (query: string, story: string, page: number) => {
try {
dispatch({ type: "loading", value: true });
const response = await fetch(`/api/comments?query=${encodeURIComponent(query)}&story=${story}&page=${page}`);
if (!response.ok) throw new Error("Failed to fetch comments");
const data = await response.json();
setComments(prevComments => page === 1 ? data.hits : [...prevComments, ...data.hits]);
dispatch({ type: "hasMore", value: data.hits.length > 0 });
dispatch({ type: "loading", value: false });
} catch (err) {
console.error("Error fetching comments:", err);
dispatch({ type: "loading", value: false });
}
};
const handleSearch = (newQuery = state.query) => {
setComments([]);
dispatch({ type: "page", value: 1 });
dispatch({ type: "loading", value: true });
dispatch({ type: "hasMore", value: true });
dispatch({ type: "query", value: newQuery });
fetchComments(newQuery, state.story, 1);
};
useEffect(() => {
fetchStories();
vawogbemi-whoishiring.web.val.run
September 19, 2024