Skip to main content

The art of conditional rendering: Tips and tricks for React and Next.js developers

Escrito por:

Kumar Harsh

blog-feature-open-source-security

30 de outubro de 2023

0 minutos de leitura

Conditional rendering is a technique used in web applications to selectively render a component out of a set of candidates based on some condition, such as user authentication status, user privilege, or application state. It can also be used to implement a wide range of core React UI concepts, such as client-side routing and lazy loading.

In this article, you'll learn about the benefits of conditional rendering, how it differs from conditional routing, and how to implement both in React, Next.js, and Remix.

Benefits of conditional rendering in React

In apps made using React or React-like frameworks, utilizing conditional rendering comes with a host of benefits, including faster load times.

The load time of a web application is influenced by the Document Object Model (DOM) size of the page being loaded. Keeping too many elements, especially those that are not displayed unless the user scrolls down, may lead to an unnecessarily large DOM size and reduce your application's performance.

In this situation, lazy loading, a popular technique used to defer the loading of resources until needed, can be implemented to defer rendering a component unless the user scrolls down to bring it into the viewport. One of the most popular lazy loading libraries in React, react-lazyload, makes use of conditional rendering to render components only when they are scrolled into the viewport of the user's browser or, in other words, are visible to the user.

In addition to performance, conditional rendering can also help improve user experience. It enables you to customize your UI based on the user's properties, such as authentication state (ie a login button for unauthenticated users and a logout button for authenticated users in the same place on the screen) and access privilege (ie editing controls/detailed analytics for owners of content, such as tweets, while showing limited information for non-owners).

Moreover, conditional rendering can help you gracefully handle client-side operations, such as data fetching and communication with the backend. You can conditionally render a loading bar while an operation is carried out or hide an empty list while data is loaded from a remote source.

Conditional rendering also enables client-side routing in single-page React apps, such as the react-router package.

In summary, it's safe to say that conditional rendering is one of the most important techniques used in React.

Conditional rendering vs. conditional routing

When discussing conditional rendering, the topic of conditional routing often comes up. Conditional routing is similar to conditional rendering in that it controls what the user gets to see based on conditions such as the user or application state. However, instead of rendering or destroying components, it navigates the user from one route (or page) to another. This is useful from an access control perspective.

Web apps can be accessed directly via URLs. This means that the URL of a page inside of your app, regardless of whether the page is public, can be captured and retained by users. In the case of privileged pages (nonpublic pages that are only accessible to users with certain roles, such as personal profile/preferences pages), the URLs can be misused to try to gain access to privileged content.

With the help of conditional routing, a React app can determine if an incoming request for a page render has the right privilege to access the route. If the privilege is not found, the render is denied, and the request is redirected to another page. The page the user is redirected to often asks to authenticate or informs the user that access has been denied.

For example, to experience conditional routing in action, try navigating to https://mail.google.com/mail in a new incognito window. When you do, you'll be redirected to the Google sign-in page. That's conditional routing working behind the scenes.

However, conditional routing in an SPA is not a guarantee for security. Even before your (single page) React app redirects unauthenticated users away, it still contains the contents of the privileged page in its JavaScript source code. This could lead to incidents of unauthorized access to privileged data. Therefore, it's best to set up authentication-related conditional routing server side using frameworks such as Next.js and Remix.

Implementing conditional rendering in React

Now that you know more about conditional rendering, in the following section, you'll learn how to implement it in a React app using one of the fundamental concepts of programming — the if…else statement.

To get started, create a new React app by running the following commands:

1npx create-react-app conditional-rendering-react
2cd conditional-rendering-react
3npm install

Once the app is ready, paste the following code into the src/App.js file:

1import { useState } from "react"
2
3// Define two components
4const LoginButton = (props) => {
5  return <>
6  <div>You are logged out</div>
7  <div><button onClick={props.toggleLoggedIn}>Log In</button></div>
8  </>
9}
10
11const LogoutButton = (props) => {
12  return <>
13  <div>You are now logged in</div>
14  <div><button onClick={props.toggleLoggedIn}>Log Out</button></div>
15  </>
16}
17
18function App() {
19  // Define a state that the condition will be based upon
20  const [loggedIn, setLoggedIn] = useState(false);
21
22  // Define a function that can change the condition value
23  const toggleLoggedIn = () => {
24    setLoggedIn((currState) => {
25        return !currState;
26    })
27  }
28
29  // Use an if...else statement to render one out of two components based on the login state
30  if (loggedIn)
31    return <LogoutButton toggleLoggedIn={toggleLoggedIn} />
32  else
33    return <LoginButton toggleLoggedIn={toggleLoggedIn} />
34}
35
36export default App;

This code has inline comments to explain how everything has been implemented.

Next, try running the app using the following command:

1npm start

You'll see a text and a button on the screen when you navigate to the app URL (ie http://localhost:3000):

blog-conditional-render-logged-out-2

