Use Isolation to Solve Complex Problems

Kent C. Dodds
Kent C. Dodds

Whenever you run into a situation where you're having trouble seeing live updates when making changes, I find it best to try to reproduce the problem in a completely separate environment.

Here's a process you can follow to isolate these types of problems in your own project.

The first thing to do is create a new test.ignored directory in your project. Check that your gitignore configuration ignores everything with .ignored in the name. You don't want this test code ending up in your repo!

Next, create the minimum number of files you need in order to recreate the problem.

The idea is that you want to have enough code to recreate your problem without being overwhelmed by all of the other parts of your project.

Now you can start experimenting and testing potential solutions. This might involve running tests using a watcher for changes, or creating new components.

Once you've found the solution in your test code, bring it back over to your main application code.

After you've verified that your problem is solved in the main app, you're safe to delete your test directory.

Isolating your problem to a small example in a temporary directory is a great way to solve complex problems!

Minimal MDX Example

In this example, I have a complicated setup involving adding interactive components to an MDX file that is being treated as plain markdown.

Inside of the test.ignored directory will be a minimal set of files to reproduce the problem I was seeing in the full project.

Since I'm working with MDX, I'll create a README.md, a test.ts file, and a component.tsx file.

The component.tsx file will import React and export a simple Counter component to act as a stand-in for the more complex component that would be used in the real project.


export function Counter() {
const [count, setCount] = React.useState(0);
const increment = () => setCount((c) => c + 1);
return <button onClick={increment}>{count}</button>;
}

The README.md file will import the Counter component and use it:


import {Counter} from './component'
This is my counter:
<Counter/>

Inside of the test.ts file, import the necessary modules and bundle the MDX:


import path from "path";
import { bundleMDX } from "mdx-bundler";
async function go() {
const result = await bundleMDX({
file: path.join(__dirname, "README.md"),
});
console.log(result.code);
}
go();

On the command line, I'll run the command tsx watch ./test.ignored/test.ts and check the output. If the MDX is not processed correctly, make changes to the configuration options.

In this case, we need to define 'process.env.NODE_ENV'


process.env.NODE_ENV = "development";

In addition, I need to add options.mdxExtensions to the MDXBundler to treat .md files as .mdx files, and add options.format to force the format to be mdx. Optionally, development can be set to false to remove debugging helpers:


mdxOptions(options) {
options.mdxExtensions = ['.mdx', '.md']
options.format = 'mdx'
options.development = false
return options
}

By creating a smaller example with less moving pieces, I was able to track down the configuration issues with my MDX processing.

The next time you run into a problem and you have no idea what could be causing it, try moving to a separate environment and slowly add files until you've reproduced the issue.

With fewer moving parts, you can more easily identify and resolve issues much faster than in the complexity of your full project.

Transcript

0:00 I've got a Markdown file here that I want to treat as MDX so I can have interactive counter components or whatever other components that I want in the instructions for the workshops. As you can see here, that's not being processed as MDX.

0:16 This is just being treated as Markdown, and if you dive through this, you can see where that is being treated as just like a regular string, rather than being treated as MDX, which I want it to be. I've got this whole, big setup here and this compile-mdx.server with a bunch of plugins and all of this stuff, even caches and stuff.

0:37 It's really difficult to work in this. Whenever you run into a situation where it's difficult to make changes and see your changes live and everything, I find it's always best to try and reproduce the problem that you're having in a completely separate environment, and then you have fewer moving parts.

0:58 What we're going to do is, I'm going to come down here. We're going to make a new directory called test.ignored. That's my global gitignore, just ignores everything that has a .ignore in the file name. Inside of this, we're going to make a test.ts, and we're going to also make whatever else we need.

1:18 In my case, I need a README.md and also, let's see, a component.tsx. I'm going to make a counter component to export that. We'll import React and then in my README, I'm going to import the Counter from the component, and we'll say this is my counter, Counter. All right, great.

1:53 That should work with what I'm trying to do, and then we'll make our test. We'll say import path from 'path' and bundleMdx. No, that's not it. mdx-bundler, and we'll make a function, because I can't use top-level await because these aren't native ESM. If it was native ESM, you don't need to make this go function. You just do await at the root.

2:25 Now, I can say bundleMdx. I think bundleMDX in [laughs] all caps, and we'll say file: path.join and I'm going to say _dirname. That will go right there, _dirname. We're actually going to be processing the README.md.

2:49 You take as few of the pieces of your actual code as possible to reproduce the problem. You narrow the problem down as much as possible. We'll get our result from that await, and then we'll console.log the result.code.

3:06 You know what, another thing that we'll do is in here, we'll make it obvious when it has been processed properly by adding a bunch of exclamation points. Now, we're going to run tsx watch ./test.ignored/test.ts. Let's move this out of the way and take a look at what we've got here.

3:32 Expected value for process.env. That's interesting. I could rerun the script with setting the app, but we'll say process.env. NODE_ENV= 'development,' which is what I'm running when I'm running my server.

3:47 That's kind of weird that it requires that, but that's OK, because I add that additional aspect to my actual environment until I get to the point where I'm reproducing the problem. I am reproducing the problem.

4:00 You don't see any all caps exclamation points, and in fact, there's the import right there. [laughs] This is not great. Now, we can go through docs and different configuration options, and I'm going to skip all that because I actually did that already.

4:19 The thing that's going to fix this for us is there's this mdxOptions (options) with mdx-bundler, and options.mdxExtensions. We need to treat our Markdown files as MDX. Actually, one other thing that we could do before we get to this is, let's test a theory that if this were an MDX file, that it would be treated as such. Boom, there it is.

4:47 All we need to do is tell our mdx-bundler or MDX that we need to treat .md files as .mdx files. Now we can switch this back to an .md. We switch this to .md. Of course, we need to return the options there. Oh no, it's still not giving us all those exclamation points in here.

5:12 The other thing that we need to add is options.format, and we're going to force it to say, "Hey, I want the format of this thing to be MDX." That's going to force it, and now we are solid.

5:25 Then the other thing that actually bothered me is I saw this fileName stuff in here. In production mode, that is all going to get removed, because these are debugging helpers and stuff. For my use case, I actually don't want that, and so I also am going to add development is false, and I can very quickly see, those are all gone.

5:50 I'm able to take this now over to my actual source code and add the pieces that were just the couple of lines that make all of the difference, and now I can come over to my running app, and indeed, it does run, and I can tab over to the button and, boom, boom, boom, boom, boom, boom. The button is totally working, so huzzah.

6:11 Next time you run into something that is there's so many moving pieces you have no idea what could be causing the problem, try to move to a separate environment and slowly add, add, add until you've reproduced the problem, and then you can work within that environment. It's way, way faster.

6:28 I hope that helps. Have an awesome day.

More Tips