🧘 Things I think about when I write React code 🧘
- star 数
- 3,734
- watcher 数
- 39
- fork 数
- 212
- issue 数
- 3
- 最終更新
- 2026/08/29
🧘 Things I think about when I write React code 🧘
If react-philosophies helped you in some way, consider buying me a few cups of coffee ☕☕☕. This motivates me to create more React "stuff"! 🙂
You have to think about what is the right way, even when you have the right idea of what the building blocks should be, there is huge flexibility in how you decide to put the whole system together. It is a craft... and it has a lot to do with valuing simplicity over complexity. Many people do have a tendency to make things more complicated than they need to be... The more stuff you throw into a system, the more complicated it gets and the more likely it is not going to work properly. - Barbara Liskov
If there's something that you think should be part of my reading list or/and if you have great ideas that you think I should include here, don't hesitate to submit a PR or an issue. Particularly, I've included the section Insights shared by others, should you wish to add your own ideas 🙂. Broken links, grammar, formatting, and typographical error corrections are also welcome. Any contribution to improve react-philosophies whether big or small is always appreciated.
Note, the following is not an exhaustive list, if you've contributed to this project and you don't see your name included, feel free to submit an PR that adds your name here. Thanks!
💡 Comments and suggestions
r/reactjs community☕ Coffee!
🚜 Pull Requests
📚 Suggested Readings
goldbergyoni/nodebestpractices, recommended by @rstacruzreact-philosophies is:
React code.React specifically 🙂react-philosophies is inspired by various places I've stumbled upon at different points of my coding journey.
Here are a few of them:
As a seasoned developer I have certain quirks, opinions, and common patterns that I fall back on. Having to explain to another person why I am approaching a problem in a particular way is really good for helping me break bad habits and challenge my assumptions, or for providing validation for good problem solving skills. - Coraline Ada Ehmke
ESLint. Enable the rule-of-hooks and exhaustive-deps rule to catch React-specific errors.exhaustive-deps warnings / errors on your useMemo's, useCallback's and useEffect's. You can try "The latest ref pattern" to keep your callbacks always up-to-date without unnecessary rerenders.map to display components.tree-shaking. Prettier. Typescript."The best code is no code at all. Every new line of code you willingly bring into the world is code that has to be debugged, code that has to be read and understood, code that has to be supported." - Jeff Atwood
"One of my most productive days was throwing away 1000 lines of code." - Eric S. Raymond
See also: Write Less Code - Rich Harris, Code is evil - Artem Sapegin
Needless to say, the more you add dependencies, the more code you ship to the browser. Ask yourself, are you actually using the features which make a particular library great?
🙈 Do you really need it? View examples of dependencies / code you might not needDo you really need Redux? It's possible. But keep in mind that React is already a state management library.
Do you really need Apollo client ? Apollo client has many awesome features, like manual normalization. However, it will significantly increase your bundle size. If your application only makes use of features that are not unique to Apollo client , consider using a smaller library such as react-query or SWR (or none at all).
Axios? Axios is a great library with features that are not easily replicable with native fetch. But if the only reason for using Axios is that it has a better looking API, then consider just using a wrapper on top of fetch (such as redaxios or your own). Determine whether or not your application is actually using Axios's best features.
Decimal.js? Maybe Big.js or other smaller libraries are sufficient.
Lodash/underscoreJS? you-dont-need/You-Dont-Need-Lodash-Underscore
You might not need Context for theming (light/dark mode), consider using css variables instead.
You might not even need Javascript. CSS is powerful. you-dont-need/You-Dont-Need-JavaScript
"What could happen with my software in the future? Oh yeah, maybe this and that. Let’s implement all these things since we are working on this part anyway. That way it’s future-proof."
You Aren't Gonna Need It! Always implement things when you actually need them, never when you just foresee that you may need them. The less code the better! (Martin Fowler: YAGNI, C2 Wiki: You Arent Gonna Need It!)
Related section: 2.4 Duplication is far cheaper than the wrong abstraction
1.3.1 Detect code smells and do something about them if you need to.
If you recognize that something is wrong, fix it right then and there. But if it's not that easy to fix or you don't have time to fix it at that moment, at least add a comment (FIXME or TODO) with a concise explanation of the identified problem. Make sure everybody knows it is broken. It shows others that you care and that they should also do the same when they encounter those kinds of things.
Keep in mind that code smells don't necessarily mean that code should be changed. A code smell just informs you that you might be able to think of a better way to implement the same functionality.
1.3.2 Merciless Refactoring. Simple is better than complex.
Is the CL more complex than it should be? Check this at every level of the CL—are individual lines too complex? Are functions too complex? Are classes too complex? “Too complex” usually means “can’t be understood quickly by code readers.” It can also mean “developers are likely to introduce bugs when they try to call or modify this code.”- Google Engineering Practices: What to look for in a code review
💁♀️ TIP: Simplify complex conditionals and exit early if you can.
🙈 Example of early returns# ❌ Not-so-good if (loading) { return <LoadingScreen /> } else if (error) { return <ErrorScreen /> } else if (data) { return <DataScreen /> } else { throw new Error('This should be impossible') } # ✅ BETTER if (loading) { return <LoadingScreen /> } if (error) { return <ErrorScreen /> } if (data) { return <DataScreen /> } throw new Error('This should be impossible')
💁♀️ TIP: Prefer chained higher-order functions over loops
If there is no discernable performance difference and if possible, replace traditional loops with chained higher-order functions (map, filter, find, findIndex, some, etc) Stackoverflow: What is the advantage of using a function over loops?
💁♀️ TIP: Remember that you may not need to put your state as a dependency because you can pass a callback function instead.
You don't need to put setState (from useState) and dispatch (from useReducer) in your dependency array for hooks like useEffect and useCallback. ESLint will NOT complain because React guarantees their stability.
❌ Not-so-good const decrement = useCallback(() => setCount(count - 1), [setCount, count]) const decrement = useCallback(() => setCount(count - 1), [count]) ✅ BETTER const decrement = useCallback(() => setCount(count => (count - 1)), [])
💁♀️ TIP: If your useMemo or useCallback doesn't have a dependency, you might be using it wrong.
❌ Not-so-good const MyComponent = () => { const functionToCall = useCallback(x: string => `Hello ${x}!`,[]) const iAmAConstant = useMemo(() => { return {x: 5, y: 2} }, []) /* I will use functionToCall and iAmAConstant */ } ✅ BETTER const I_AM_A_CONSTANT = { x: 5, y: 2 } const functionToCall = (x: string) => `Hello ${x}!` const MyComponent = () => { /* I will use functionToCall and I_AM_A_CONSTANT */ }
💁♀️ TIP: Wrapping your custom context with a hook creates a better-looking API
Not only does it look better, but you also only have to import one thing instead of two.
🙈 View example❌ Not-so-good
// you need to import two things every time import { useContext } from "react" import { SomethingContext } from "some-context-package" function App() { const something = useContext(SomethingContext) // looks okay, but could look better // blah }
✅ Better
// on one file you declare this hook function useSomething() { const context = useContext(SomethingContext) if (context === undefined) { throw new Error('useSomething must be used within a SomethingProvider') } return context } // you only need to import one thing each time import { useSomething } from "some-context-package" function App() { const something = useSomething() // looks better // blah }
💁♀️ TIP: Think about how your component will be used before coding it
Writing APIs is hard. README Driven Development is one useful technique to help design better APIs. I'm not saying we should RDD religiously, just saying that the idea behind it is great. I find that when I first write the API (how the component will be used) before implementing it, this usually creates a better designed component than when I don't.
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." - Martin Fowler
"The ratio of time spent reading versus writing is well over 10 to 1. We are constantly reading old code as part of the effort to write new code. So if you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to read." ― Robert C. Martin (Not saying I agree with his political views)
TL;DR
Context is not the solution for every state sharing problemuseEffects to smaller independent ones (KCD: Myths about useEffect)useCallback, useMemo, and useEffectuseCallback, useMemo, and useEffectuseStates, consider using useReducer if some values of your state rely on other values of your state and previous stateContext does not have to be global to your whole app. Put Context as low as possible in your component tree. Do this the same way you put variables, comments, states (and code in general) as close as possible to where they're relevant / being used.When you have redundant states, some states may fall out of sync; you may forget to update them given a complex sequence of interactions. Aside from avoiding synchronization bugs, you'd notice that it's also easier to reason about and require less code.
📝🖊️ View the business requirement / problem statementYou are tasked to display properties of a right triangle
The triangle is an object with two numbers {a: number, b: number} that should be fetched from an API.
The two numbers represent the two shorter sides of a right triangle.
const TriangleInfo = () => { const [triangleInfo, setTriangleInfo] = useState<{a: number, b: number} | null>(null) const [hypotenuse, setHypotenuse] = useState<number | null>(null) const [perimeter, setPerimeter] = useState<number | null>(null) const [areas, setArea] = useState<number | null>(null) useEffect(() => { fetchTriangle().then(t => setTriangleInfo(t)) }, []) useEffect(() => { if(!triangleInfo) { return } const { a, b } = triangleInfo const h = computeHypotenuse(a, b) setHypotenuse(h) const newArea = computeArea(a, b) setArea(newArea) const p = computePerimeter(a, b, h) setPerimeter(p) }, [triangleInfo]) if (!triangleInfo) { return null } /*** show info here ****/ }
const TriangleInfo = () => { const [triangleInfo, setTriangleInfo] = useState<{ a: number; b: number; } | null>(null) useEffect(() => { fetchTriangle().then((r) => setTriangleInfo(r)) }, []); if (!triangleInfo) { return } const { a, b } = triangeInfo const area = computeArea(a, b) const hypotenuse = computeHypotenuse(a, b) const perimeter = computePerimeter(a, b, hypotenuse) /*** show info here ****/ };
Suppose you are assigned to design a component which:
x or y (ascending order)maxDistance (increase by 10 each time, initial value should be 100)maxDistance from the origin (0, 0)useMemo.type SortBy = 'x' | 'y' const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x' const Points = () => { const [points, setPoints] = useState<{x: number, y: number}[]>([]) const [filteredPoints, setFilteredPoints] = useState<{x: number, y: number}[]>([]) const [sortedPoints, setSortedPoints] = useState<{x: number, y: number}[]>([]) const [maxDistance, setMaxDistance] = useState<number>(100) const [sortBy, setSortBy] = useState<SortBy>('x') useEffect(() => { fetchPoints().then(r => setPoints(r)) }, []) useEffect(() => { const sorted = sortPoints(points, sortBy) setSortedPoints(sorted) }, [sortBy, points]) useEffect(() => { const filtered = sortedPoints.filter(p => getDistance(p.x, p.y) < maxDistance) setFilteredPoints(filtered) }, [sortedPoints, maxDistance]) const otherSortBy = toggle(sortBy) const pointToDisplay = filteredPoints.map( p => <li key={`${p.x}|{p.y}`}>({p.x}, {p.y})</li> ) return ( <> <button onClick={() => setSortBy(otherSortBy)}> Sort by: {otherSortBy} <button> <button onClick={() => setMaxDistance(maxDistance + 10)}> Increase max distance <button> Showing only points that are less than {maxDistance} units away from origin (0, 0) Currently sorted by: '{sortBy}' (ascending) <ol>{pointToDisplay}</ol> </> ) }
// NOTE: You can also use useReducer instead type SortBy = 'x' | 'y' const toggle = (current: SortBy): SortBy => current === 'x' ? : 'y' : 'x' const Points = () => { const [points, setPoints] = useState<{x: number, y: number}[]>([]) const [maxDistance, setMaxDistance] = useState<number>(100) const [sortBy, setSortBy] = useState<SortBy>('x') useEffect(() => { fetchPoints().then(r => setPoints(r)) }, []) const otherSortBy = toggle(sortBy) const filtedPoints = points.filter(p => getDistance(p.x, p.y) < maxDistance) const pointToDisplay = sortPoints(filteredPoints, sortBy).map( p => <li key={`${p.x}|{p.y}`}>({p.x}, {p.y})</li> ) return ( <> <button onClick={() => setSortBy(otherSortBy)}> Sort by: {otherSortBy} <button> <button onClick={() => setMaxDistance(maxDistance + 10)}> Increase max distance <button> Showing only points that are less than {maxDistance} units away from origin (0, 0) Currently sorted by: '{sortBy}' (ascending) <ol>{pointToDisplay}</ol> </> ) }
You wanted a banana but what you got was a gorilla holding the banana and the entire jungle. - Joe Armstrong
To avoid falling into this trap, it's a good idea to pass mostly primitives (boolean, string, number, etc) types as props. (Passing primitives is also a good idea if you want to use React.memo for optimization)
A component should just know enough to do its job and nothing more. As much as possible, components should be able to collaborate with others without knowing what they are and what they do.
When we do this, the components will be more loosely coupled, the degree of dependency between two components will be lower. Loose coupling makes it easier to change, replace, or remove components without affecting other components. stackoverflow:2832017
📝🖊️ View the business requirement / problem statementCreate a MemberCard component that displays two components: Summary and SeeMore.
The MemberCard takes in the prop id . The MemberCard consumes the hook useMember which takes in an id and returns the corresponding Member information.
type Member = { id: string firstName: string lastName: string title: string imgUrl: string webUrl: string age: number bio: string /****** 100 more fields ******/ }
The SeeMore component should display the age and bio of the member.
Include a button to toggle between showing and hiding the age and bio of the member.
The Summary component displays the picture of the member.
It also displays his title, firstName and lastName (e.g. Mr. Vincenzo Cassano).
Clicking the member's name should take you to the member's personal site.
The Summary component may also have other functionalities.
(Example, whenever this component is clicked...
the font, size of the image, and background color are randomly changed...
for brevity let's call this "the random styling feature")
const Summary = ({ member } : { member: Member }) => { /*** include "the random styling feature" ***/ return ( <> <img src={member.imgUrl} /> <a href={member.webUrl} > {member.title}. {member.firstName} {member.lastName} </a> </> ) } const SeeMore = ({ member }: { member: Member }) => { const [seeMore, setSeeMore] = useState<boolean>(false) return ( <> <button onClick={() => setSeeMore(!seeMore)}> See {seeMore ? "less" : "more"} </button> {seeMore && <>AGE: {member.age} | BIO: {member.bio}</>} </> ) } const MemberCard = ({ id }: { id: string })) => { const member = useMember(id) return <><Summary member={member} /><SeeMore member={member} /></> }
const Summary = ({ imgUrl, webUrl, header }: { imgUrl: string, webUrl: string, header: string }) => { /*** include "the random styling feature" ***/ return ( <> <img src={imgUrl} /> <a href={webUrl}>{header}</a> </> ) } const SeeMore = ({ componentToShow }: { componentToShow: ReactNode }) => { const [seeMore, setSeeMore] = useState<boolean>(false) return ( <> <button onClick={() => setSeeMore(!seeMore)}> See {seeMore ? "less" : "more"} </button> {seeMore && <>{componentToShow}</>} </> ) } const MemberCard = ({ id }: { id: string }) => { const { title, firstName, lastName, webUrl, imgUrl, age, bio } = useMember(id) const header = `${title}. ${firstName} ${lastName}` return ( <> <Summary {...{ imgUrl, webUrl, header }} /> <SeeMore componentToShow={<>AGE: {age} | BIO: {bio}</>} /> </> ) }
Notice that in the ✅ "better" solution", SeeMore and Summary are components that can be used not just by Member. It can be used perhaps by other objects such as CurrentUser, Pet, Post... anything that needs those specific functionality.
What is the single responsibility principle?
A component should have one and only one job. It should do the smallest possible useful thing. It only has responsibilities that fulfill its purpose.
A component with various responsibilities is difficult to reuse. If you want to reuse some but not all of its behavior, it's almost always impossible to just get what you need. It is also likely to be entangled with other code. Components that do one thing which isolate that thing from the rest of your application allows change without consequence and reuse without duplication.
How to know if your component has a single responsibility?
Try to describe that component in one sentence. If it is only responsible for one thing then it should be simple to describe. If it uses the word ‘and’ or ‘or’ then it is likely that your component fails this test.
Inspect the component's state, the props and hooks it consumes, as well as variables and methods declared inside the component (there shouldn't be too many). Ask yourself: Do these things actually work together to fulfill the component's purpose? If some of them don't, consider moving those somewhere else or breaking down your big component into smaller ones.
(The paragraphs above are based on my 2015 article: Three things I learned from Sandi Metz’s book as a non-Ruby programmer)
📝🖊️ View the business requirement / problem statementThe requirement is to display special kinds of buttons you can click to shop for items of a specific category. For example, the user can select bags, chairs, and food.
WavingHand componentgreygreenredtype ShopCategoryTileProps = { isBooked: boolean icon: ReactNode label: string componentInsideModal?: ReactNode items?: {name: string, quantity: number}[] } const ShopCategoryTile = ({ icon, label, items componentInsideModal, }: ShopCategoryTileProps ) => { const [openDialog, setOpenDialog] = useState(false) const [hover, setHover] = useState(false) const disabled = !items || items.length === 0 return ( <> <Tooltip title="Not Available" show={disabled}> <StyledButton className={disabled ? "grey" : isBooked ? "green" : "red" } disabled={disabled} onClick={() => disabled ? null : setOpenDialog(true) } onMouseEnter={() => disabled ? null : setHover(true)} onMouseLeave={() => disabled ? null : setHover(false)} > {icon} <StyledLabel>{label}<StyledLabel/> {!disabled && isBooked && <FaCheckCircle/>} {!disabled && hover && <WavingHand />} </StyledButton> </Tooltip> {componentInsideModal && <Dialog open={openDialog} onClose={() => setOpenDialog(false)}> {componentInsideModal} </Dialog> } </> ) }
// split into two smaller components! const DisabledShopCategoryTile = ({ icon, label }: { icon: ReactNode, label: string }) => { return ( <Tooltip title="Not available"> <StyledButton disabled={true} className="grey"> {icon} <StyledLabel>{label}<StyledLabel/> </Button> </Tooltip> ) } type ShopCategoryTileProps = { icon: ReactNode label: string isBooked: boolean componentInsideModal: ReactNode } const ShopCategoryTile = ({ icon, label, isBooked, componentInsideModal, }: ShopCategoryTileProps ) => { const [openDialog, setOpenDialog] = useState(false) const [hover, setHover] = useState(false) return ( <> <StyledButton disabled={false} className={isBooked ? "green" : "red"} onClick={() => setOpenDialog(true) } onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} > {icon} <StyledLabel>{label}<StyledLabel/> {isBooked && <FaCheckCircle/>} {hover && <WavingHand />} </StyledButton> {openDialog && <Dialog onClose={() => setOpenDialog(false)}> {componentInsideModal} </Dialog> }} </> ) }
Note: The example above is a simplified version of a component that I've actually seen in production
❌ View a not-so-good solutionconst ShopCategoryTile = ({ item, offers, }: { item: ItemMap; offers?: Offer; }) => { const dispatch = useDispatch(); const location = useLocation(); const history = useHistory(); const { items } = useContext(OrderingFormContext) const [openDialog, setOpenDialog] = useState(false) const [hover, setHover] = useState(false) const isBooked = !item.disabled && !!items?.some((a: Item) => a.itemGroup === item.group) const isDisabled = item.disabled || !offers const RenderComponent = item.component useEffect(() => { if (openDialog && !location.pathname.includes("item")) { setOpenDialog(false) } }, [location.pathname]); const handleClose = useCallback(() => { setOpenDialog(false) history.goBack() }, []) return ( <GridStyled xs={6} sm={3} md={2} item booked={isBooked} disabled={isDisabled} > <Tooltip title="Not available" placement="top" disableFocusListener={!isDisabled} disableHoverList