theme | layout | highlighter | lineNumbers |
---|---|---|---|
slidev-theme-nearform |
default |
shiki |
false |
- This workshop is made of multiple, incremental steps
- It's built on top of Next.js, a convenient way to kickstart React applications
- At each step you are asked to add features and solve problems
- The
src/challenges
folder is where you should write your code - The
src/solutions
folder contains the solutions to the challenges - The 💡 icon indicates hints
- Read the code in the
src/solutions/step-{n}-{name}
folder - Visit the solution pages at
http://localhost:3000/solutions/step-{n}-{name}
- If you are running on CodeSandbox, replace
http://localhost:3000
with the url of your sandbox
- React provides Hooks to "hook" into the lifecycle of the React runtime
- These hooks can be abstracted into custom hooks to allow easier sharing of stateful and effectful logic
- We'll use this exercise to get familiar with the codebase and create a custom hook to fetch from our sample movies API
- 💡 You will work in
src/challenges/step-01-custom-hooks
Complete a custom Hook to query the most popular action movies of the current year from the TMDB API.
- A local version of the API is already provided
- Use the native browser's fetch api to request
/api/movies?year={year}
- You will need to use the built-in
useState
anduseEffect
hooks - The hook should accept the
year
as an argument, defaulting to the current year, and return:- The list of movies
- The loading status of the request
- Whenever the
year
changes, a new request should be made - 💡 The list of movies is in the
results
property of the response body
- Run
npm run dev
in your terminal - Go to
http://localhost:3000
in your web browser - See the list of movies appearing on the page
- Go to your sandbox url
- See the list of movies appearing on the page
- Hooks are a great way to share logic, but the data stored inside a Hook is isolated
- This means that two components using the same Hook are not necessarily accessing the same data
- To give multiple components access to the same data, we could pass the data as props from a shared parent
- This works fine for simple components tree, but deeper the tree then more you'll be repeating yourself, passing data through several nested children components, we call it "prop drilling" issue
- Abusing of prop drilling decreases performance, because all components touched by the prop drilling might re-render even if not using that variable for nothing else
- And, of course, repeating yourself so much makes it harder to maintain the code
- React also provides Context, which can be used to share state across components (and Hooks) without failing into prop drilling in case of several nested components
- Implement a year filter for the list of movies
- Use React's createContext to create a context for storing the filter state
- In the Provider component, keep a local
useState
whose value is an object with ayear
property - Populate the context provider
value
property with an object that has ayear
numeric value and asetYear
function, and wrap the children in the context provider - 💡 You can use
useMemo
for optimal performance - In the hook, use the
useContext
hook to replace the hardcoded values - 💡 You can always check out the solution for each challenge in the
src/solutions
folder, and see the running solution at{baseUrl}/solutions/{step name}
- Change the year in the top right of the app interface and press Enter or click the GO button
- Observe that the list of movies updates to reflect these changes
- This demonstrates that the filter data is being propagated throughout the app
- Components are usually rendered by React as children of other components with a shared parent in the DOM
- It is sometimes necessary to render components outside of this hierarchy (e.g. for message dialogs)
- Portals make it possible to render components outside of the current DOM tree while still allowing access to props inside of React's component tree
- Replace the whole body of
ToggleModalChallenge
with a button labeled Show Filters that opens a modal dialog - 💡 You will need to import the
useModal
hook insrc/context/ModalContext.js
and call itstoggle
method to open the dialog - 💡 You are replacing the filter form with a simple button - a new version of the form will be added to the modal in a later step
- In
FilterModalChallenge
, instead of directly returning theModalContainer
component, surround it with thecreatePortal
built-in Higher Order Component (HOC) - A div with the id
modal
has been setup already to be used as target DOM element for the modal. Check it out insrc/pages/_document.js
- Click on the Show filters button
- Observe that the modal shows correctly above all other page content
- In React, an error in a small component can crash the whole application
- We can improve the user experience with Error Boundaries, so only the affected component will be reactive to the error and not everything else
- Create an
ErrorBoundary
class component by copying and pasting the boilerplate code from error boundaries console.error
the error incomponentDidCatch
- Surround the
children
inErrorBoundaryChallenge
with this component
- Render
ExampleComponentWithError
as a sibling ofchildren
in theErrorBoundaryChallenge
component - Observe that the error is now caught and will display your custom component if this happens
- Remove the
ExampleComponentWithError
before proceeding to allow the app to run as normal
- It's normal to use controlled components to implement forms with React
- In controlled components, React manages the component's state
- This is not always desirable or possible (e.g. for file input data)
- Instead, we can create uncontrolled components
- The component state is then managed by the DOM and can be accessed using React refs
- Create a form containing a numeric input field with name
year
- Create a ref using
useRef
and assign it to the input field - Add an event handler to the form for the
onSubmit
event - 💡 Prevent the default behavior of the form submit event
- Read the current value from the year input ref as a number
- Use this value to update the filter state using the
useFilterStateChallenge
hook from the previous steps - 💡 Inspire from the solution code for helper components you can use
- Click on the Show filters button
- Type a year into the text box
- Press Enter or click submit
- The list of movies should change to show movies from the year you entered
- Notice that the component doesn't rerender when typing in the input box. This is thanks to the use of uncontrolled components
- 💡 You can use React Dev Tools to visualize component rerenders. More on this later
- We've previously used
refs
to access the value of an uncontrolled component - It is sometimes useful for a parent component to define a ref and pass it to a child component
- This gives the parent access to the ref that the child component assigns to one of its children
- React provides forwardRef to help achieve this
- This step builds upon the form you created in the last step
- Replace the body of the
FilterFormWithStyledInput.js
component with the code ofFilterFormChallenge.js
from the previous step - If you didn't manage to complete the last step you can copy and paste the code from
FilterFormSolution.js
instead
- Replace the original year input component with the
FancyInput
component - Click the Show filters button. This should generate a React error in the browser's console
- Fix the error by wrapping the
FancyInput
component in theforwardRef
HOC and making the necessary adjustments
- Click on the Show filters button
- You will see the input component now has a custom styling
- But also allows access to the underlying DOM element
refs
provide access to the DOM node for a component after it has been rendered- After accessing the DOM node, we can interact with it
- In this example, we'll ensure a form input receives the browser's focus after it has been rendered
- This step builds upon the form you created in the last step
- Copy and paste the code of the previous step from
FilterFormWithStyledInputChallenge.js
intoFilterFormWithAutofocusChallenge.js
- If you didn't manage to complete the last step you can copy and paste the code from
FilterFormWithStyledInputSolution.js
instead
- Make the year input element autofocus when it appears
- 💡 You will need to use
useEffect
along with the input ref created in the previous step - 💡 An input element can be focused by calling the
.focus()
method on it
- Click on the Show filters button
- The year input element should be automatically focused
- Code splitting and lazy loading allows you to optimize your user experience, by only loading code chunks as needed instead of loading everything at once
- Render the
DetailedHelpBox
component provided insrc/components
- Instead of directly importing it, lazy load it by using the React
lazy
HOC - Wrap it in a
Suspense
container and display a placeholder component with a "Loading..." message - Click on the Show filters button and observe the network request for a new javascript file
- To see the effect of loading the component lazily it's best to simulate a slow connection
- It can be set in the Network tab of the dev tools in Chrome (screenshot in next slide)
- You will now briefly see a loading message when you open the modal
- You can also see the request for the javascript file in the network tab
- Application performance can be affected if the code performs expensive calculations on each render
- Passing functions to child components can cause React to re-render them unnecessarily
- React provides a few hooks to help improve performance in these scenarios
- We will be optimizing the rerendering of
DarkModeButtonChallenge
so we need to know when it renders - A simple way to do this is to add a console log to the component's body. E.g.
console.log("dark mode button rendered")
- Another way would be to use the React dev tools chrome extension (see next slide)
- Clicking the Show filters button will cause a re-render of the Dark mode button as a side effect. This is unnecessary and we'll learn how to prevent it from happening
- Wrap the
DarkModeButtonChallenge
button in thememo
HOC to make sure the component only renders when its props change. This will prevent the component from rendering unless its props have changed - 💡 Note that the component will still render when its internal state changes
- Use the
useMemo
hook to optimize theThemeProviderChallenge
context - 💡 This will prevent the Provider's
value
from changing at each render, thereby causing a render of all the users of theuseTheme
hook
- Click the Dark mode button
- Notice that the dark mode button logs a rendered message
- Now click the Show filters button
- The dark mode button no longer logs a rendered message
- This shows that it has been optimized and doesn't render unnecessarily
- We can improve the performance of our apps by performing an initial render on the server
- This gives the client less work to do before it has something to display
- React can then take over on the client side, so we still benefit from our client side code
- Next.js provides functionality to perform server side renders, but the principles apply regardless of the framework you use
- Please note in this step you will need to update the code in
src/pages/index.js
to enable SSR functionality so open this file to get started - Look for the
useMovieQueryChallenge
hook and replace it with a call touseMovieQueryWithPreloadedData
passing the same first parameter but also includingpreloadedMoviesForDefaultYear
as a second parameter
- In
src/pages/index.js
export agetServerSideProps
async function that asynchronously calls themovieResultsFromDefaultYear()
function - Return an object with a
props
property that has a nestedpreloadedMovies
property with the movies data frommovieResultsFromDefaultYear()
. E.g.
{
props: {
preloadedMovies: dataFromFunctionCall
}
}
- Open the network tab of chrome dev tools (or equivalent in your browser of choice)
- Refresh the page
- Notice that the
movies?year={{ new Date().getFullYear() }}
is no longer called - The endpoint will only be called if you change the year in the filters