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

推荐订阅源

Hacker News - Newest:
Hacker News - Newest: "LLM"
Webroot Blog
Webroot Blog
S
Security @ Cisco Blogs
H
Heimdal Security Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com
N
News and Events Feed by Topic
H
Hacker News: Front Page
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
SecWiki News
SecWiki News
N
News | PayPal Newsroom
T
Tor Project blog
W
WeLiveSecurity
A
Arctic Wolf
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Secure Thoughts
月光博客
月光博客
AWS News Blog
AWS News Blog
D
Docker
C
CERT Recently Published Vulnerability Notes
MyScale Blog
MyScale Blog
Google Online Security Blog
Google Online Security Blog
大猫的无限游戏
大猫的无限游戏
T
The Blog of Author Tim Ferriss
I
InfoQ
人人都是产品经理
人人都是产品经理
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
Hacker News: Ask HN
Hacker News: Ask HN
Blog — PlanetScale
Blog — PlanetScale
博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
罗磊的独立博客
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
The Exploit Database - CXSecurity.com
D
DataBreaches.Net
S
Security Affairs
WordPress大学
WordPress大学
T
Threatpost
Microsoft Security Blog
Microsoft Security Blog
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
S
SegmentFault 最新的问题
B
Blog RSS Feed
Project Zero
Project Zero
P
Proofpoint News Feed

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
State Colocation will make your React app faster
2019-09-23 · via Kent C. Dodds Blog