Try clicking on the Log In button to change the state and trigger a re-render. You'll notice that both the text and the button are changed with those applicable to a logged-in user:

blog-conditional-render-logged-in-2

And that's it! That's how easy it is to implement conditional rendering in React. Next, you'll see how to do it in a Next.js app.

Implementing conditional rendering and conditional routing in Next.js

Next.js is an opinionated, production-focused, open source, React-based framework that helps you to build highly performant web apps. It was released by Vercel in 2016 and has since grown to become one of the top React-based frameworks in the web development industry.

Out of the box, Next.js supports server-side rendering and static rendering, and it provides easy-to-use solutions for common development problems, such as data fetching, routing, and server runtimes.

How to implement conditional rendering in Next.js

Because the syntax for JSX is consistent across Next.js and React, you can apply conditional rendering techniques in Next.js just as you would in React.

To get started in Next.js, create a new Next.js app by running the following command:

1npx create-next-app conditionals-next

Follow the on-screen prompts to create your app. You can choose the default options (except for TypeScript, where you’ll need to select No). Once the project is created, paste the following code in the pages/index.js file:

1import { useState } from "react";
2
3// Define two components
4const LoginButton = (props) => {
5  return <>
6    <div>You are logged out</div>
7    <div><button onClick={props.toggleLoggedIn}>Log In</button></div>
8  </>
9}
10
11const LogoutButton = (props) => {
12  return <>
13    <div>You are now logged in</div>
14    <div><button onClick={props.toggleLoggedIn}>Log Out</button></div>
15  </>
16}
17
18export default function Home() {
19  // Define a state that the condition will be based upon
20  const [loggedIn, setLoggedIn] = useState(false);
21
22  // Define a function that can change the condition value
23  const toggleLoggedIn = () => {
24    setLoggedIn((currState) => {
25        return !currState;
26    })
27  }
28
29  // Use the ternary conditional operator to render one out of two components based on login state
30  return loggedIn 
31  ? <LogoutButton toggleLoggedIn={toggleLoggedIn} /> 
32  : <LoginButton toggleLoggedIn={toggleLoggedIn} />
33}

This code contains inline comments to explain what's happening at each step. A notable change here compared to the React example is that this example makes use of the ternary conditional operator in JavaScript to choose one out of two components when rendering. You can run the app using the following command:

1npm run dev

Once the app starts, you'll see something like this at http://localhost:3000:

blog-conditional-render-logged-out-black

Once again, you can try clicking on the Log In button to change the state:

blog-conditional-render-youre-logged-in-black

How to implement conditional routing in Next.js

Implementing conditional routing in Next.js 10+ is also easy. To get started, create a new profile.js file in your conditionals-next/pages directory and paste the following code snippet in it:

1// This is the profile page
2const Profile = () => {
3  return <div>This is your profile</div>
4}
5
6// This function is called on the server side before the page is rendered
7// Redirecting from this method means that the user will not see the page even for a flash of a second (which often happens in client-side redirection)
8export async function getServerSideProps(context) {
9    const allowAccess = false; // This is usually deduced from the authentication state of the user
10
11    if (!allowAccess) {
12
13        console.log("Redirecting to home...")
14
15        return {
16            redirect: {
17                permanent: false,
18                destination: "/"
19            }
20        }
21    } else
22        return { props: {} }
23}
24
25export default Profile

Then try running the app again and navigating to http://localhost:3000/profile. You will be instantly redirected to the home page (ie the / route). You can check the terminal to find the redirection log:

1# ... other output here
2wait  - compiling...
3event - compiled client and server successfully in 109 ms (199 modules)
4wait  - compiling /profile (client and server)...
5event - compiled client and server successfully in 114 ms (202 modules)
6Redirecting to home...

If you change the value of allowAccess to true, you can view the profile page:

blog-conditional-render-your-profile-black

Implementing conditional rendering and conditional routing in Remix

Remix is another growing React-based framework for building web apps that focus on web standards and help you build better user experiences. Remix became open source in 2021 and has become a strong competitor of Next.js.

Remix is a fully server-side rendered framework and offers features such as built-in error boundaries and transition handling.

How to implement conditional rendering in Remix

Since Remix is based on React, you can implement conditional rendering in Remix similar to how you did in React and Next.js.

To get started, create a new Remix project by running the following command:

1npx create-remix@latest

Follow the prompt to create your project. Make sure to choose JavaScript over TypeScript. Once the project is created, paste the following code snippet at app/routes/index.jsx:

