DEV Community

Cover image for Currying State Handlers in React
Gabriel Corrêa
Gabriel Corrêa

Posted on

Currying State Handlers in React

Introduction

If you're used to React, you probably needed to change the parent's component state through a child component. In this post you'll learn how to do that using curried functions, that will make your code a lot cleaner and will save you some good time.

What is currying and how to write a curried function in JS?

First things first. What is currying?

Currying is a technique to write functions. The curried function will receive one argument at a time.

Ok, but what the hell does that mean?

Let's imagine we have a curried function that takes two arguments. It will take the first argument and return a function that will take the next argument, after all arguments are taken, it will return the result of applying those arguments.
This will be helpful when you don´t want to re-write several functions that execute the same thing but with different values. To understand the concept better, we will write our own curried function:

Curried function in JS

We will create a simple function. It will take two numbers and return the sum of them. In order to create it we will use arrow functions style.

//Function:
const add = x => y => x + y

console.log(add(2)(3))
//Outputs 5.
//add(2) calls the function and it returns a second function that will take the second argument, 
//since the second argument (3) is already in front of it, it immediately resolves the sum.

Ok, I understand the concept but I don´t see any benefit... I can do this with normal functions.

The interesting part comes now. Remember the explanation?
"It will take the first argument and return a function that will take the next argument [...]"

We can use it to assign the function to another name with an argument alredy passed in.

//Function:
const add = x => y => x + y

//Instead of writing this:
console.log(add(2)(1))
console.log(add(2)(2))
console.log(add(2)(3))
console.log(add(2)(4))

//We could simply write:
const addTwo = add(2)
console.log(addTwo(1)) //Outputs 3.
console.log(addTwo(2)) //Outputs 4.
console.log(addTwo(3)) //Outputs 5.
console.log(addTwo(4)) //Outputs 6.

So, this is it. A simple explanation about curried functions in JS. You could add as many arguments as you want in it. I hope you make good use of this great power. But before you walk away, let´s see the implementation of this in React.

Curried state handlers in React

This part of the tutorial requires previous knowledge of React

Our mission is to create an app that has 3 counters and 3 buttons that when clicked will add an specified value to the counter.

As always, let´s create our React App. I´ll use the create-react-app tool, but you can use any boilerplate you want. So, run npx create-react-app and delete everything but:

  • index.html
  • index.js
  • app.js

We are going to make some changes inside these three files too:

  • index.html will be looking like:
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Currying In React</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
  • index.js will be looking like:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

-App.js will be looking like:

import React from 'react';
import Main from './components/Main'

function App() {
  return <Main /> 
}

export default App;

Ok, everything is right, let´s jump into the code.

We will have two components, so create a components folder and add Main.js and Button.js. I like creating my components from the smallest to the biggest so let´s start with the Button.

import React from 'react';

//Button component:
//  No state.
//  3 props:
//      -text: Text that will be displayed inside the button.
//      -handleState: will update parent's state based on the value prop.
//      -value: Number that will be added to the counter.

export default function Button(props) {
    return (
        <button onClick={() => props.handleState(props.value)}>{props.text}</button>
    );
}

The code will be commented so I don´t have much too explain here. The handleState will be a function that will update the state based on the value props. It will add the value to the counter. This passed down function will be curried so we only have to pass the value to be added, the parent component (Main) will take care of specifying to which state we should add.

Everything is fine with the Button, so, let´s start messing around with the Main component. This component will be large compared to the Button so we will break it into parts. First we will initialize our three counters and add them as paragraphs to be rendered.

import React, { useState } from 'react';
import Button from './Button';

export default function Main() {
        //Counters set to 0.
    const [counter1, setCounter1] = useState(0);
    const [counter2, setCounter2] = useState(0);
    const [counter3, setCounter3] = useState(0);

        return (
        <div>
            <p>Counter 1: {counter1}</p>
            <p>Counter 2: {counter2}</p>
            <p>Counter 3: {counter3}</p>
                </div>

}

So far we have 3 counters showing in the screen, the next thing to be added should be our buttons, but first we need to create our handleState function:

//Function:
//   -Three separated arguments:
//      -setState: the function that updates our state, e.g. ``setCounter1``
//      -state: the state that the setState argument refers to.
//      -value: the value that will be added to the counter, this argument will
//      be passed by the button
const handleState = setState => state => value => {
        setState((state += value));
};

Then we can add our buttons, and the whole code will look like this:

import React, { useState } from 'react';
import Button from './Button';

//Main component:
//  Functions and variables:
//      -handleState:
//          Curried function that updates the state based on the setState 
//          function, the current state and the value to be added.
//  State:
//      -Three counters.
//  No props.
//  Content:
//      -3 paragraphs returning the counters.
//      -3 Button components referring to the 3 counters.

export default function Main() {
    const [counter1, setCounter1] = useState(0);
    const [counter2, setCounter2] = useState(0);
    const [counter3, setCounter3] = useState(0);

    const handleState = setState => state => value => {
        setState((state += value));
    };

    return (
        <div>
            <p>Counter 1: {counter1}</p>
            <p>Counter 2: {counter2}</p>
            <p>Counter 3: {counter3}</p>

            <Button
                text='Add 1 to counter 1!'
                value={1}
                handleState={setCounter1State}
            />

                        {'\n'}

            <Button
                text='Add 2 to counter 2!'
                value={2}
                handleState={handleState(setCounter2)(counter2)}
            />

            {'\n'}

            <Button
                text='Add 3 to counter 3!'
                value={3}
                handleState={handleState(setCounter3)(counter3)}
            />
        </div>
    );
}

We send the code to our client and... He´s completely mad with us! For some reason that only God knows, he read the code and noticed that the our handleState function is curried but it doesn´t make that much of a difference to the code. So, we have to show him how this could help us in the future.

We add 2 more buttons and so we don´t have to re-write a whole other function or call the handleState with the two arguments over and over, we create a new function just passing in handleState(setCounter1)(counter1) to a variable. The (real) final code looks this now:

import React, { useState } from 'react';
import Button from './Button';

//Main component:
//  Functions and variables:
//      -handleState:
//          Curried function that updates the state based on the setState 
//          function, the current state and the value to be added.
//       -setCounter1State:
//          Uses handleState to create a function already set with 
//          setCounter1(setState argument) and counter1 (state).
//  State:
//      -Three counters.
//  No props.
//  Content:
//      -3 paragraphs returning the counters.
//      -3 Button components referring to 1 counter.
//      -Other 2 Button components referring to the last 2 counters.

export default function Main() {
    const [counter1, setCounter1] = useState(0);
    const [counter2, setCounter2] = useState(0);
    const [counter3, setCounter3] = useState(0);

    const handleState = setState => state => value => {
        setState((state += value));
    };

    const setCounter1State = handleState(setCounter1)(counter1);

    return (
        <div>
            <p>Counter 1: {counter1}</p>
            <p>Counter 2: {counter2}</p>
            <p>Counter 3: {counter3}</p>

            <Button
                text='Add 1 to counter 1!'
                value={1}
                handleState={setCounter1State}
            />
            <Button
                text='Add 2 to counter 1!'
                value={2}
                handleState={setCounter1State}
            />
            <Button
                text='Add 3 to counter 1!'
                value={3}
                handleState={setCounter1State}
            />

                        {'\n'}

            <Button
                text='Add 2 to counter 2!'
                value={2}
                handleState={handleState(setCounter2)(counter2)}
            />

            {'\n'}

            <Button
                text='Add 3 to counter 3!'
                value={3}
                handleState={handleState(setCounter3)(counter3)}
            />
        </div>
    );
}

Conclusion

So, this is it. This is how you curry state handlers in React, I used functional components but this should work fine with class components. You can find the GitHub code here.
Thank you for reading so far! I´m quite new to React and this is my first (hopefully not last) post here on dev.to, I´m also not a native english speaker so please correct me if you find any errors. All feedback is welcome!
See yah! :)

Top comments (0)