Watch "Lifting and colocating React State" on egghead.io (part of The Beginner's Guide to ReactJS).

One of the leading causes to slow React applications is global state, especially the rapidly changing variety. Allow me to illustrate my point with a super contrived example, then I'll give you a slightly more realistic example so you can determine how it can be more practically applicable in your own app.

Here's the code for that

function sleep(time) {
	const done = Date.now() + time
	while (done > Date.now()) {
		// sleep...
	}
}

// imagine that this slow component is actually slow because it's rendering a
// lot of data (for example).
function SlowComponent({ time, onChange }) {
	sleep(time)
	return (
		<div>
			Wow, that was{' '}
			<input
				value={time}
				type="number"
				onChange={(e) => onChange(Number(e.target.value))}
			/>
			ms slow
		</div>
	)
}

function DogName({ time, dog, onChange }) {
	return (
		<div>
			<label htmlFor="dog">Dog Name</label>
			<br />
			<input id="dog" value={dog} onChange={(e) => onChange(e.target.value)} />
			<p>{dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'}</p>
		</div>
	)
}

function App() {
	// this is "global state"
	const [dog, setDog] = React.useState('')
	const [time, setTime] = React.useState(200)
	return (
		<div>
			<DogName time={time} dog={dog} onChange={setDog} />
			<SlowComponent time={time} onChange={setTime} />
		</div>
	)
}

Play around that for a second and you'll notice a significant performance problem when you interact with either field. There are various things that we can do to improve the performance of both the DogName and SlowComponent components on their own. We could pull out the rendering bailout escape hatches like React.memo and apply that all over our codebase where we have slow renders. But I'd like to propose an alternative solution.

If you haven't already read Colocation, then I suggest you give that a read. Knowing that colocation can improve the maintenance of our application, let's try colocating some state. Observe that the time state is used by every component in the App, which is why it was lifted to the App. However the dog state is only used by one component, so let's move that state to be colocated (updated lines are highlighted):

function DogName({ time }) {
	const [dog, setDog] = React.useState('')
	return (
		<div>
			<label htmlFor="dog">Dog Name</label>
			<br />
			<input id="dog" value={dog} onChange={(e) => setDog(e.target.value)} />
			<p>{dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'}</p>
		</div>
	)
}

function App() {
	// this is "global state"
	const [time, setTime] = React.useState(200)
	return (
		<div>
			<DogName time={time} />
			<SlowComponent time={time} onChange={setTime} />
		</div>
	)
}

And here's the result:

Wow! Typing in the dog name input is WAY better now. And what's more, the component's easier to maintain thanks to colocation. But how did it get faster?

I've heard it said that the best way to make something fast is to do less stuff. That's exactly what's going on here. When we manage the state higher up in the React component tree, every update to that state results in an invalidation of the entire React tree. React doesn't know what's changed, so it has to go and check all the components to determine whether they need DOM updates. That process is not free (especially when you have arbitrarily slow components). But if you move your state further down the React tree as we did with the dog state and the DogName component, then React has less to check. It doesn't even bother calling our SlowComponent because it knows that there's no way that could have changed output because it can't reference the changed state anyway.

In short, before, when we changed the dog name, every component had to be checked for changes (re-rendered). After, only the DogName component needed to be checked. This resulted in a big performance win! Sweet!

Real World

Where I see this principle apply in real-world applications is when people put things into a global Redux store or in a global context that don't really need to be global. Inputs like the DogName in the example above are often the places where this perf issue manifests itself, but I've also seen it happen plenty on mouse interactions as well (like showing a tooltip over a graph or table of data).

Often the solution that people try for this kind of problem is to "debounce" the user interaction (ie wait for the user to stop typing before applying the state update). This is sometimes the best we can do, but it definitely leads to a sub-optimal user experience (React's upcoming concurrent mode should make this less necessary in the future. Watch this demo from Dan about it).

Another solution people try is to apply one of React's rendering bailout escape hatches like React.memo. This works pretty well in our contrived example because it allows React to skip re-rendering our SlowComponent, but in a more practical scenario, you often suffer from "death by a thousand cuts" which means that there's not really a single place that's slow, so you wind up applying React.memo everywhere. And when you do that, you have to start using useMemo and useCallback everywhere as well (otherwise you undo all the work you put into React.memo). Each of these optimizations together may solve the problem, but it drastically increases the complexity of your application's code and it actually is less effective at solving the problem than colocating state because React does still need to run through every component from the top to determine whether it should re-render. You'll definitely be running more code with this approach, there's no way around that.

If you'd like to play around with a slightly less contrived example, give this codesandbox a look.

What is colocated state?

The principle of colocation is:

Place code as close to where it's relevant as possible

So, to accomplish this, we had our dog state inside the DogName component:

function DogName({ time }) {
	const [dog, setDog] = React.useState('')
	return (
		<div>
			<label htmlFor="dog">Dog Name</label>
			<br />
			<input id="dog" value={dog} onChange={(e) => setDog(e.target.value)} />
			<p>{dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'}</p>
		</div>
	)
}

But what happens when we break that up? Where does that state go? The answer is the same: "as close to where it's relevant as possible." That would be the closest common parent. As an example, let's break the DogName component up so the input and the p show up in different components:

function DogName({ time }) {
	const [dog, setDog] = React.useState('')
	return (
		<div>
			<DogInput dog={dog} onChange={setDog} />
			<DogFavoriteNumberDisplay time={time} dog={dog} />
		</div>
	)
}

function DogInput({ dog, onChange }) {
	return (
		<>
			<label htmlFor="dog">Dog Name</label>
			<br />
			<input id="dog" value={dog} onChange={(e) => onChange(e.target.value)} />
		</>
	)
}

function DogFavoriteNumberDisplay({ time, dog }) {
	return (
		<p>{dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'}</p>
	)
}

In this case we can't move the state to the DogInput component, because the DogFavoriteNumberDisplay needs access to that state, so we navigate up the tree until we find the least common parent of these two components and that's where the state is managed.

And this applies just as well as if your state needs to be accessed in dozens of components on a specific screen of your application. You can even put it into context to avoid prop drilling if you want. But keep that context value provider as close to where it's relevant as possible and you'll still benefit from the performance (and maintenance) characteristics of colocation. By this I mean that while some of your context providers could be rendered at the top of your application's React tree, they don't all have to be there. You can put them wherever they make the most sense.

This is the essence of what my Application State Management with React blog post is all about. Keep your state as close to where it's used as possible, and you'll benefit from a maintenance perspective and a performance perspective. From there, the only performance concerns you should have is the occasional especially complex UI interaction.

What about context or Redux?

If you read "One simple trick to optimize React re-renders," then you know that you can make it so only components that actually use the changing state will be updated. So that can side step this issue. While this is true, people do still have performance problems with Redux. If it's not React itself, what is it? The problem is that React-Redux expects you to follow guidelines to avoid unnecessary renders of connected components, and it can be easy to accidentally set up components that render too often when other global state changes. The impact of that becomes worse and worse as your app grows larger, especially if you're putting too much state into Redux.

Fortunately, there are things you can do to help reduce the impact of these performance issues, like using memoized Reselect selectors to optimize mapState functions, and the Redux docs have additional info on improving performance of Redux apps.

I also want to note that you can definitely apply colocation with Redux to get these benefits as well. Just limit what you store in Redux to be actual global state and colocate everything else and you're golden. The Redux FAQ has some rules of thumb to help decide whether state should go in Redux, or stay in a component.

In addition, if you separate your state by domain (by having multiple domain-specific contexts), then the problem is less pronounced as well.

But the fact remains that if you colocate your state, you don't have these problems and maintenance is improved.

So how do you decide where to put state?

I made this decision tree chart to help:

where to put react state

Chart perfected by Stephan Meijer

Here's that written out (for screen readers and friends):

  • 1 Start building an app. Go to 2
  • 2 useState. Go to 3
  • 3 used by only this component?
    • Yes: Go to 4
    • No: used by only one child?
      • Yes: Colocate state. Go to 3
      • No: used by a sibling/parent?
        • Yes: Lift state. Go to 3
        • No: Go to 4
  • 4 Leave it. Go to 5
  • 5 having a "prop drilling" problem?
    • Yes: Can child function outside of parent?
      • Yes: Move State to Context Provider. Go to 6
      • No: Use Component Composition. Go to 6
    • No: Go to 6
  • 6 Ship the app. As requirements change, Go to 1

It's important that this is something you do as part of your regular refactoring/app maintenance process. This is because lifting state up is a requirement of getting this working so it happens naturally, but your app will "work" whether you colocate your state or not, so being intentional about thinking through this is important to keep your app manageable and fast.

If you want to learn a bit more about that component composition step, read about that in One React mistake that's slowing you down.

Conclusion

In general, I think people are pretty good at "lifting state" as things change, but we don't often think to "colocate" state as things change in our codebase. So my challenge to you is to look through your codebase and look for opportunities to colocate state. Ask yourself "do I really need the modal's status (open/closed) state to be in Redux?" (the answer is probably "no"). Colocate your state and you'll find yourself with a faster, simpler codebase. Good luck!

P.S. I wrote another article that covers this topic from the perspective of forms and "perf death by a thousand cuts". Head on over to EpicReact.dev to read: Improve the Performance of your React Forms