Finishing My React course
Today I used Claude.ai to help me generate a schedule to finish a React course I want to finish by June.
I want to finish 2 lessons per day until I complete the course. So, I had Claude parse the HTML page from the course to pull out the 150+ lesson titles.
Then, I asked it to generate a node.js script to import each of the lessons as a Todoist task. I ran npm i dotenv --save
, then added a .env
file with my Todoist API key (accessible in Todoist at Settings > Integrations > Developer).
I ran the script, and it worked perfectly on the first try.
Now all I need to do is diligently follow the tasks laid out for each day in Todoist, and I'll be done with the course by the end of May.
require("dotenv").config();
const axios = require("axios");
const fs = require("fs");
const path = require("path");
const csv = require("csv-parser");
// Todoist API configuration
// Replace YOUR_API_TOKEN with your actual Todoist API token
const TODOIST_API_TOKEN = process.env.TODOIST_API_TOKEN;
const TODOIST_API_URL = "https://api.todoist.com/rest/v2";
// Create a new project for the Joy of React course
async function createTodoistProject() {
try {
const response = await axios.post(
`${TODOIST_API_URL}/projects`,
{
name: "Joy of React Course",
},
{
headers: {
Authorization: `Bearer ${TODOIST_API_TOKEN}`,
"Content-Type": "application/json",
},
}
);
console.log("Project created successfully:", response.data.name);
return response.data.id;
} catch (error) {
console.error(
"Error creating project:",
error.response ? error.response.data : error.message
);
throw error;
}
}
// Parse the schedule data
function parseScheduleData() {
return new Promise((resolve, reject) => {
const results = [];
// Create a readable stream from our CSV data
const csvData = `Date,Day,Module,Lesson1,Lesson2
2025-03-01,Sat,Module 3: React Hooks,Running on Mount,On Mount Exercises
2025-03-02,Sun,Module 3: React Hooks,Cleanup,Cleanup Exercises
2025-03-03,Mon,Module 3: React Hooks,Stale Values,State-Setter Callback
2025-03-04,Tue,Module 3: React Hooks,Stale Values Exercises,Strict Mode
2025-03-05,Wed,Module 3: React Hooks,Custom Hooks,Custom Hook Exercises
2025-03-06,Thu,Module 3: React Hooks,Data Fetching,Fetching on Event
2025-03-07,Fri,Module 3: React Hooks,Fetching on Mount,Fetch Exercises
2025-03-08,Sat,Module 3: React Hooks,Async Effect Gotcha,Why React Re-Renders
2025-03-09,Sun,Module 3: React Hooks,Pure Components,The useMemo Hook
2025-03-10,Mon,Module 3: React Hooks,The useCallback Hook,Memo Exercises
2025-03-11,Tue,Module 3: React Hooks,Alternatives,Future of Memoization
2025-03-12,Wed,Module 4: Component API Design,Introduction,The Spectrum of Components
2025-03-16,Sun,Module 4: Component API Design,Spectrum Exercises,Component Libraries
2025-03-17,Mon,Module 4: Component API Design,Prop Delegation,Delegating Props Exercises
2025-03-18,Tue,Module 4: Component API Design,Conflicts,Delegating Styles
2025-03-19,Wed,Module 4: Component API Design,Forwarding Refs,ForwardRef Exercises
2025-03-20,Thu,Module 4: Component API Design,Polymorphism,Polymorphism Exercises
2025-03-21,Fri,Module 4: Component API Design,Compound Components,Slots
2025-03-22,Sat,Module 4: Component API Design,Slots Exercises,Context
2025-03-23,Sun,Module 4: Component API Design,The Problem,Syntax
2025-03-24,Mon,Module 4: Component API Design,Syntax Exercises,Provider Components
2025-03-25,Tue,Module 4: Component API Design,Performance,Modals
2025-03-25,Tue,Project: Toast Component,About This Project,Getting Started
2025-03-26,Wed,Module 4: Component API Design,Modal Exercises,Unstyled Component Libraries
2025-03-26,Wed,Project: Toast Component,Hints,Submit Your Project
2025-03-27,Thu,Module 4: Component API Design,Converting Our Modal,Library Exercises
2025-03-27,Thu,Project: Toast Component,Solution,Unguided Practice
2025-03-28,Fri,Module 5: Happy Practices,Introduction,Leveraging Keys
2025-03-29,Sat,Module 5: Happy Practices,Key Exercises,Elements Revisited
2025-03-30,Sun,Module 5: Happy Practices,Deriving State,Deriving Exercises
2025-03-31,Mon,Module 5: Happy Practices,Breaking the Rules,Lifting Content Up
2025-04-01,Tue,Module 5: Happy Practices,Lifting Content Up Exercises,Single Source of Truth
2025-04-02,Wed,Module 5: Happy Practices,SST Exercises,Principle of Least Privilege
2025-04-03,Thu,Module 5: Happy Practices,Least Privilege Exercises,useReducer
2025-04-04,Fri,Module 5: Happy Practices,Switch / Case,useReducer Exercises
2025-04-05,Sat,Module 5: Happy Practices,Immer,Immer 101
2025-04-06,Sun,Module 5: Happy Practices,Immer Exercises,The "useImmer" Hook
2025-04-07,Mon,Module 5: Happy Practices,Portals,Portal Exercises
2025-04-08,Tue,Module 5: Happy Practices,Refs Revisited,Error Boundaries
2025-04-09,Wed,Module 6: Full Stack React,Introduction,Client vs. Server Rendering
2025-04-10,Thu,Module 6: Full Stack React,Hydration,SSR Flavors
2025-04-11,Fri,Module 6: Full Stack React,React Server Components,Intro to Next.js
2025-04-12,Sat,Module 6: Full Stack React,Hello Next!,Initial Exercises
2025-04-13,Sun,Module 6: Full Stack React,Client Components,Client Components Exercises
2025-04-14,Mon,Module 6: Full Stack React,SSR Gotchas,SSR Exercises
2025-04-15,Tue,Module 6: Full Stack React,Routing,Page Transitions
2025-04-16,Wed,Module 6: Full Stack React,Programmatic Routing,Dynamic Segments
2025-04-17,Thu,Module 6: Full Stack React,Routing Exercises,Next's Metadata API
2025-04-18,Fri,Module 6: Full Stack React,Creating Building Deploying,Starting a New Project
2025-04-19,Sat,Module 6: Full Stack React,Import Aliases,Building for Production
2025-04-20,Sun,Module 6: Full Stack React,Deploying,Rendering Strategies
2025-04-21,Mon,Module 6: Full Stack React,Rendering Strategies Exercises,React Cache
2025-04-22,Tue,Module 6: Full Stack React,Suspense,An Exciting New World
2025-04-23,Wed,Module 6: Full Stack React,Graphing It Out,Understanding Suspense
2025-04-24,Thu,Module 6: Full Stack React,Putting It All Together,Using Suspense in Next
2025-04-25,Fri,Module 6: Full Stack React,Suspense Exercises,Lazy Loading
2025-04-26,Sat,Module 6: Full Stack React,Lazy Loading in Next,Baked-in Laziness
2025-04-27,Sun,Module 6: Full Stack React,Dark Mode,Module Review
2025-04-28,Mon,Bonus: Layout Animations,Intro to Framer Motion,Getting Started
2025-04-29,Tue,Bonus: Layout Animations,Basics Exercises,Layout Animations
2025-04-30,Wed,Bonus: Layout Animations,Layout Exercises,Shared Layout
2025-05-01,Thu,Bonus: Layout Animations,Working With Groups,Shared Layout Exercises
2025-05-02,Fri,Bonus: Layout Animations,One Last Exercise,Motion Accessibility
2025-05-03,Sat,Bonus: Layout Animations,Disabling Motion in Next,Troubleshooting
2025-05-04,Sun,Project: MDX Blog,About This Project,Intro to MDX
2025-05-05,Mon,Project: MDX Blog,MDX in Next.js,Getting Started
2025-05-06,Tue,Project: MDX Blog,Submit Your Project,Solution
2025-05-07,Wed,Project: MDX Blog,Suspense Investigations,Navigation Performance
2025-05-08,Thu,Project: MDX Blog,The Journey Continues,Project Review
2025-05-09,Fri,Bonus: Job Hunting Kit,Introduction,The Hiring Funnel
2025-05-10,Sat,Bonus: Job Hunting Kit,Cover Letters,Quantity vs. Quality
2025-05-11,Sun,Bonus: Job Hunting Kit,Portfolios,Building a Portfolio Site
2025-05-12,Mon,Bonus: Job Hunting Kit,Technical Interviews,FizzBuzz
2025-05-13,Tue,Bonus: Job Hunting Kit,Scroll Position,Todo App
2025-05-14,Wed,Bonus: Job Hunting Kit,Project Euler,Starfield Button
2025-05-15,Thu,Bonus: Job Hunting Kit,Multi-Step Signup,Trello Clone
2025-05-16,Fri,Bonus: Job Hunting Kit,Clicking Outside,Multi Checkbox
2025-05-17,Sat,Bonus: Job Hunting Kit,Fetch Race Condition,useEffectSkipMount
2025-05-18,Sun,Bonus: Job Hunting Kit,Retro Questions,Module Review
2025-05-19,Mon,Course Completion,Course Review,Celebration`;
// Create a readable stream from the CSV string
const Readable = require("stream").Readable;
const s = new Readable();
s.push(csvData);
s.push(null); // Indicates EOF
s.pipe(csv())
.on("data", (data) => results.push(data))
.on("end", () => {
resolve(results);
})
.on("error", (error) => {
reject(error);
});
});
}
// Create tasks in Todoist for each lesson
async function createTasks(projectId, scheduleData) {
console.log(
`Creating ${scheduleData.length * 2} tasks (2 lessons per day)...`
);
// Create a counter to track progress
let tasksCreated = 0;
// Process tasks in batches to avoid rate limiting
const batchSize = 10;
for (let i = 0; i < scheduleData.length; i += batchSize) {
const batch = scheduleData.slice(i, i + batchSize);
// Create tasks for each item in the batch
const promises = batch.flatMap((item) => {
const dateStr = item.Date; // Format: 2025-03-01
// Create two tasks - one for each lesson
return [
createTask(projectId, `${item.Module} - ${item.Lesson1}`, dateStr),
createTask(projectId, `${item.Module} - ${item.Lesson2}`, dateStr),
];
});
// Wait for all tasks in this batch to be created
await Promise.all(promises);
// Update progress
tasksCreated += batch.length * 2;
console.log(
`Progress: ${tasksCreated}/${scheduleData.length * 2} tasks created`
);
// Add a small delay between batches to avoid rate limiting
if (i + batchSize < scheduleData.length) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
console.log("All tasks created successfully!");
}
// Create a single task in Todoist
async function createTask(projectId, content, dueDate) {
try {
const response = await axios.post(
`${TODOIST_API_URL}/tasks`,
{
project_id: projectId,
content: content,
due_date: dueDate,
},
{
headers: {
Authorization: `Bearer ${TODOIST_API_TOKEN}`,
"Content-Type": "application/json",
},
}
);
return response.data;
} catch (error) {
console.error(
"Error creating task:",
error.response ? error.response.data : error.message
);
throw error;
}
}
// Main function to run the script
async function main() {
try {
console.log("Starting Joy of React schedule import to Todoist...");
// Create a new project in Todoist
const projectId = await createTodoistProject();
// Parse the schedule data
const scheduleData = await parseScheduleData();
// Create tasks for each lesson
await createTasks(projectId, scheduleData);
console.log("Import completed successfully!");
} catch (error) {
console.error("Error running the script:", error);
}
}
// Run the script
main();