Menjaga Kemurnian Komponen
Beberapa fungsi JavaScript bersifat murni, yaitu hanya melakukan kalkulasi. Dengan begitu, Anda bisa menghindari berbagai macam bug dan tingkah laku yang membingungkan dari aplikasi yang Anda bangun. Namun, ada beberapa aturan yang harus Anda ikuti untuk mencapai keadaan ini.
You will learn
- Apa itu kemurnian dan bagaimana hal tersebut dapat membantu Anda menghindari bug
- Bagaimana cara menjaga kemurnian komponen dengan tidak melakukan pengubahan pada fase render
- Bagaimana cara menggunakan Strict Mode untuk menemukan kesalahan pada komponen Anda
Kemurnian: Komponen sebagai rumus
Dalam ilmu komputer, terutama di dunia pemrograman fungsional, fungsi murni adalah sebuah fungsi yang memenuhi kriteria berikut:
- Dia hanya mengurus dirinya sendiri. Dia tidak mengubah objek atau variabel yang ada sebelum dia dipanggil.
- Masukan yang sama, luaran yang sama. Untuk masukan yang sama, fungsi murni akan selalu menghasilkan luaran yang sama.
Anda mungkin sudah akrab dengan salah satu contoh fungsi murni, yaitu rumus-rumus dalam matematika.
Perhatikan rumus ini: y = 2x.
Jika x = 2, y = 4. Selalu.
Jika x = 3, y = 6. Selalu.
Jika x = 3, y tidak mungkin bernilai 9 ataupun –1 ataupun 2.5 hanya karena ada pergantian hari atau pergerakan bursa saham.
Jika y = 2x dan x = 3, y akan selalu bernilai 6.
Jika kita mengonversi rumus ini menjadi fungsi JavaScript, fungsi tersebut akan terlihat seperti ini:
function double(number) {
return 2 * number;
}
Pada contoh di atas, double
adalah sebuah fungsi murni. Jika Anda masukkan 3
, fungsi itu akan mengembalikan 6
. Selalu.
React dibuat berdasarkan konsep ini. React berasumsi kalau setiap komponen yang Anda buat adalah fungsi murni. Ini berarti komponen React yang Anda buat harus selalu menghasilkan JSX yang sama jika diberikan masukan yang sama:
function Recipe({ drinkers }) { return ( <ol> <li>Boil {drinkers} cups of water.</li> <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li> <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li> </ol> ); } export default function App() { return ( <section> <h1>Spiced Chai Recipe</h1> <h2>For two</h2> <Recipe drinkers={2} /> <h2>For a gathering</h2> <Recipe drinkers={4} /> </section> ); }
Jika Anda memberikan drinkers={2}
ke Recipe
, komponen tersebut akan mengembalikan JSX yang berisi 2 cups of water
. Selalu.
Jika Anda memberikan drinkers={4}
, komponen tersebut akan mengembalikan JSX yang berisi 4 cups of water
. Selalu.
Seperti rumus matematika.
Anda bisa menganggap komponen Anda sebagai resep: jika Anda mengikuti resep tersebut dan tidak menambahkan bahan apapun dalam proses pemasakan, Anda akan selalu mendapatkan makanan yang sama. “Makanan” itu adalah JSX yang diserahkan sebuah komponen ke React untuk di-render.

Illustrated by Rachel Lee Nabors
Efek Samping: Konsekuensi yang (tidak) diinginkan
Proses render React harus selalu murni. Komponen hanya mengembalikan JSX mereka dan tidak mengubah objek atau variabel apapun yang telah ada sebelumnya—ini membuat komponen tersebut menjadi tidak murni!
Ini contoh komponen yang tidak mengikuti aturan tersebut:
let guest = 0; function Cup() { // Buruk: mengubah variabel yang sudah ada! guest = guest + 1; return <h2>Tea cup for guest #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup /> <Cup /> <Cup /> </> ); }
Komponen ini membaca dan menulis sebuah variabel guest
yang telah dideklarasi di luar komponen tersebut. Ini berarti memanggil komponen JSX ini berkali-kali akan menghasilkan JSX yang berbeda pada setiap percobaan! Bukan hanya itu, jika ada komponen *lain yang juga membaca guest
, komponen tersebut juga akan menghasilkan JSX yang berbeda, bergantung kepada kapan dia di-render. Hal ini sangat sulit untuk diprediksi.
Kembali ke rumus y = 2x, meskipun x = 2, kita tidak bisa menjamin y = 4. Kasus uji kita akan gagal, pengguna kita menjadi sangat bingung, dan pesawat akan berjatuhan dari langit—Anda bisa melihat bagaimana ini akan berujung kepada bug yang sangat membingungkan!
Anda bisa memperbaiki komponen ini dengan memberikan guest
sebagai sebuah prop:
function Cup({ guest }) { return <h2>Tea cup for guest #{guest}</h2>; } export default function TeaSet() { return ( <> <Cup guest={1} /> <Cup guest={2} /> <Cup guest={3} /> </> ); }
Sekarang, komponen Anda menjadi murni karena JSX yang dikembalikan hanya bergantung kepada prop guest
.
Secara umum, Anda jangan mengharapkan komponen Anda untuk di-render mengikuti suatu urutan yang pasti. Meskipun Anda memanggil y = 2x sebelum atau sesudah y = 5x, kedua rumus tersebut akan berjalan secara independen. Oleh karena itu, setiap komponen sebaiknya hanya “mengurus dirinya sendiri” dan tidak mencoba untuk berkoordinasi atau bergantung kepada komponen lain selama proses render berjalan. Proses render mirip dengan ujian di sekolah, setiap komponen harus mengalkulasi JSX dia sendiri.
Deep Dive
Although you might not have used them all yet, in React there are three kinds of inputs that you can read while rendering: props, state, and context. You should always treat these inputs as read-only.
When you want to change something in response to user input, you should set state instead of writing to a variable. You should never change preexisting variables or objects while your component is rendering.
React offers a “Strict Mode” in which it calls each component’s function twice during development. By calling the component functions twice, Strict Mode helps find components that break these rules.
Notice how the original example displayed “Guest #2”, “Guest #4”, and “Guest #6” instead of “Guest #1”, “Guest #2”, and “Guest #3”. The original function was impure, so calling it twice broke it. But the fixed pure version works even if the function is called twice every time. Pure functions only calculate, so calling them twice won’t change anything—just like calling double(2)
twice doesn’t change what’s returned, and solving y = 2x twice doesn’t change what y is. Same inputs, same outputs. Always.
Strict Mode has no effect in production, so it won’t slow down the app for your users. To opt into Strict Mode, you can wrap your root component into <React.StrictMode>
. Some frameworks do this by default.
Local mutation: Your component’s little secret
In the above example, the problem was that the component changed a preexisting variable while rendering. This is often called a “mutation” to make it sound a bit scarier. Pure functions don’t mutate variables outside of the function’s scope or objects that were created before the call—that makes them impure!
However, it’s completely fine to change variables and objects that you’ve just created while rendering. In this example, you create an []
array, assign it to a cups
variable, and then push
a dozen cups into it:
function Cup({ guest }) { return <h2>Tea cup for guest #{guest}</h2>; } export default function TeaGathering() { let cups = []; for (let i = 1; i <= 12; i++) { cups.push(<Cup key={i} guest={i} />); } return cups; }
If the cups
variable or the []
array were created outside the TeaGathering
function, this would be a huge problem! You would be changing a preexisting object by pushing items into that array.
However, it’s fine because you’ve created them during the same render, inside TeaGathering
. No code outside of TeaGathering
will ever know that this happened. This is called “local mutation”—it’s like your component’s little secret.
Where you can cause side effects
While functional programming relies heavily on purity, at some point, somewhere, something has to change. That’s kind of the point of programming! These changes—updating the screen, starting an animation, changing the data—are called side effects. They’re things that happen “on the side”, not during rendering.
In React, side effects usually belong inside event handlers. Event handlers are functions that React runs when you perform some action—for example, when you click a button. Even though event handlers are defined inside your component, they don’t run during rendering! So event handlers don’t need to be pure.
If you’ve exhausted all other options and can’t find the right event handler for your side effect, you can still attach it to your returned JSX with a useEffect
call in your component. This tells React to execute it later, after rendering, when side effects are allowed. However, this approach should be your last resort.
When possible, try to express your logic with rendering alone. You’ll be surprised how far this can take you!
Deep Dive
Writing pure functions takes some habit and discipline. But it also unlocks marvelous opportunities:
- Your components could run in a different environment—for example, on the server! Since they return the same result for the same inputs, one component can serve many user requests.
- You can improve performance by skipping rendering components whose inputs have not changed. This is safe because pure functions always return the same results, so they are safe to cache.
- If some data changes in the middle of rendering a deep component tree, React can restart rendering without wasting time to finish the outdated render. Purity makes it safe to stop calculating at any time.
Every new React feature we’re building takes advantage of purity. From data fetching to animations to performance, keeping components pure unlocks the power of the React paradigm.
Recap
- A component must be pure, meaning:
- It minds its own business. It should not change any objects or variables that existed before rendering.
- Same inputs, same output. Given the same inputs, a component should always return the same JSX.
- Rendering can happen at any time, so components should not depend on each others’ rendering sequence.
- You should not mutate any of the inputs that your components use for rendering. That includes props, state, and context. To update the screen, “set” state instead of mutating preexisting objects.
- Strive to express your component’s logic in the JSX you return. When you need to “change things”, you’ll usually want to do it in an event handler. As a last resort, you can
useEffect
. - Writing pure functions takes a bit of practice, but it unlocks the power of React’s paradigm.
Challenge 1 of 3: Fix a broken clock
This component tries to set the <h1>
’s CSS class to "night"
during the time from midnight to six hours in the morning, and "day"
at all other times. However, it doesn’t work. Can you fix this component?
You can verify whether your solution works by temporarily changing the computer’s timezone. When the current time is between midnight and six in the morning, the clock should have inverted colors!
export default function Clock({ time }) { let hours = time.getHours(); if (hours >= 0 && hours <= 6) { document.getElementById('time').className = 'night'; } else { document.getElementById('time').className = 'day'; } return ( <h1 id="time"> {time.toLocaleTimeString()} </h1> ); }