惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Security Latest
Security Latest
C
Cisco Blogs
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
Project Zero
Project Zero
C
Cyber Attacks, Cyber Crime and Cyber Security
NISL@THU
NISL@THU
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
D
Docker
Google Online Security Blog
Google Online Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
G
Google Developers Blog
Schneier on Security
Schneier on Security
小众软件
小众软件
爱范儿
爱范儿
GbyAI
GbyAI
J
Java Code Geeks
T
Tailwind CSS Blog
Cisco Talos Blog
Cisco Talos Blog
The Hacker News
The Hacker News
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
TaoSecurity Blog
TaoSecurity Blog
MyScale Blog
MyScale Blog
B
Blog RSS Feed
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
C
CXSECURITY Database RSS Feed - CXSecurity.com
S
Securelist
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Y
Y Combinator Blog
S
Schneier on Security
Latest news
Latest news
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
F
Fortinet All Blogs
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
V
V2EX
V2EX - 技术
V2EX - 技术
O
OpenAI News
W
WeLiveSecurity

Kent C. Dodds Blog

Implementing Hybrid Semantic + Lexical Search Simplifying Containers with Cloudflare Sandboxes Migrating to Workspaces and Nx Offloading FFmpeg with Cloudflare Building Semantic Search on my Content Helping YOU ask ME questions with AI How I used Cursor to Migrate Frameworks The Dow's Start on the Covenant Path 2025 in Review The next chapter: EpicAI.pro AI is taking your job How I increased my visibility Launching Epic Web 2023 in Review Stop Being a Junior RSC with Dan Abramov and Joe Savona Live Stream Fixing a Memory Leak in a Production Node.js App 2022 in Review My Car Accident I Migrated from a Postgres Cluster to Distributed SQLite with LiteFS I'm building EpicWeb.dev A review of my time at Remix Remix: The Yang to React's Yin How I help you build better websites Why I Love Remix The State Initializer Pattern How to React ⚛️ Get a catch block error message with TypeScript Building an awesome image loading experience How Remix makes CSS clashes predictable Introducing the new kentcdodds.com How I built a modern website in 2021 How to use React Context effectively Static vs Unit vs Integration vs E2E Testing for Frontend Apps The Testing Trophy and Testing Classifications Array reduce vs chaining vs for loop Don't Solve Problems, Eliminate Them Super Simple Start to Remix Super Simple Start to ESModules in Node.js JavaScript Pass By Value Function Parameters How to write a Constrained Identity Function (CIF) in TypeScript How to optimize your context value How to write a React Component in TypeScript TypeScript Function Syntaxes Listify a JavaScript Array Build vs Buy: Component Libraries edition Using fetch with TypeScript Wrapping React.useState with TypeScript Define function overload types with TypeScript 2020 in Review Business and Engineering alignment Hi, thanks for reaching out to me 👋 useEffect vs useLayoutEffect Super simple start to Firebase functions Super simple start to Netlify functions Super Simple Start to css variables Favor Progress Over Pride in Open Source Testing Implementation Details How getting into Open Source has been awesome for me useState lazy initialization and function updates Use ternaries rather than && in JSX Application State Management with React Use react-error-boundary to handle errors in React JavaScript to Know for React How I structure Express apps What open source project should I contribute to? When I follow TDD AHA Programming 💡 How I Record Educational Videos Should I write a test or fix a bug? Stop mocking fetch Intentional Career Building Improve test error messages of your abstractions Tracing user interactions with React Eliminate an entire category of bugs with a few simple tools Common mistakes with React Testing Library Super Simple Start to React Stop using client-side route redirects The State Reducer Pattern with React Hooks Function forms Replace axios with a simple custom fetch wrapper How to test custom React hooks React Production Performance Monitoring Should I useState or useReducer? Stop using isLoading booleans Make Your Test Fail Make your own DevTools An Argument for Automation Fix the "not wrapped in act(...)" warning Super Simple Start to ESModules in the Browser Implementing a simple state machine library in JavaScript 2010s Decade in Review Why users care about how you write code Why I avoid nesting closures Don't call a React function component Why your team needs TestingJavaScript.com Inversion of Control Understanding React's key prop How to Enable React Concurrent Mode Profile a React App for Performance
5 Tips to Help You Avoid React Hooks Pitfalls
Ryan Florence@ryanflorence · 2019-08-05 · via Kent C. Dodds Blog