1import { useState } from "react";
2
3// Define two components
4const LoginButton = (props) => {
5  return <>
6    <div>You are logged out</div>
7    <div><button onClick={props.toggleLoggedIn}>Log In</button></div>
8  </>
9}
10
11const LogoutButton = (props) => {
12  return <>
13    <div>You are now logged in</div>
14    <div><button onClick={props.toggleLoggedIn}>Log Out</button></div>
15  </>
16}
17
18export default function Index() {
19  // Define a state that the condition will be based upon
20  const [loggedIn, setLoggedIn] = useState(false);
21
22  // Define a function that can change the condition value
23  const toggleLoggedIn = () => {
24    setLoggedIn((currState) => {
25        return !currState;
26    })
27  }
28
29  // Use a switch statement to render one of two components based on login state
30  switch(loggedIn) {
31    case true: return <LogoutButton toggleLoggedIn={toggleLoggedIn} />
32    case false: return <LoginButton toggleLoggedIn={toggleLoggedIn} />
33    default: return null
34  }
35}

This code snippet has inline comments to explain what's happening at each step. Instead of using an if...else statement or a ternary conditional operator, this example uses a switch statement to decide which component to render.

Similar to the previous examples, here's what the home page will look like at http://localhost:3000:

blog-conditional-render-youre-logged-out-white

As you've done previously, you can try clicking on the Log In button to change the state:

blog-conditional-render-youre-logged-in-white

How to implement conditional routing in Remix

Similar to how you implemented server-side conditional routing in Next.js, you can implement it in Remix as well. Create a new file, profile.jsx, in the conditionals-remix/app/routes directory and paste the following code in it:

1import { redirect } from '@remix-run/node'
2
3const Profile = () => {
4    return <div>This is your profile</div>
5}
6
7export const loader = () => {
8    const allowAccess = false; // This is usually deduced from authentication state of the user
9
10    if (!allowAccess) {
11        console.log("Redirecting to home...")
12
13        return redirect('/')
14    }
15
16    return null
17}
18
19export default Profile

A loader function is used here instead of the getServerSideProps function since Remix supports loaders for loading data in components while they are being rendered on the server (which is similar to what getServerSideProps can do for Next.js). In addition, a redirect utility function (similar to the Next.js redirect function) is returned to initiate a redirect when allowAccess is set to false.

Navigate to http://localhost:3000/profile, and once again, you'll be instantly redirected to the home page (ie the / route). You can check the terminal to find the redirection log:

1# ... other output here
2💿 Rebuilding...
3💿 Rebuilt in 100ms
4GET / 200 - - 17.179 ms
5Redirecting to home...        #=== here it is
6GET /profile 302 - - 7.107 ms
7GET / 200 - - 6.564 ms

If you change the value of allowAccess to true, you'll be able to view the profile page:

blog-conditional-render-your-profile-white

Additional considerations for conditional rendering

When working with conditional rendering, you may wonder how it impacts the virtual DOM. Do the various methods of conditional rendering perform equally? Does creating a new component or Higher Order Component (HOC) to abstract away the details of the conditional rendering impact your app's DOM (and performance)? In this section, you'll learn the answers to these questions.

Conditional rendering and JSX

The way in which you implement conditional rendering does not have an impact on your app's DOM and performance. Consider the following example:

1if (loggedIn)
2  return <button>Log Out</button>
3else
4  return <button>Log In</button>

You can also write it like this using the ternary conditional operator:

1return <button>{loggedIn ? "Log Out" : "Log In"}</button>

Both of these will reduce to one of the two components (ie the LoginButton and LogoutButton) being returned by the parent component and will create the exact same change in the DOM. You may argue that both of these could return different instances of the button tag, but that's simply not true. This is because JSX elements, such as buttons and other tags, aren't instances. They are merely a description or a skeletal outline of your final HTML structure.

As long as you return the same elements in the same structure, the impact on the DOM will be the same. This means you can freely abstract away rendering behind components as long as the JSX structure is the same.

This also gives birth to interesting situations, such as a single state being shared by two components that are exactly the same in structure, where only one of them is conditionally rendered in the same place in the DOM. You can learn more about this on the React website, which shows how large an impact a simple ternary operator in JSX can have on your app's functioning.

Nested components and conditional rendering

Nesting the definition of components inside the components is considered an anti-pattern. One of the main reasons behind this is that a nested component is always recreated when its parent component rerenders, regardless of its position in the DOM tree. This behavior is different from what you saw before because the nested component's definition in the runtime gets recreated on the rerender of the parent component. This is somewhat similar to a new instance of the nested component's definition being created, therefore causing this issue.

Conclusion and further resources

While conditional rendering looks (and is) quite simple at a quick glance, it's a very powerful technique to improve the performance and user experience of React apps. In this article, you learned about its benefits and how it's different from conditional routing, as well as how to implement it in React, Next.js, and Remix.

To learn more about conditional rendering and how React handles the state across renders, make sure to check out the new React docs.

When working on React apps, you'll often need to select and install npm packages to add additional functionalities (which include routing and state management, among others). Snyk Advisor can help you find the right npm package for your project while maintaining tight security standards for your app. Also, check out our list of 10 security best practices you should follow when working with React.

Snyk can also be used as an IDE extension to find insecure code in React codebases and can help you fix any security vulnerabilities in open source dependencies.

blog-feature-open-source-security