Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
We could also put some funky types on it. I prefer to return the value from async functions so it would look something like

TypeScript code:
function publish (metrics: TheThing) {
  return async function (input: ExampleObjType): Promise<MetricsLogger> {
    return await metrics.someChainedFuncsThatPublishAMetric(input.exampleKey);
  };
}

// Called as
await publish(metrics)({ exampleKey: 'example value' });
// Opposed to
await metrics.someChainedFuncsThatPublishAMetric('example value');

Nolgthorn fucked around with this message at 14:39 on Jan 23, 2024

Adbot
ADBOT LOVES YOU

VagueRant
May 24, 2012
Unless I'm misunderstanding even more :smith:, we all seem to be forgetting the metricScope() func from the library, and the fact I'm not explicitly defining the 'metrics' value above any of the code?

To reference their docs:
TypeScript code:
const { metricScope, Unit, StorageResolution } = require("aws-embedded-metrics");

const myFunc = metricScope(metrics =>
  async (param1: string, param2: number) => {
    metrics.putDimensions({ Service: "Aggregator" });
    metrics.putMetric("ProcessingLatency", 100, Unit.Milliseconds, StorageResolution.Standard);
    metrics.putMetric("Memory.HeapUsed", 1600424.0, Unit.Bytes, StorageResolution.High);
    metrics.setProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
    // ...
  });

await myFunc('myParam', 0);

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
Oh I get it.

I misred the code. Dunno what you're working with but assuming it's a callback because it's got a delay you can make it async yourself.

TypeScript code:
async function getMetrics () {
  return new Promise((res) => metricScope(res));
}

const metrics = await getMetrics();

metrics.putDimensions({ Service: "Aggregator" });
metrics.putMetric("ProcessingLatency", 100, Unit.Milliseconds, StorageResolution.Standard);
metrics.putMetric("Memory.HeapUsed", 1600424.0, Unit.Bytes, StorageResolution.High);
metrics.setProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
// ...
Or, inline even if you wanted to.

TypeScript code:
const metrics = await new Promise((res) => metricScope(res));
Apologies if I gummed up this thread with a bunch of nonsense.

prisoner of waffles
May 8, 2007

Ah! well a-day! what evil looks
Had I from old and young!
Instead of the cross, the fishmech
About my neck was hung.

VagueRant posted:

Unless I'm misunderstanding even more :smith:, we all seem to be forgetting the metricScope() func from the library, and the fact I'm not explicitly defining the 'metrics' value above any of the code?

To reference their docs:
TypeScript code:
const { metricScope, Unit, StorageResolution } = require("aws-embedded-metrics");

const myFunc = metricScope(metrics =>
  async (param1: string, param2: number) => {
    metrics.putDimensions({ Service: "Aggregator" });
    metrics.putMetric("ProcessingLatency", 100, Unit.Milliseconds, StorageResolution.Standard);
    metrics.putMetric("Memory.HeapUsed", 1600424.0, Unit.Bytes, StorageResolution.High);
    metrics.setProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
    // ...
  });

await myFunc('myParam', 0);

quote:

Unless I'm misunderstanding even more :smith:, we all seem to be forgetting the metricScope() func from the library, and the fact I'm not explicitly defining the 'metrics' value above any of the code?

`metrics` is not a variable, it's an argument to a function. `metricScope` takes a function as its argument and `metrics` is the argument of that function.

Here comes the "this sounds like scolding but it's actually meant to make you more effective at asking for help in the future": if aws-embedded-metrics has types in its documentation, you should read them; if you want people in a forum to pay attention to the specifics of a library, you should 1. read the docs yourself 2. indicate in your post that you read them and what you didn't understand 3. post a link to the docs, like this one. Because I'm an idiot, I'm going to guess how all this works before I read the docs, which are here.

Now, here's your free help.

TypeScript code:
// Purely based on working backwards from the code you posted, I've pulled out of my rear end one implementation of what metricScope might be doing.
// This type signature I also pulled out of my rear end, but it's probably right though who knows that they called MetricsRecorder.
function metricScope(metricsCb: (metrics: MetricsRecorder) => async (name: string, value: number) => void) => async (name: string, value: number) {
	// Get a metrics recorder from somewhere
	const metrics = pullMetricsOutOfMyAss();
	return async function(name: string, value: number) {
		return metricsCb(metrics); // <-- this return value is an async function that takes `name` and `value` and returns nothing.
	}
	// Or it might have a different implementation where it gets a fresh metrics recorder every time you call the return value:
/*
	return async function(name: string, value: number) {
		const metrics = pullMetricsOutOfMyAss();
		return metricsCb(metrics); // <-- this return value is an async function that takes `name` and `value` and returns nothing.
	}
*/
}
// So how would you write myFunc as a plain function?
function myFunc(m: MetricsRecorder): async (name: string, value: number) => void {
	// inside the body of this function, we already have a `MetricsRecorder` value in `m`, so!
	// functions returned from in here can use that value of `m`.
	return async function functionThatDoesStuff(name: string, value: number) {
		metrics.putDimensions({ Service: "Aggregator" });
		metrics.putMetric("ProcessingLatency", 100, Unit.Milliseconds, StorageResolution.Standard);
		metrics.putMetric("Memory.HeapUsed", 1600424.0, Unit.Bytes, StorageResolution.High);
		metrics.setProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
		// etc.
		// presumably you'll actually want to use those two parameters.
	}
}

const myFuncReadyToUse: async(name: string, value: number) = metricScope(myFunc);
So, perusing the docs rapidly I see:

- the type of `metrics` is actually `MetricsLogger`
- `metricScope` is a decorator function to which you pass a function that expects a `MetricsLogger` and returns any async function; as a decorator, it wraps the inner function so that it has a freshly created `MetricsLogger` and so that when the inner function is done, the `.flush()` method will be called on that `MetricsLogger`.

Basically, `metricScope` looks confusing, but it's there to reduce boilerplate, assuming you have lots of little chunks of code that need to get a MetricsLogger. If you don't use `metricScope`, you have to remember to get the logger and flush the logger in each one of those chunks of code.

TypeScript code:
// How to do it without `metricScope`:
const { createMetricsLogger, Unit, StorageResolution } = require("aws-embedded-metrics");