I've given this blog post as a talk which you can watch here:

The React Hooks feature was proposed in October 2018 and released ~4 months later in February 2019. Since then, people have been rapidly learning and adopting hooks in their production codebases because hooks drastically simplify the management of state and side effects in applications.

It definitely took its rightful place as "the new hotness." But as hot as it is, React Hooks require a bit of a change in the way you think about React Component Lifecycles, State, and Side Effects and it can be easy to fall into problematic scenarios if you're not thinking about React Hooks properly. So let's look a bit at what pitfalls you could come across and how you can change your thinking so you avoid them.

Pitfall 1: Starting without a good foundation

The React Hooks documentation is brilliant and I strongly recommend you give it a read through, especially the FAQ which has a LOT of helpful information. So give yourself a good hour or two and just read the docs without touching your keyboard. It'll give you a great overview of hooks conceptually and save you a lot of time.

Also, don't skip the React Conf talks by Sophie, Dan, and Ryan that introduce hooks.

To avoid the first pitfall: Read the docs and the FAQ 📚

Pitfall 2: Not using (or ignoring) the ESLint plugin

Around the time Hooks was released, the eslint-plugin-react-hooks package was built and released. It has two rules: "rules of hooks" and "exhaustive deps." The default recommended configuration of these rules is to set "rules of hooks" to an error, and the "exhaustive deps" to a warning.

I strongly advise that you install, use, and follow these rules. It will not only catch real bugs that you can easily miss, but it will also teach you things about your code and hooks in the process (not to mention the awesome auto-fix feature).

I've talked with a lot of people who are annoyed by the exhaustive deps plugin, so let me quickly demonstrate a scenario where ignoring that plugin can lead to a bug:

Imagine you have one screen that shows a list of dogs, and when you click on a dog, it takes you to another page that shows the information for that dog. Something like this:

Ok, so for the details page we make a DogInfo component which accepts a dogId prop and fetches the dog's information:

function DogInfo({ dogId }) {
	const [dog, setDog] = useState(null)
	// imagine you also have loading/error states. omitting to save space...

	useEffect(() => {
		getDog(dogId).then((d) => setDog(d))
	}, []) // 😱

	return <div>{/* render the dog info here */}</div>
}

We feel comfortable omitting dependencies in the array there because this request should only be made on mount. And with the way things are today we'll be ok. But now let's imagine that the UI changes a bit and we start listing a "related dogs" UI on this page. We're going to have a bug and clicking on the related dog won't update the dog information, even though the component is re-rendered! Observe (click "Poodle" then under "Related Dogs" click "Bernedoodle" and notice no change occurs):

So it's triggering a re-render of our DogInfo with a new dogId, but because we provided an empty array of dependencies, our effect is not re-running.

So let's add that dependency:

function DogInfo({ dogId }) {
	const [dog, setDog] = useState(null)
	// imagine you also have loading/error states. omitting to save space...

	useEffect(() => {
		getDog(dogId).then((d) => setDog(d))
	}, [dogId]) // ✅

	return <div>{/* render the dog info here */}</div>
}

Most excellent. Here's how it works now:

Here's the key takeaway from this example: If it really will never change, then there's no harm in including it anyway. Also, if you think it will never change and it does, including it will help you avoid bugs.

There are a lot of other situations which are more nasty and harder to identify/explain (like, if you skip adding a function to the dependencies list you could be calling a stale closure). Just trust me, every time I thought "oh, I don't need to follow the rule this time" I later regretted disabling it because I was wrong.

Please do note that sometimes the rule is incapable of performing static analysis on your code properly thanks to limitations of static analysis tools like ESLint. I believe this is why it's recommended to set the exhaustive deps rule to "warn" instead of "error." When this happens, the plugin will tell you so in the warning. I recommend that you try to restructure your code a bit to avoid that warning (please favor explicitness over cleverness). If that won't work, then disabling the plugin is your escape hatch so you can keep working.

To avoid the second pitfall: Install, use, and follow the ESLint plugin 👨‍🏫

Pitfall 3: Thinking in Lifecycles

For as long as React has been popular (before hooks), we had a nice and clear component API that made it easy for us to tell React when it should do certain things:

class LifecycleComponent extends React.Component {
	constructor() {
		// initialize component instance
	}
	componentDidMount() {
		// run this code when the component is first added to the page
	}
	componentDidUpdate(prevProps, prevState) {
		// run this code when the component is updated on the page
	}
	componentWillUnmount() {
		// run this code when the component is removed from the page
	}
	render() {
		// call me anytime you need some react elements...
	}
}

Writing components like this still works (and will for the foreseeable future), and it worked really well for many years. Hooks come with a lot of benefits, but one of my favorites is that it makes your components more declarative in that it allows you to stop thinking about "when things should happen in the lifecycle of the component" (which doesn't matter all that much) and more about "when things should happen in relation to state changes" (which matters much more).

So now we have:

function HookComponent() {
	React.useEffect(() => {
		// This side effect code is here to synchronize the state of the world
		// with the state of this component.
		return function cleanup() {
			// And I need to cleanup the previous side-effect before running a new one
		}
		// So I need this side-effect and it's cleanup to be re-run...
	}, [when, any, ofThese, change])

	React.useEffect(() => {
		// this side effect will re-run on every single time this component is
		// re-rendered to make sure that what it does is never stale.
	})

	React.useEffect(() => {
		// this side effect can never get stale because
		// it legitimately has no dependencies
	}, [])

	return /* some beautiful react elements */
}

Ryan Florence put it really well another way:

@dan_abramov @_developit @mjackson The question is not "when does this effect run" the question is "with which state does this effect synchronize with"
useEffect(fn) // all state
useEffect(fn, [])// no state
useEffect(fn, [these, states])

163331,267

The reason I love this so much is because it naturally helps me avoid bugs. So often I have found I had a bug in my code because I forgot to handle a prop or state update in componentDidUpdate, and when I did remember, I would often forget to cleanup the previous side-effect before starting up the new one (for example, if you make an HTTP request, but a prop changes before that request completes, you should cancel the previous request).

With React Hooks, you do still kinda think about when side effects should run, but you're not thinking about component Lifecycles, you're thinking about synchronizing the state of the side-effects with the state of the application. Grasping that requires a little bit of unlearning, but it's such a powerful idea that once you wrap your head around it, you will naturally experience fewer bugs in your apps thanks to the design of the API.

So when you're thinking: "Hey, my dependencies list needs to be []" don't do that because you think it only needs to run on mount, do it because you know that the stuff it's doing will never get stale.

To avoid this pitfall: Don't think about Lifecycles, think about synchronizing side effects to state 🔄

Pitfall 4: Overthinking performance

For some reason when some people see this, they freak out:

function MyComponent() {
	function handleClick() {
		console.log('clicked some other component')
	}
	return <SomeOtherComponent onClick={handleClick} />
}

There are 2 reasons people worry about this:

  1. We're defining the function inside the component, meaning it's getting re-defined every single time <MyComponent /> is rendered
  2. We're passing that newly defined function as a prop to <SomeOtherComponent /> which means it can't be optimized properly with React.memo, React.PureComponent, or shouldComponentUpdate and will suffer from "unnecessary re-renders"

For the first point, JavaScript engines (even those on low-end mobile devices) are extremely fast at defining functions. You're very unlikely to run into a problem with (re-)defining too many functions.

For the second point, "unnecessary re-renders" are not necessarily bad for performance. Just because a component re-renders, doesn't mean the DOM will get updated (updating the DOM can be slow). React does a great job at optimizing itself so you don't have to do weird things to your code to make it fast. It's fast by default.

If your app's unnecessary re-renders are causing your app to be slow, first investigate why renders are slow. If rendering your app is so slow that a few extra re-renders produces a noticeable slow-down, then you'll likely still have performance problems when you hit "necessary re-renders." Once you fix what's making the render slow, you may find that unnecessary re-renders aren't causing problems for you anymore.

If you do determine that unnecessary re-renders are causing you performance problems, then you can unpack the built-in performance optimization APIs that React has available to you like React.memo, React.useMemo, and React.useCallback. Learn more from my blog post useMemo and useCallback. Remember that sometimes you can apply performance optimizations and your app actually runs slower! So measure first!

Also remember that the production version of react is faster than the development version

To avoid this pitfall: Know that React is fast by default and do some digging before applying performance optimizations pre-maturely 🏎💨

Pitfall 5: Overthinking the testing of hooks

I've noticed some people are concerned that they need to rewrite their tests along with all of their components when they refactor to hooks. This may or may not be true depending on how your tests were written.

To borrow from my post "React Hooks: What's going to happen to my tests?", if you've written your tests like this:

test('setOpenIndex sets the open index state properly', () => {
	// using enzyme
	const wrapper = mount(<Accordion items={[]} />)
	expect(wrapper.state('openIndex')).toBe(0)
	wrapper.instance().setOpenIndex(1)
	expect(wrapper.state('openIndex')).toBe(1)
})

Then you can look at this as a great opportunity to improve your tests! You will definitely need to scrap that test and instead write it like this:

test('can open accordion items to see the contents', () => {
	const hats = { title: 'Favorite Hats', contents: 'Fedoras are classy' }
	const footware = {
		title: 'Favorite Footware',
		contents: 'Flipflops are the best',
	}
	const items = [hats, footware]
	// using React Testing Library
	render(<Accordion items={items} />)

	expect(screen.getByText(hats.contents)).toBeInTheDocument()
	expect(screen.queryByText(footware.contents)).toBeNull()

	userEvent.click(screen.getByText(footware.title))

	expect(screen.getByText(footware.contents)).toBeInTheDocument()
	expect(screen.queryByText(hats.contents)).toBeNull()
})

The key difference here is that the previous is testing the implementation details of the component and the new one is not. Whether your component is implemented via Hooks or as a class is an implementation detail of the component. Therefore, if your test is written in such a way that reveals that (like using .state() or .instance()), then refactoring your component to hooks will naturally cause your test to break.

But the end-user doesn't care about whether your components are written with hooks or classes. They just care about being able to interact with what those components render to the screen. So if your tests interact with what's being rendered, then it doesn't matter how that stuff gets rendered to the screen, it'll all work whether you're using classes or hooks.

You can learn more about this from Testing Implementation Details and Avoid the Test User.

So, to avoid this pitfall: Avoid testing implementation details 🔬

Conclusion:

To review, here are the bits of advice I'd like to give you to help you avoid some of the pitfalls of hooks:

  1. Read the docs and the FAQ 📚
  2. Install, use, and follow the ESLint plugin 👨‍🏫
  3. Don't think about Lifecycles, think about synchronizing side effects to state 🔄
  4. Know that React is fast by default and do some digging before applying performance optimizations pre-maturely 🏎💨
  5. Avoid testing implementation details 🔬

I hope that's helpful to you! Hooks have made my apps less buggy and made me more productive. While it cannot be denied that hooks have a learning curve (that can be sharper if you've been using React for a while), it's well worth the investment.