-
React = declarative UI programming. You define the target UI state - not the steps to get there;
-
import images only using
import
statement for better optimization:import logo from './assets/logo.png'; export function Header() { // NOT src="src/assets/logo.png" return <img src={logo} />; }
-
These two are equivalent:
function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = { firstName: 'Ben', lastName: 'Hector' }; return <Greeting {...props} />; }
-
JavaScript statements like function definitions, if-statements etc. can't bet used as part of your JSX code / between curly braces in JSX code.
-
By default, all CSS rules are scoped globally for the entire React application. This means that using the same className for 2 components will cause a collision.
import './componentA.css'; export const ComponentA = () => { return <div className="container">Component A</div>; };
-
props.children
- this is all the content that is between the opening and closing tags of this component; -
onClick={handleClick}
- naming convention for event handlers. -
Another naming convention for props (
on...
):export const TabButton = ({ onSelect }) => { return <button onClick={onSelect}>TEXT</button>; };
-
There are no built-in attributes for custom components. Therefore, even
id
andclassName
are consideredprops
and should be used inside the component asprops
.<CustomSection id="section" className="section">
-
A common way to forward down multiple props (e.g.
id
,className
) for wrapper elements:export function Section({ title, children, ...props }) { return ( <section {...props}> <h2>{title}</h2> {children} </section> ); }
-
It's possible to set component type dynamically:
export function Tabs({ buttons, CustomComponent }) { // variable should start with uppercase letter const ButtonsContainer = 'div'; // Or it can be custom component: const ButtonsContainer = CustomComponent; return <ButtonsContainer>{buttons}</ButtonsContainer>; }
-
All files stored in the
public
folder are publicly available (localhost:5173/some-image.jpg
) and will be served alongside with theindex.html
file, so they can be referenced globally inindex.html
,index.css
or in the any component:<img src="game-logo.png" />
-
If the new state is computed using the previous state, it's better to pass a function to setState. The function will receive the previous value, and return an updated value:
setCount((prevCount) => prevCount + 1);
-
event.target.value
from the input field is always of typeString
; -
import ./Header.css
- vanilla CSS styles are not scoped to components; -
import classes from './Header.module.css'
- css module styles are scoped; -
Styled Components
props
should be named with$
prefix to not clash build-in props:<StyledInput $invalid="{emailNotValid}" />
-
Don't forget about 2-way binding (
selected
prop in this case):export const ExpensesFilter = ({ selected, onChangeFilter }: Props) => { const handleChange = (event) => { onChangeFilter(event.target.value); }; return ( <select value={selected} onChange={handleChange}> ... </select> ); };
-
Strict Mode enables the following development-only behaviors:
- Components will re-render an extra time to find bugs caused by impure rendering.
- Components will re-run Effects an extra time to find bugs caused by missing Effect cleanup.
- Components will be checked for usage of deprecated APIs.
<StrictMode> <App /> </StrictMode>
-
Refs
- without:
export default function Player() { const [name, setName] = useState(null); const [enteredName, setEnteredName] = useState(null); const handleChange = (event) => { setName(event.target.value); }; const handleClick = () => { setEnteredName(name); setName(''); }; return ( <section id="player"> <h2>Welcome {enteredName || 'unknown entity'}</h2> <p> <input type="text" onChange={handleChange} value={name} /> <button onClick={handleClick}>Set Name</button> </p> </section> ); }
- with:
export default function Player() { const inputRef = useRef(); const [playerName, setPlayerName] = useState(''); const handleClick = () => { setPlayerName(inputRef.current.value); inputRef.current.value = ''; }; return ( <section id="player"> <h2>Welcome {playerName || 'unknown entity'}</h2> <p> <input type="text" ref={inputRef} /> <button onClick={handleClick}>Set Name</button> </p> </section> ); }
-
inputRef.current
is undefined during the first render, sinceinputRef
is not connected toinput
yet; -
Refs
can be used like instance fields in classes, i.e. if we want to store some value between component renders but not to update the UI when this value is changed (unlikestate
); -
Use
forwardRef
to pass theref
from one component to another; -
Use
useImperativeHandle
together withforwardRef
to expose the child's public API to the parent component; -
Prop Drilling
- Passing shared data through multiple component layers; -
The default value set when creating the context is only used if a component that was not wrapped by the
Provider
component tries to access the context value and for better autocompletion:export const CartContext = createContext({ items: [] });
-
callback inside
useEffect(cb, [deps])
is executed only after the component function is executed/rendered. And thiscb
is executed only once if the 2nd argument is an empty array[]
. Without 2nd argument it will be executed after every component render cycle; -
not every side effect needs
useEffect
. We need it only to prevent infinite loops or we have code that can only run after the component function is executed at least once; -
Cleanup function in
useEffect
runs every time before a new call ofuseEffect
callback function and after the component is removed from the DOM; -
If the
useEffect
dependency is a function created inside a component then it could lead to an infinite loop, since every render creates a new instance of that functionnew function(){} !== new function(){}
; -
To fix the issue above there is a
useCallback(cb, [deps])
hook that lets you cache a function definition between re-renders. If theuseCallback
dependencies change react will recreate (NOT CALL!!!
) a function;
-It's safe to omit the state updating functions (setState
) from the useEffect
or useCallback
dependency list. React guarantees that this won't change on re-renders;
-
The
key
prop can be used for any component (not only for lists). And ifkey
is changed then the component is fully recreated (like removed from and added to the DOM). Example from 9th project:<QuestionTimer key={QUESTIONS[activeQuestionIndex].id} time={3_000} onTimeout={handleSkipAnswer} />
-
const Component = memo(componentFn)
lets us skip re-rendering a component when its props are unchanged.
Use carefully:- us high up in the component tree as possible;
- checking props also costs performance;
- don't use where props will change frequently.
-
useMemo
lets us to memoize functions inside a component. Should also be used carefully:const isPrime = useMemo(() => isPrimeHeavyFunction(count), [count]);
-
React tracks
state
by component type and position of that component in the tree, that's why thekey
prop is needed; -
If
initialCount
prop will be changed in the parent component, internalcounter
state will not bew reinitialized:const Counter = ({ initialCount }) => { // executed only on the initial render const [counter, setCounter] = useState(initialCount); return <div>{counter}</div>; };
To fix this we could use
useEffect
, but it will be not optimal. The better way is to fully reset component usingkey
prop:<Counter key={count} initialCount={count} />
-
Multiple state updates that are triggered from the same function do not cause multiple re-renders. Instead, they batched together by React and only 1 render is performed;
-
For error boundary components we have to use
Class
-based components since the class hascomponentDidCatch
lifecycle method which can catch errors from the child components; -
If the
state
inside thecustom hook
is updated then the component that is using this state is also executed again like with regular component state; -
Why
Redux
library and not just built-inContext
?- In large enterprise apps it can be hard to read and maintain a lot of Contexts (or a 1 large universal Context);
- Performance. Context is great for low-frequency updates. Redux for high-frequency updates;
- redux devtools is also a very good plus in favor of redux;
-
We can use side-effects with Redux inside the component itself, without involving the Redux in it, or inside the action creator;
-
Routes that begin with
/
are absolute; -
We can preload data for the route component using
loader
:{ index: true; // path: '' element: <EventsPage />, loader: async () => { const response = await fetch('http://localhost:8080/events'); const resData = await response.json(); return resData.events; } }
-
We can submit data to the backend in the route component using
action
:{ index: true; // path: '' element: <EventDetailPage />, action: deleteEventAction, }
-
For
loaders
andactions
with a long delay we can provide feedback to he user about the progress usinguseNavigation().state
or returningdefer(...)
inside the loader; -
useFetcher
is used to trigger the action from the not current route;