This is only one of the first apps I've built with Node, React and TypeScript. I was wondering what could be made better. This is at this point the best I've been able to come up with, but I'm not sure if there's code here that could be difficult for another developer to read, could be improved, or anything else really. Or even stuff that could be added to this app.
I'd appreciate any kind of feedback on the entirety of my code.
Backend
app.ts
import { mySqlConnect } from "./utils/mySqlConnect"
const NUM_GAMES = 50
const app = express()
app.use(cors())
const db = mysql.createConnection(mySqlConnect)
db.connect(function (err) {
if (err) {
console.error("failed connection:" + err.stack)
return
}
console.log("Connected.")
})
app.get("/api/games", (req, res) => {
const teamId = req.query.team_id
const offset = req.query.offset
let sql = ` SELECT * FROM games
WHERE team = '${teamId}'
ORDER BY date DESC
LIMIT ${NUM_GAMES} OFFSET ${offset}
`
db.query(sql, (err, results) => {
if (err) throw err
const response = results.map((game: { payload: string }) => JSON.parse(game.payload))
res.send(response)
})
})
mySqlConnect.ts
import * as dotenv from "dotenv"
dotenv.config()
const config = {
host: process.env.HOSTNAME,
user: process.env.USERNAME,
password: process.env.PASSWORD,
port: process.envPORT,
database: process.env.DB_NAME,
}
type RemoveUndefinedFields<T> = {
[P in keyof T]: Exclude<T[P], undefined>
}
export const mysqlConnect = config as RemoveUndefinedFields<typeof config>
Frontend
App.js
const App = () => {
return (
<div>
<GlobalStyle />
<NavBar />
<Games />
</div>
)
}
Games.tsx
const OFFSET_GAMES = 10
const TEAM_ID = "team_id_here"
const Games = () => {
const [games, setGames] = useState<Game[]>([])
const [numFetches, setNumFetches] = useState(OFFSET_GAMES)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
setIsLoading(prev => !prev)
fetch(`https://localhost:3402/api/games/?team_id=${TEAM_ID}&offset=${numFetches}`)
.then((res) => res.json())
.then((res) => {
setGames((prevGames) => [...prevGames, ...res])
setIsLoading(prev => !prev)
})
}, [numFetches])
const fetchGames = () => setNumFetches((prev) => prev + OFFSET_GAMES)
const displayGames = () => {
const datesShown: string[] = []
return games.map(game => {
const date = parseDate(game.date)
let showDate = true
if (datesShown.includes(date)) {
showDate = false
} else {
datesShown.push(date)
}
return (
<>
{showDate && <Date date={date}/>}
<Game
id={game.id}
game_type={game.game_type}
date={game.date}
observations={game.observations}
/>
</>
)
})
}
return (
<div>
{games.length ? (
<GamesContainer>
{displayGames()}
<Button
text="Load more games"
onClick={fetchGames}
isLoading={isLoading}
/>
</GamesContainer>
) : null}
{(!games.length || isLoading) && <Loader text={"Loading games..."} />}
</div>
)
}
GameCard.tsx
export type Game = {
id: string
game_type: string
date: string
observations?: string[] | undefined
}
const GameCard: React.FC<Game> = ({
id,
game_type,
date,
observations,
}) => {
const gameMap = new Map(Object.entries(gameDictionary))
return (
<GameCardContainer key={id}>
<h3>{parseDate(date)}</h3>
<h4>{gameMap.get(game_type)}</h4>
{observations?.map((obs) => (
<h5>{obs}</h5>
))}
</GameCardContainer>
)
}
gameDictionary.ts
export const gameDictionary = {
football: "Football match, the derby!",
basketball: "Basketball game. Looking for MJ?",
tennis: "Nadal X Federer? Tennis match!",
}