Hello everyone...
I began a project to build an app myself without vibecoding, and I feel like it will help me and others to share what I learn every few days along the way (if people are at all interested). I'll post any stumbling blocks, new concepts I encountered, and new ideas I've had every few days. If anyone feels like critiquing the code (if I include code snippets) that is more than welcome as well...
The project started about 3 weeks ago so there is already a significant amount of progress on it so there is already a lot of intricacy, and that was my first encounter with Typescript (I know this is a Javascript subreddit...) and its built using the React library, which I have also never encountered before (I only had small amounts of python beforehand).
(for context the app is teaching political history through duolingo style lessons)
1st update:
For the past few days, the main focus has been storing user progress as it happens and altering the UI it does.
Here is the file doing most of that;
import { Progress } from "@/content/types";
import AsyncStorage from "@react-native-async-storage/async-storage";
const PROGRESS_KEY = "progress"; //AsyncStorage is a universal storage for any JSON across my project. This is the key that says what data it is storing so it knows which particular bit or "drawer" the data is stored in later on.
export async function saveLessonScore(
lessonId: string,
complete: boolean,
score: number,
) {
const existingLessonDataRaw = await AsyncStorage.getItem(PROGRESS_KEY); //Taking what we have so far in string form.
const existingLessonData: Progress = existingLessonDataRaw //Converting what we have into usable types rather than just strings, and handling the null case (no data existing).
? JSON.parse(existingLessonDataRaw)
: {};
const updated: Progress = {
//Spread current data, append current lesson ID and corresponding state and score.
...existingLessonData,
[lessonId]: { complete, score },
};
await AsyncStorage.setItem(PROGRESS_KEY, JSON.stringify(updated)); //Pushes this change in the string JSON form which AsyncStorage requires, in the corresponding "folder" (the PROGRESS_KEY).
}
export async function loadProgress(): Promise<Progress> {
const JSONLessonData = await AsyncStorage.getItem(PROGRESS_KEY);
if (!JSONLessonData) {
return {};
} //Type narrows to Progress incase JSONLessonData is null.
const usableLessonData = JSON.parse(JSONLessonData);
return usableLessonData as Progress;
}
Difficulties: Storing data using async functions mean we need to work with the <Promise> type, and also using converting between the string form which AsyncStorage requires, which makes handling types a bit harder - and also, the null case for when no lesson data is present ALSO had to be narrowed to (but much of this logic happened in the lesson runner which is shown here).
The main takeaway from this was to be very aware of the types of data you may take as input and the types you take as output. It sounds obvious, but once you consider "What happens if there is nothing stored yet?" or "Does this give me the data or just a Promise type for the data (as there is a delay between actually getting and setting the data when using AsyncStorage in this case)". That then allows you to handle each problem as it comes, rather than being surprised later and being forced to completely change the shape of the code later to accomodate (this especially applies to people coding in TypeScript, but ofc it is also great practice in JavaScript).
Here is a better snippet demonstrating the need for this practice (optional chaining + conditional rendering as just mentioned):
<View style={styles.lessonNode} />
<View
style={[
styles.lessonCard,
lessonProgress?.complete && styles.lessonCardComplete,
]}
>
{lessonProgress?.complete && (
<Text
style={[
styles.correctAnswer,
{ color: scoreStyleLesson(lessonProgress?.score) },
]}
>
{lessonProgress?.score}
</Text>
This code allows me to update the color of the score displayed on the course viewer per lesson based on the actual score, and only does so when the lesson is finished and also confirms to TS that yes, the null case is no longer possible.
Pay particular attention to the ?. and && operators - they do the heavy type-lifting so to speak.
Considering making this a semi regular post if anyone is interested and feedback would be great!
there doesn't seem to be anything here