const myFunc = async (param1: string, param2: number) => {
  const metrics = createMetricsLogger();
  metrics.putDimensions({ Service: "Aggregator" });
  metrics.putMetric("ProcessingLatency", 100, Unit.Milliseconds, StorageResolution.Standard);
  metrics.putMetric("Memory.HeapUsed", 1600424.0, Unit.Bytes, StorageResolution.High);
  metrics.setProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
  // ...
  await metrics.flush();
};

await myFunc('myParam', 0);

// How to do the exact same thing with metricScope:
const { metricScope, Unit, StorageResolution } = require("aws-embedded-metrics");

const myFunc = metricScope(metrics =>
  async (param1: string, param2: number) => {
    metrics.putDimensions({ Service: "Aggregator" });
    metrics.putMetric("ProcessingLatency", 100, Unit.Milliseconds, StorageResolution.Standard);
    metrics.putMetric("Memory.HeapUsed", 1600424.0, Unit.Bytes, StorageResolution.High);
    metrics.setProperty("RequestId", "422b1569-16f6-4a03-b8f0-fe3fd9b100f8");
    // ...
  });

await myFunc('myParam', 0);

pentium166
Oct 15, 2012
Fully unwrapping the initial code sample, you end up with something like this:

TypeScript code:
function myMetricsHandler(metrics) {
  // metrics is provided by the function that calls myMetricsHandler,
  // in this case "metricScope"
  return async function butts(input) {
    return metrics.someChainedFuncsThatPublishAMetric(input.exampleKey);
  }
}

const myPublish = metricScope(myMetricsHandler);
await myPublish({exampleKey: 5})


// Where in the AWS library, metricScope might be defined as something like:
function metricScope(userFunction) {
  const metrics = new MetricsService();
  return async function (args) {
    // first, get the async function returned by myMetricsHandler
    const buttsFunction = userFunction(metrics);
    // then call that async function with the final arguments ({exampleKey: 5}).
    // butts will have access to metrics by virtue of being defined inside
    // the closure created by myMetricsHandler
    return buttsFunction(args);
  }
}

echinopsis
Apr 13, 2004

by Fluffdaddy
e:dw

echinopsis fucked around with this message at 09:11 on Jan 24, 2024

fuf
Sep 12, 2004

haha
I have a pretty simple React app that I made with create-react-app and want to host somewhere.

A lot of the stuff I'm looking at, like https://www.digitalocean.com/products/app-platform, assumes that you want to pull from a github repo and then run the build process on the production server itself, but I'm struggling to see why this is an advantage.

If I run "npm run build" locally then what I end up with in the /build folder is just a static site that I can host anywhere, right? It feels easier to just deploy that folder directly to a basic hosting server running nginx, or even a crappy shared hosting package, compared to a server running a node build process.

Is it weird / bad to have a second github repo just for the /build directory to push new builds to the hosting server?

Do hosting providers get pissed if you host an actual "app" rather than just another generic WordPress site? It's just serving a couple of mb of html, js, and css so I don't see why, but maybe I'm missing something.

I saw something about using S3 to server static sites but that seems like overkill for where I'm at. I dunno if there's a middle ground somewhere between S3 and an old school cPanel on a shared host setup?

fuf
Sep 12, 2004

haha

fuf posted:

Is it weird / bad to have a second github repo just for the /build directory to push new builds to the hosting server?

I did this with a new "build" branch instead and removed /build from .gitignore on this branch.

Then I created a new DigitalOcean App and pointed it at the /build directory of the build branch and it successfully detected it as a static site and deployed it. Pretty cool!

I haven't used DO for years and I kind of love it. It's like baby's first AWS. It's pretty powerful but generally very straightforward.

Osmosisch
Sep 9, 2007

I shall make everyone look like me! Then when they trick each other, they will say "oh that Coyote, he is the smartest one, he can even trick the great Coyote."



Grimey Drawer
You can also just use the gh-pages module to automate the whole thing on github pages from a single repo.

smackfu
Jun 7, 2004

fuf posted:

assumes that you want to pull from a github repo and then run the build process on the production server itself

That doesn’t seem right to me? In 2024, I would expect them to build your site into a docker image that would be what is actually the “production server.”

fuf
Sep 12, 2004

haha

Osmosisch posted:

You can also just use the gh-pages module to automate the whole thing on github pages from a single repo.

Oh yeah, that might save a few steps. Thanks.


smackfu posted:

That doesn’t seem right to me? In 2024, I would expect them to build your site into a docker image that would be what is actually the “production server.”

I'm probably missing a few steps. When you create an App you point it at a repository and it tries to figure out the best way to build and serve it. Maybe it builds a docker image every time I dunno.

It seems good but when I point it at my create-react-app repository, it says:


To me this looks like it's gonna serve the app by just running "npm start", which doesn't make sense. I want it to serve the contents of /build as a static site...
You can probably configure it all but it seems easier to build locally and then let it recognise my new build branch as a static site.
(also if it has to do the build then it classifies it as a "web service" and it's billed differently than the static site)

abraham linksys
Sep 6, 2010

:darksouls:
I just had coffee so I'm going to write too many words here:

fuf posted:

A lot of the stuff I'm looking at, like https://www.digitalocean.com/products/app-platform, assumes that you want to pull from a github repo and then run the build process on the production server itself, but I'm struggling to see why this is an advantage.

DO App Platform is a Heroku-like. It uses "buildpacks," which are a spec for how to build an application's source into an image that can be deployed somewhere (probably an OCI/Docker image under the hood). Heroku's buildpacks became a spec (https://buildpacks.io/) over the past five or six years and now a few different platforms support it.

It's... relatively antiquated, I guess. It made more sense in a pre-Docker world. Now, at the end of the day, it's just generating a Docker image, there's no reason a developer couldn't just write their own Dockerfile instead. But for a single-service project that doesn't use Docker in any other parts of its development process, it can be nice to avoid having the overhead of setting up a Dockerfile and testing it out for production. That said, App Platform also just supports Docker images for builds, which I think is true of most hosted services that support buildpacks in 2024.

Now in terms of "why your build runs on App Platform:" generally it is best-practice for production applications to not be built on a developer's local computer. None of the reasons for this are really going to matter for your personal project, but collaborative projects usually have some degree of logging and security around builds and deploys. Plus, you don't want your application to build differently between different developer machines and then have problems in production that can't be replicated easily.

I do want to note that the builds do not run in the "production server." There is a separate build environment, because it's gonna output that Docker image, and that's your "production server."

BTW, other competitors in this space: Heroku (probably not a good choice in 2024), AWS Elastic Beanstalk (ditto), GCP App Engine (extremely ditto), Azure App Service (not sure anyone has ever used this), Render (actually pretty awesome though I don't expect them to stick around long, someone will buy them).

quote:

If I run "npm run build" locally then what I end up with in the /build folder is just a static site that I can host anywhere, right? It feels easier to just deploy that folder directly to a basic hosting server running nginx, or even a crappy shared hosting package, compared to a server running a node build process.

Is it weird / bad to have a second github repo just for the /build directory to push new builds to the hosting server?

Do hosting providers get pissed if you host an actual "app" rather than just another generic WordPress site? It's just serving a couple of mb of html, js, and css so I don't see why, but maybe I'm missing something.

I saw something about using S3 to server static sites but that seems like overkill for where I'm at. I dunno if there's a middle ground somewhere between S3 and an old school cPanel on a shared host setup?

So, the punchline is that everything I just typed above is kind of irrelevant for your use case because you have a static site. You can absolutely put static sites wherever the hell you want. My personal website is the cheapest DigitalOcean VPS, a Caddy server (re: hipster Nginx), and some files in ~/www that I just scp up there whenever I feel like making changes to it.

Now, there are some managed static site hosting solutions that are pretty nice. You've already settled on GitHub Pages from the sound of it and, yeah, you should probably just use that, it's super effective. The fancier managed solutions will get you cloud builds for your static assets (again, not really relevant for a personal project) as well as some fancy vendor-specific tools they'll try to lock you in to keep you from realizing that all they're doing is offering S3 and CloudFront at a 1000% markup. Products in this space include Netlify Core and Vercel Frontend Cloud, which I mention really just because they have pretty generous free tiers (for now).

fuf posted:

It seems good but when I point it at my create-react-app repository, it says:


To me this looks like it's gonna serve the app by just running "npm start", which doesn't make sense. I want it to serve the contents of /build as a static site...
You can probably configure it all but it seems easier to build locally and then let it recognise my new build branch as a static site.
(also if it has to do the build then it classifies it as a "web service" and it's billed differently than the static site)

This is because the buildpack system sees that there's a package.json there and just assumes it is a Node server that it should run; it's not built to just serve static assets. You could stick a static server in there and have it host those files, but that would be pretty silly compared to using a static site host.

fuf
Sep 12, 2004

haha
Thanks for the detailed writeup, much appreciated.

I am going to stick with my DO "App" for now, since it's on the free tier and I have a process that works, but I'll move to GitHub pages if this project continues.

I have another React project that is using Strapi for its database and API, so suddenly I won't be in static site territory anymore. I guess this is where Docker could come in? I was just going to try and get it working on a DO VPS tbh. I already have Strapi installed on a Droplet so I think adding the React front end which calls it shouldn't be too hard...

fuf
Sep 12, 2004

haha
My adventures in learning React state management continue, and I'm currently rebuilding my card game app in NextJS 14.

I like the idea of using routes to manage state, but uhhhh now my app router directory looks like this:


Is this kind of thing normal or have I gone completely insane? I probably don't need all of those pages and layouts, but I'm trying to come up with a consistent pattern so I can build out the UI as necessary.

I think I could use parallel routes and intercepting routes to make the hierarchy a little less deep, but I'm not sure I need them (yet) and they might just make things more complex.

The app is all about selecting cards for different slots, and then doing stuff with the array of currently selected cards. The routes above handle doing the selecting, but it looks like I'm still going to need to use something like Zustand to keep track of my currently selected cards array in a global store. Which is fine but it would be nice if I could do it all within NextJS.

The server-side rendering thing feels like a bit of a double edged sword. I really really love that you can just do something like this directly in a component:
code:
const categories = await prisma.category.findMany();
and cut out the whole middle layer of api calls.

But it also adds a lot of complexity, like apparently with Zustand you have to initialise the store both on the server and the client somehow.

Anyhoo any NextJS thoughts or tips very welcome.

fsif
Jul 18, 2003

fuf posted:

Is this kind of thing normal or have I gone completely insane?

Probably closer to the "completely insane" side of that spectrum.

Why do you want to do all of your state management via routing? From my vantage point, it seems like a comically clunky way to do what you're trying to do.

fuf
Sep 12, 2004

haha
I dunno I just kept reading loads of stuff about how the thing to do in NextJS is put the state in the URL!

The last build of the app (React and zustand, then React and preact signals) had a lot of state stuff that was like currentCategoryId, currentCardId, currentFilter, etc. etc. and it seemed like this would be a good alternative to that.

The other thing the old app did was retrieve all the cards from the db on page load and then just store them in state as a massive array. Maybe this is fine but it didn't feel great. With the NextJS version I wanted to see if I could just store the currently selected card IDs in client state (using something like zustand because I know I can't do this bit with the url) and then get the rest of the card data from the DB only when required.

Boosh!
Apr 12, 2002
Oven Wrangler
If you don't need a page for every dynamic route you can use catch-all segments: https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes

You can also ditch the static folders (category, preset, , slot and card). Your route would be something like: /<category id>/<preset id>/<slot id>/<card id>

fsif
Jul 18, 2003

fuf posted:

I dunno I just kept reading loads of stuff about how the thing to do in NextJS is put the state in the URL!

The last build of the app (React and zustand, then React and preact signals) had a lot of state stuff that was like currentCategoryId, currentCardId, currentFilter, etc. etc. and it seemed like this would be a good alternative to that.

The other thing the old app did was retrieve all the cards from the db on page load and then just store them in state as a massive array. Maybe this is fine but it didn't feel great. With the NextJS version I wanted to see if I could just store the currently selected card IDs in client state (using something like zustand because I know I can't do this bit with the url) and then get the rest of the card data from the DB only when required.

I guess I don't understand why the ergonomics of storing all of your state via routing is preferable over just using zustand.

I've never personally heard of anyone suggesting they use Next.js's routes as a replacement for a more conventional state management tool. The theoretical advantage of it would be, I guess, being able to share your instance of an app via a URL? But I'd still prefer using query params. Just doesn't seem like you'd want to leverage routing unless you really want to make distinct pages; doesn't make sense to me to use it to track the state of a game/app.

fuf
Sep 12, 2004

haha

Boosh! posted:

If you don't need a page for every dynamic route you can use catch-all segments: https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes

You can also ditch the static folders (category, preset, , slot and card). Your route would be something like: /<category id>/<preset id>/<slot id>/<card id>

Thanks, those catch-all segments look like they could be useful.

And yeah adding a static layer in between each dynamic route was definitely overkill.

One thing I can't figure out is how to use Next's <Link> component to append to the current route instead of building an entirely new route. Right now I have to do
code:
<Link href={`/category/${categoryId}/${slotId}/${cardId}/">Go to card</Link> 
each time. It's fine two layers deep but when I get to cards they don't know what category they're in (only what slot they're in) so I don't have immediate access to {categoryId}. I wish I could just do something like:
code:
<Link href={`${currentRoute}/${cardId}`}>Go to card</Link>

fsif posted:

I guess I don't understand why the ergonomics of storing all of your state via routing is preferable over just using zustand.

I've never personally heard of anyone suggesting they use Next.js's routes as a replacement for a more conventional state management tool. The theoretical advantage of it would be, I guess, being able to share your instance of an app via a URL? But I'd still prefer using query params. Just doesn't seem like you'd want to leverage routing unless you really want to make distinct pages; doesn't make sense to me to use it to track the state of a game/app.

Maybe I've overegged it slightly by calling it state management. Those routes are just to get to the various pages where cards are listed and can be selected. The actual current state of the game (basically which slots are enabled and which cards are selected) is still in zustand.

I have a new pattern where all the route pages are server components that do all the data fetching and list the data, and then the individual list items are client components that have access to the client side zustand state.

So for example page.tsx in /[categoryId] looks like:

JavaScript code:
export default async function categoryPage({
  params,
}: {
  params: { categoryId: string };
}) {
  const category = await getCategoryWithSlots(params.categoryId); // Prisma
  if (!category) return <div>Category not found</div>;
  return (
    <div className="flex gap-5">
      {category.slots.map((slot) => (
        <SlotCard key={slot.id} slot={slot} />
      ))}
    </div>
  );
}
and SlotCard.tsx looks like:

JavaScript code:
"use client"
import { Slot } from "@prisma/client";
import React from "react";
import { Card } from "../ui/card";
import Link from "next/link";
import { Button } from "../ui/button";

export default function SlotCard({ slot }: { slot: Slot }) {

  const addSlot = store((state) => state.addSlot); // Zustand store containing selected slots

  const handleSelect = () => {
    addSlot(slot.id);
  }

  return (
    <Card className="p-5 flex flex-col">
      <p>{slot.title}</p>
      <Button onClick={handleSelect}>Select</Button>
      <Link href={`/category/${slot.categoryId}/slots/${slot.id}`}>
        <Button>Edit</Button>
      </Link>
    </Card>
  );
}
Again not really sure whether this is sensible but some kind of server/client divide like this seems necessary because I can't do data fetching from Prisma and use a zustand store in the same component...

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Can't you just link to "./{cardId}"?

fuf
Sep 12, 2004

haha
No, it cuts off the last node of the url for some reason. It's very weird:

JavaScript code:
// Current URL: http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/slots

<Link href={`/category/${slot.categoryId}/slots/${slot.id}`}>Edit Slot</Link>
// Correctly links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/slots/3a602f4a-f65c-403c-a6c6-db58545246f6 but requires knowing every ID in the route. 

<Link href={`./${slot.id}`}>Edit Slot</Link>
// Links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/10c09190-cc28-4931-885c-e06ba99322f5 and gets a 404

<Link href={`${slot.id}`}>Edit Slot</Link>
// Links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/10c09190-cc28-4931-885c-e06ba99322f5 and gets a 404

<Link href={`./slots/${slot.id}`}>Edit Slot</Link>
// Correctly links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/slots/10c09190-cc28-4931-885c-e06ba99322f5 but requires a fixed folder name between every dynamic ID. 

fuf
Sep 12, 2004

haha
Ok turns out there is usePathname https://nextjs.org/docs/app/api-reference/functions/use-pathname so I can do this in client components and it works:

JavaScript code:
const pathName = usePathname();
<Link href={`${pathName}/${slot.id}`}>Edit Slot</Link>
The server equivalent is supposed to be https://nextjs.org/docs/app/api-reference/functions/redirect but it doesn't look like it can return the current URL to append to, and also I don't know how I would call it from a server component because you can't do onClick events...

fuf fucked around with this message at 12:38 on Feb 9, 2024

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

fuf posted:

No, it cuts off the last node of the url for some reason. It's very weird:

JavaScript code:
// Current URL: http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/slots

<Link href={`/category/${slot.categoryId}/slots/${slot.id}`}>Edit Slot</Link>
// Correctly links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/slots/3a602f4a-f65c-403c-a6c6-db58545246f6 but requires knowing every ID in the route. 

<Link href={`./${slot.id}`}>Edit Slot</Link>
// Links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/10c09190-cc28-4931-885c-e06ba99322f5 and gets a 404

<Link href={`${slot.id}`}>Edit Slot</Link>
// Links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/10c09190-cc28-4931-885c-e06ba99322f5 and gets a 404

<Link href={`./slots/${slot.id}`}>Edit Slot</Link>
// Correctly links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/slots/10c09190-cc28-4931-885c-e06ba99322f5 but requires a fixed folder name between every dynamic ID. 

I don't get your problem. It seems to be doing exactly what you want it to do?

fuf
Sep 12, 2004

haha

Jabor posted:

I don't get your problem. It seems to be doing exactly what you want it to do?

Which one? I tried to put a comment explaining the drawback of each.

I have a workaround now for server components because you can get all the route sections up to that point in {params}, but it's still weird to me that "." in the href cuts off the last node.

necrotic
Aug 2, 2005
I owe my brother big time for this!
If your route ended in a / the ./ would work like you desire. A regular old anchor tag behaves the same way.

If you’re on /foo then ./bar links to /bar.

If you’re on /foo/ then ./bar links to /foo/bar.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
I'm pretty sure the only reason this:

quote:

code:

// Links to http://localhost:3000/category/4cda16ff-300f-4b11-aafe-e1fad4c2eec0/10c09190-cc28-4931-885c-e06ba99322f5 and gets a 404

Is giving you a 404 is because your routes are currently set up to expect /category/{a}/slots/{b}.

So you have options that work if you do have those fixed fragments in between each value, and options that don't work now but will work if you get rid of them. (Or can trivially be made to work by just modifying the link, like make it "./slots/{slotId}")

So what's the problem?

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense

fuf posted:

My adventures in learning React state management continue, and I'm currently rebuilding my card game app in NextJS 14.

I like the idea of using routes to manage state, but uhhhh now my app router directory looks like this:


Is this kind of thing normal or have I gone completely insane? I probably don't need all of those pages and layouts, but I'm trying to come up with a consistent pattern so I can build out the UI as necessary.

I think I could use parallel routes and intercepting routes to make the hierarchy a little less deep, but I'm not sure I need them (yet) and they might just make things more complex.

The app is all about selecting cards for different slots, and then doing stuff with the array of currently selected cards. The routes above handle doing the selecting, but it looks like I'm still going to need to use something like Zustand to keep track of my currently selected cards array in a global store. Which is fine but it would be nice if I could do it all within NextJS.

The server-side rendering thing feels like a bit of a double edged sword. I really really love that you can just do something like this directly in a component:
code:
const categories = await prisma.category.findMany();
and cut out the whole middle layer of api calls.

But it also adds a lot of complexity, like apparently with Zustand you have to initialise the store both on the server and the client somehow.

Anyhoo any NextJS thoughts or tips very welcome.

I've been playing with this stuff too.

Regarding the router, I'm finding it to be pretty awesome for keeping pages organized and all the magic it does with layout is wicked. So I'm feeling drawn to defend it. Why do you need so many ids in your url? Assuming they're unique identifiers all you really need is the last one.

You could have `/category/categoryId` the same way you could have `/card/cardId`, unless I'm misunderstanding why your url is so complicated.

Also I would never initialize a store on the server that seems weird. That's your source of truth and you should be updating a store on the client based off of that source of truth, I'm not sure managing state on your server is something you're supposed to do. On the other hand writing sql statements in a react component didn't used to be something we are supposed to do either.

fuf
Sep 12, 2004

haha

necrotic posted:

If your route ended in a / the ./ would work like you desire. A regular old anchor tag behaves the same way.

If you’re on /foo then ./bar links to /bar.

If you’re on /foo/ then ./bar links to /foo/bar.

Oh interesting, I never would have thought the trailing slash might be relevant. I tried adding a trailing slash but it still cuts off the last node when linking to ./
The trailing slash isn't visible in the url bar though so maybe Next cuts it off.


Jabor posted:

I'm pretty sure the only reason this:

Is giving you a 404 is because your routes are currently set up to expect /category/{a}/slots/{b}.

No, if I removed the fixed /slots/ fragment from my route then the generated link would cut off the {a} instead so it would still fail. It would link to /category/{b}.
The last node of the URL always gets cut off when linking to ./

Jabor posted:

(Or can trivially be made to work by just modifying the link, like make it "./slots/{slotId}")

Yeah this was the fourth example in my list, it works but requires a fixed fragment between every dynamic route.


Nolgthorn posted:

I've been playing with this stuff too.

Regarding the router, I'm finding it to be pretty awesome for keeping pages organized and all the magic it does with layout is wicked. So I'm feeling drawn to defend it. Why do you need so many ids in your url? Assuming they're unique identifiers all you really need is the last one.

You could have `/category/categoryId` the same way you could have `/card/cardId`, unless I'm misunderstanding why your url is so complicated.

Also I would never initialize a store on the server that seems weird. That's your source of truth and you should be updating a store on the client based off of that source of truth, I'm not sure managing state on your server is something you're supposed to do. On the other hand writing sql statements in a react component didn't used to be something we are supposed to do either.

Oh yeah I think all the routing stuff is really cool too! I'm just still getting my head around it. But yeah it's really fun.

I could definitely just have category/{categoryId} and /card/{cardId} etc.

It's mostly about UI and navigation: I have a list of categories, and then you click one to open up a list of slots, and then you click a slot to open up a list of cards. So the UI drills down two layers but keeps bits of the first two layers visible. It makes sense to me in that situation to have a route with three layers too.

Maybe I could do the same thing with parallel routes or something and keep everything on the same layer, but I can't immediately see how.

Sad Panda
Sep 22, 2004

I'm a Sad Panda.
Following a beginner React course. Created this rather ugly to-do app. https://codesandbox.io/p/sandbox/todo-almost-forked-znfh4d?file=%2Fsrc%2FApp.js%3A187%2C1

Adding a person works.
Removing a person works.
Rendering the to-dos works.
Ticking off to-dos... is a work in progress and I'm not sure why.

When I tick a task in the ShowTodosForm, the count goes down in the sidebar. And if I click to a different person and back to that person, their todos are fine. However, if not then the item doesn't go away - and if I click to the other person the first item is ticked for them too.

I'm not really sure how to diagnose this so would love a pointer. Thanks.

Ima Computer
Oct 28, 2007

Stop all the downloading!

Help computer.

Sad Panda posted:

When I tick a task in the ShowTodosForm, the count goes down in the sidebar. And if I click to a different person and back to that person, their todos are fine. However, if not then the item doesn't go away - and if I click to the other person the first item is ticked for them too.

I'm not really sure how to diagnose this so would love a pointer. Thanks.

When you switch between selected persons, you're still rendering the same component tree. Any elements in the DOM that are shared with the previous selected person's render will be kept in place. Only the differences are applied as DOM transformations. So, as long as each list has some number of todos in it, and todos always render a checkbox input, there will be some checkbox inputs that get reused.

You can opt out of this by setting a unique key prop, which tells React to treat something as a unique instance of a component or element.

I would try updating the key here on the <Todo> to be a unique value for each person and todo combination:
Diff code:
person.todos.map((todo, i) => (
- <Todo key={i} i={i} todo={todo} onTaskChecked={onTaskChecked} />
+ <Todo key={`${person.id}-${todo}`} i={i} todo={todo} onTaskChecked={onTaskChecked} />
))

smackfu
Jun 7, 2004

Related to that, you are using an uncontrolled input for the checkbox, so if it is reused, it will stay checked.

So passing in a value for the checked attribute might fix it.

Sad Panda
Sep 22, 2004

I'm a Sad Panda.
Thanks for that. I've changed the key, and that does indeed stop the same ID checkbox being ticked. It still doesn't immediately pop that item off my to-do list - I've coded it so when I check it, it pops it from the list.

JavaScript code:
  function handleTaskChecked(taskID) {
    const updatedPeopleData = peopleData.map((person) =>
      person === selectedPerson
        ? { ...person, todos: person.todos.filter((todo, i) => i !== taskID) }
        : person
    );
    setPeopleData(updatedPeopleData);
  }
It correctly sets the array to be a new version with the todo item removed, but doesn't re-render that version of the todo list. What's causing that? This also means that if I try to check multiple items off, it won't work. I assume that's because the rendered state in my todo list form is stale?

smackfu posted:

Related to that, you are using an uncontrolled input for the checkbox, so if it is reused, it will stay checked.

So passing in a value for the checked attribute might fix it.

Thanks - I was wondering about that. To me I wasn't sure what value to assign to it. If I had a boolean of completed, then that'd make sense to toggle it. As is I just want to pop it so unsure what value would work for it.

Sad Panda fucked around with this message at 21:44 on Feb 12, 2024

Ima Computer
Oct 28, 2007

Stop all the downloading!

Help computer.

Sad Panda posted:

It still doesn't immediately pop that item off my to-do list - I've coded it so when I check it, it pops it from the list.

It correctly sets the array to be a new version with the todo item removed, but doesn't re-render that version of the todo list. What's causing that? This also means that if I try to check multiple items off, it won't work. I assume that's because the rendered state in my todo list form is stale?

Even though you're updating the state in peopleData, you're not updating the state in selectedPerson, which has a copy of the old person object.

Instead of storing a copy of the entire person object in state, try storing a selectedPersonId, and then use a memo to derive the selected person from selectedPersonId and peopleData whenever either of them changes:
JavaScript code:
const [peopleData, setPeopleData] = useState(initialData);
const [selectedPersonId, setSelectedPersonId] = useState(undefined);
const selectedPerson = useMemo(() => {
  if (selectedPersonId === undefined) return undefined;
  return peopleData.find(({ id }) => id === selectedPersonId)
}, [peopleData, selectedPersonId]);

Sad Panda
Sep 22, 2004

I'm a Sad Panda.

Ima Computer posted:

Even though you're updating the state in peopleData, you're not updating the state in selectedPerson, which has a copy of the old person object.

Instead of storing a copy of the entire person object in state, try storing a selectedPersonId, and then use a memo to derive the selected person from selectedPersonId and peopleData whenever either of them changes:
JavaScript code:
const [peopleData, setPeopleData] = useState(initialData);
const [selectedPersonId, setSelectedPersonId] = useState(undefined);
const selectedPerson = useMemo(() => {
  if (selectedPersonId === undefined) return undefined;
  return peopleData.find(({ id }) => id === selectedPersonId)
}, [peopleData, selectedPersonId]);

Thanks for the push. I'd not learnt about useMemo yet so I fixed this instead by...

Storing the person's ID not the person. This meant in handleTaskChecked I could do it like this.
JavaScript code:
  function handleTaskChecked(taskID) {
    const updatedPeopleData = peopleData.map((person) =>
      person.id === selectedPersonID
        ? { ...person, todos: person.todos.filter((todo, i) => i !== taskID) }
        : person
    );
    setPeopleData(updatedPeopleData);
  }
And passing the whole peopleData into ShowTodosForm which meant that when that array was updated, it triggered the re-render. I also included the length of the todo list in the key. This meant they would change when the checkbox was ticked and not stay stale.

JavaScript code:
function ShowTodosForm({ peopleData, onTaskChecked, selectedPersonID }) {
  const person = peopleData.find((person) => person.id === selectedPersonID);
  return (
    <form className="form-show-todos">
      <h2>{person.name}&apos;s Todos</h2>
      <ul>
        {person.todos.length > 0
          ? person.todos.map((todo, i) => (
              <Todo
                key={`${person.id} - ${i}/${person.todos.length}`}
                taskIndex={i}
                todo={todo}
                onTaskChecked={onTaskChecked}
              />
            ))
          : 'Nothing to do'}
      </ul>
    </form>
  );
}

Leng
May 13, 2006

One song / Glory
One song before I go / Glory
One song to leave behind


No other road
No other way
No day but today
Hi thread! I'm pretty embarrassed to be posting this because I'm positive that I'm overlooking something so stupidly simple that I'm an idiot for not being able to see it. This is my first real coding attempt in something like 12+ years so I've forgotten most everything and have been trying to cobble stuff together.

The project:
I'm trying to build a filterable gallery using the base code provided here: https://www.codingnepalweb.com/filterable-image-gallery-html-bootstrap/

However, instead of just one group of filters, I have multiple groups of filters and some of them should be single selection only while others are multiple selection.

Single selection filters:
  • type of book (e.g. standalone, 1st in series)
  • ebook availability (Kindle Unlimited, Kobo Plus, Google Play, Nook, etc)
  • intended audience (all ages, YA, adult, etc)

Multiple selection filters:
  • length (e.g. short, medium, long)
  • genre (e.g. mystery, action, romance)

Toggling the filters on/off should update the results in real time. So if a user selects "standalone", "Kindle Unlimited", "YA", "short", "mystery" and "action", the only books that show should be short standalone books intended for a YA audience that are available on Kindle Unlimited and are either in the mystery or action genres.

Current status:
I've been able to get the visibility of whether a button is selected or deselected working mostly right so I'm using const selectedFilters = document.querySelectorAll("#filter-buttons .active"); to then figure out what should/shouldn't be showing. I'm then splitting those into two separate arrays, selectedFilterLimits to represent the single selection filters and selectedFilterParams to represent the multiple selection filters.

Somewhere along the way of trying to get the results to display as I want them to, I screwed up and now the filters don't deselect properly.

. :negative:

My fail code, which has numerous redundant bits in it from abandoned previous attempts at getting something to work, as well as a couple of parts where I'm SURE it's the culprit but my eyes are totally crossing and I can't figure out the fix:

JavaScript code:
// Select relevant HTML elements
const filterReset = document.querySelector("#filter-reset");
const filterButtons = document.querySelectorAll("#filter-buttons .filter-param");
const filterScope = document.querySelectorAll("#filter-scope .filter-param");
const filterAudience = document.querySelectorAll("#filter-audience .filter-param");
const filterLength = document.querySelectorAll("#filter-length .filter-param");
const filterMood = document.querySelectorAll("#filter-mood .filter-param");
const filterEbook = document.querySelectorAll("#filter-ebook .filter-param");
const filterFormat = document.querySelectorAll("#filter-format .filter-param");
const filterRetailer = document.querySelectorAll("#filter-retailer .filter-param");

const filterableCards = document.querySelectorAll("#filterable-cards .card");
const limitScopeTo = [];
const activeFilters = [];
const activeFilterGroups = [];

// Define what filter options there are
const filterScopeOptions = ["standalone","1st"];
const filterEbookOptions = ["ku","kobo","koboplus","nook","googleplay","apple","smashwords"];
const singleSelectFilters = ["scope","ebook"]

// Define what filters are selected
const selectedFilterParams = [];
const selectedFilterLimits = [];


// Function to filter cards based on filter buttons
const filterCards = (e) => {

  // define previous states to button press
  let previousFilterSelection = activeFilters;
  let previousFilterGroups = activeFilterGroups;
  let previousScopeSelection = limitScopeTo;
  console.log("Previous filter selection is: " + previousFilterSelection);
  console.log("Previously selected filter groups: " + previousFilterGroups);
  console.log("Previous scope limited to: " + previousFilterGroups);


// current state
  let filterSelection = e.target.dataset.filter;
  let filterName = e.target.dataset.filtername;
  let filterChoice = e.target.dataset.filterchoice;
  let currentFilterGroup = filterName;
  let filterGroupSelected = document.querySelectorAll("#filter-" + e.target.dataset.filtername + " .filter-param");
  let currentState = activeFilters;

  // which button has been pressed?
  console.log("Filter pressed: " + filterSelection);
  console.log("Filter name: " + filterName);
  console.log("Filter allows: " + filterChoice + " selection");
  console.log("Current active filters: " + currentState);

    // if button is already selected, then deselect it
    if (activeFilters.includes(filterSelection)) {
      console.log("Removing the filter " + filterSelection);
      e.target.classList.remove("active");
      activeFilters.splice(activeFilters.indexOf(filterSelection));
      console.log("Current active filter/s: " + activeFilters);
      console.log("Removing the scope " + filterSelection);
      limitScopeTo.splice(limitScopeTo.indexOf(filterName));
      console.log("Current scope is limited to: " + limitScopeTo);
      // if there are no active filters, show everything is reset
      console.log("No filters selected, showing all options.");
      if (activeFilters.length === 0) {
        filterReset.classList.add("active");
      }
    } else {

      if (filterChoice === "single") {
        // deselect the other options within the scope filter group
        console.log(filterSelection + " is single selection only. Deselecting other filters in " + filterName);
        filterGroupSelected.forEach(button => button.classList.remove("active"));

        if (activeFilters.length === 0){
          console.log("Filters available for removal: none.");
        } else {
          activeFilters.splice(activeFilters.indexOf(filterSelection));
        }
      }

      // if the filter is multi selection, leave previous filters selected
      if (e.target.dataset.filterchoice === "multi") {
        console.log(filterName + " is multi selection. Leaving other filters in " + filterName);
      }

    // select the button and add it to the list of active filters
    console.log("Adding " + filterSelection + " to active filters.");
    e.target.classList.add("active");
    currentState.push(filterSelection);
    const selectedFilters = document.querySelectorAll("#filter-buttons .active");
    console.log("Selected filters:")
    console.log(selectedFilters);
    console.log("Active filters:")
    console.log(activeFilters);


    selectedFilters.forEach(button => {
      if (button.dataset.filter === "") {
        console.log("Return nothing");
      } else {
        console.log("Returning value: " + button.dataset.filter);
    }

    if (button.dataset.filterchoice === "single") {
      // add to filter limits only if it's not a null value or already included
      if (button.dataset.filter === "" || selectedFilterLimits.includes(button.dataset.filter)) {
        console.log("Skipped as either null value or already selected.")
      } else {
      // clear previous filter and add new to filter limits
      // THIS IS THE PART WITH THE PROBLEMS
      
      selectedFilterLimits.push(button.dataset.filter);
    }} else {
      if (selectedFilterParams.includes(button.dataset.filter)) {
        console.log("Skipped as already in filter params");
      } else {
        selectedFilterParams.push(button.dataset.filter);
  }
  activeFilters.push(button.dataset.filter);
}
  console.log("Currently limiting scope to: " + selectedFilterLimits);
  console.log("Current active filter params: " + selectedFilterParams);
  console.log("Current state at end of button press: " + activeFilters);

// WHY IS EVERYTHING BEING ADDED TWICE?????? Is it because of the line 6 lines up?

  // deselect the reset button
  filterReset.classList.remove("active");

});

}
If anyone could point me in the right direction, I would be very grateful for the help!

Nolgthorn
Jan 30, 2001

The pendulum of the mind alternates between sense and nonsense
I don't have anything to contribute but I want to compliment your easy to read well organized code.

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Nolgthorn posted:

I don't have anything to contribute but I want to compliment your easy to read well organized code.

Leng posted:

JavaScript code:
// Select relevant HTML elements
    }} else {
      if (selectedFilterParams.includes(button.dataset.filter)) {
        console.log("Skipped as already in filter params");
      } else {
        selectedFilterParams.push(button.dataset.filter);
  }
I do not find these indents-not-matching-brackets to be well-organized or easy to read, and I suspect without having done much inspection that the problem lies in a condition being in the wrong place from this. Run it through an auto-formatter! (Though I also would be suspicious of a block that says it removes something calling Array.push which is an action that doesn't remove anything.)

Wheany
Mar 17, 2006

Spinyahahahahahahahahahahahaha!

Doctor Rope

Leng posted:

If anyone could point me in the right direction, I would be very grateful for the help!

From skimming the code:

You seem to be using a non-checkbox element like a checkbox, you could try using checkboxes and trigger your filtering with the "input" event.

You also seem to be doing both the active/not active toggling and filtering in the same pass. I would do the toggling in one function, then run a separate function that just gathers the current selection and filters on that.

Leng
May 13, 2006

One song / Glory
One song before I go / Glory
One song to leave behind


No other road
No other way
No day but today

roomforthetuna posted:

I do not find these indents-not-matching-brackets to be well-organized or easy to read, and I suspect without having done much inspection that the problem lies in a condition being in the wrong place from this. Run it through an auto-formatter! (Though I also would be suspicious of a block that says it removes something calling Array.push which is an action that doesn't remove anything.)

Wheany posted:

From skimming the code:

You seem to be using a non-checkbox element like a checkbox, you could try using checkboxes and trigger your filtering with the "input" event.

You also seem to be doing both the active/not active toggling and filtering in the same pass. I would do the toggling in one function, then run a separate function that just gathers the current selection and filters on that.

:ughh:

You are both 100% on the money.

I am the stupid. I indeed messed up the conditions and also tripped myself up by not separating out my functions. I managed to impose on two others outside of this thread to take a detailed look and they very kindly turned my travesty into this elegant script:

JavaScript code:
// Select relevant HTML elements
const filterReset = document.querySelector("#filter-reset");
const filterButtons = document.querySelectorAll("#filter-buttons .filter-param");

const filterableCards = document.querySelectorAll("#filterable-cards .card");

const single_filters_map = new Map()
const multi_filters_map = new Map()


const showCards = () => {
    filterableCards.forEach(card => {
    //console.log(card.dataset)
    //console.log(activeFilters)
    const cardFilters = card.dataset.name.split(' ');
    const matchesSingleFilters = single_filters_map.length === 0 || Array.from(single_filters_map.values()).every(filter => cardFilters.includes(filter));
    const matchesMultiFilters = multi_filters_map.length === 0 || Array.from(multi_filters_map.values()).every(filterArray => filterArray.length === 0 || filterArray.some(filter => cardFilters.includes(filter)));
    const isVisible = matchesSingleFilters && matchesMultiFilters;

    if (isVisible) {
      card.style.display = "block";
    } else {
      card.style.display = "none";
    }
});
  
  console.log("Filters now:");
  console.log(single_filters_map);
  console.log(multi_filters_map);
}
                            
const clearRow = (filterName) => {
  document.querySelector("#filter-" + filterName).querySelectorAll(".filter-param").forEach(child => {
    if (child.classList.contains("active")) {
      child.classList.remove("active");
    }
  })
}
                            

// Function to handle filter button click
const handleButtonClick = (button, filterType, filterValue, filterName) => {
  console.log("Filter of type:", filterType)
  console.log("Filter to value:", filterValue)
  if (button.classList.contains("active")) {
    button.classList.remove("active")
    if (filterType === "single") {
     single_filters_map.delete(filterName)
    } else {
      const arr = multi_filters_map.get(filterName)
      arr.splice(arr.indexOf(filterValue), 1)
    }
  } else {
  if (filterType === "single") {
    clearRow(filterName)
    button.classList.add("active")
    single_filters_map.set(filterName, filterValue)
  } else  {
    button.classList.add("active")
    if (multi_filters_map.has(filterName)){
      multi_filters_map.get(filterName).push(filterValue)
    } else {
      multi_filters_map.set(filterName, [filterValue])
    }
  }
  }
  showCards();
}

// Add event listeners to filter buttons
filterButtons.forEach(button => {
  button.addEventListener("click", () => {
    const filterType = button.dataset.filterchoice;
    const filterValue = button.dataset.filter;
    const filterName = button.dataset.filtername;
    handleButtonClick(button, filterType, filterValue, filterName);
  });
});

// Function to reset filters and show all cards
const resetFilters = () => {
  single_filters_map.clear();
  multi_filters_map.clear();

  filterButtons.forEach(button => {
    button.classList.remove("active");
  });
  showCards();
}

// Add event listener to reset button
filterReset.addEventListener("click", resetFilters);
That saying about using a hammer on everything because it's the only tool you know has never felt more applicable. I'm gonna be chewing over this solution for weeks, and probably off to go read some docs to fill in the holes of my ignorance in the hopes that maybe the next time I write some code, it won't be several hundred lines of brain vomit.

Thank you all for taking a look!

Adbot
ADBOT LOVES YOU

necrotic
Aug 2, 2005
I owe my brother big time for this!
One of the biggest lessons to learn from that help is to work at breaking down what you are doing into smaller chunks. One big function doing a lot of stuff is generally a signal that it can be broken down into smaller "units of work" that each individually solve a smaller part of the larger problem.

This turns out to be one of the harder things for people to "figure out", in my experience, but can be a huge boost on writing at least decent code. Any time you find yourself struggling to resolve an issue, take a step back and see if you can break down what you're working on into smaller pieces. That process alone might lead you to resolving whatever issue, but if not it can help you narrow down where it is and then focus on that specific part.

I believe this is what you are acknowledging with

quote:

maybe the next time I write some code, it won't be several hundred lines of brain vomit.

so don't take my post as a "you got the wrong lesson", just an aid on top if anything.

And to add a bit more: figuring out how to break things down never goes away. It may become easier to see where things can be broken down, but you will at some point find yourself back here going "okay I have this thing and... what's wrong?".

necrotic fucked around with this message at 01:43 on Feb 23, 2024

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply