DEV Community

Cover image for Don't be Evil; using JavaScript eval method safely with React hooks
Vincent Kipyegon
Vincent Kipyegon

Posted on • Edited on

Don't be Evil; using JavaScript eval method safely with React hooks

Eval is a built-in JavaScript method that allows develops to evaluate and run JavaScript syntax that has been wrapped in a string. It is one of the least used methods due to its security vulnerabilities.
Google Chrome,for example, blocks the usage of eval with following error

Image description

Douglas Crockford claims in his book JavaScript: The Good Parts that the security problem with eval is that code contained inside a string can be exploited in cross-site attacks, with the result that "eval is evil";

However, in React, Javascript UI library, eval can be used to securely set the state of several form input values with only one function. Given that React uses a virtual DOM to update the actual DOM, there are no security concerns to worry about.

Consider the following React component;

export function MyForm() {
  const [firstName, setFirstName] = React.useState("");
  const [lastName, setLastName] = React.useState("");
  const [age, setAge] = React.useState("");
  const [gender, setGender] = React.useState("");
  const [location, setLocation] = React.useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ firstName, lastName, age, gender, location });
    /*
    {
      age: "61",
    firstName: "Brendan",
    gender: "Male",
    lastName: "Eich",
    location: "Pitsburrg, PA"} */

    //send payload to server or display on UI
  };
  return (
    <form
      onSubmit={handleSubmit}
      style={{
        display: "flex",
        flexDirection: "column",
        background: "#ccc",
        gap: 10,
        maxWidth: 500,
        padding: "1rem",
      }}
    >
      <h1 style={{ textAlign: "center" }}>Simple User Form</h1>
      <div>
        <label htmlFor="FirstName">First Name</label>
        <input
          type="text"
          name="FirstName"
          id="FirstName"
          value={firstName}
          onChange={(e) => setFirstName(e.target.value)}
        />
      </div>

      <div>
        <label htmlFor="LastName">Last Name</label>
        <input
          type="text"
          name="LastName"
          id="LastName"
          className="input"
          value={lastName}
          onChange={(e) => setLastName(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="Age">Age</label>
        <input
          type="number"
          name="Age"
          id="Age"
          value={age}
          onChange={(e) => setAge(e.target.value)}
        />
      </div>
      <div>
        <label>Gender</label>
        <label htmlFor="Male">
          {" "}
          <input
            type="radio"
            name="Gender"
            id="Male"
            value="Male"
            onChange={(e) => setGender(e.target.value)}
          />
          Male
        </label>

        <label htmlFor="Female">
          {" "}
          <input
            type="radio"
            name="Gender"
            id="Female"
            value="Female"
            onChange={(e) => setGender(e.target.value)}
          />
          Female
        </label>
      </div>
      <div>
        <label htmlFor="Location">Location</label>
        <input
          type="text"
          name="Location"
          id="Location"
          value={location}
          onChange={(e) => setLocation(e.target.value)}
        />
      </div>
      <button
        style={{
          padding: 10,
          background: "skyblue",
          borderRadius: 8,
          border: "none",
          margin: "0 auto",
          width: "100%",
          color: "white",
        }}
        type="submit"
      >
        Submit
      </button>
      <style jsx>{`

      label{
        display:block;
        margin:5px
        font-weight:600;
      }
      input[type="text"],input[type="number"]{
        width:100%;
        padding:8px;
        margin:5px;
        border-radius:5px
      }
      input[type="radio"]{
        display:inline-block;
      }
      `}</style>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This is a straightforward form element that gathers user biodata, sets it to the appropriate state using the onChange event handler function, and dynamically binds values from state variables to the HTML inputs.

It works as expected

Image description

The drawback with inline onchange event handling is that it doesn't offer a mechanism to evaluate data or make the user interface interactive when the user triggers blur or focus events.

In order to correct this, we employ the eval method, a single onchange event handler the HTML input attribute  name, which we use to dynamically setState using the eval method, and ultimately get the input values.

NB name , id and data-* are all valid HTML input attributes, however I personally prefer name when using eval.

The name of the HTML atribute property that will be utilised should resemble the name of the setState function that is set as the state variable.

const [lastName,setLastName]=React.useState(“”) 
// LastName will be appended to keyword set

<input
          type="text"
          name="LastName" // this one should match LastName in setLastName
          id="LastName"
          value={lastName}
          onChange={(e) => setLastName(e.target.value)}
        />
Enter fullscreen mode Exit fullscreen mode

Our updated functional component should now look like this:

export function MyForm() {
  const [firstName, setFirstName] = React.useState("");
  const [lastName, setLastName] = React.useState("");
  const [age, setAge] = React.useState("");
  const [gender, setGender] = React.useState("");
  const [location, setLocation] = React.useState("");

  /**this function extracts the name attribute from target as well as
   *  its value then dynamically uses eval to set state of respective state variable */

  const handleChange = (e) => {
    // grab the value
    const value = e.target.value;
    // grab name attribute of element under change
    const nameAttribute = e.target.name;
    // concatenate set + name atrribute, i.e setAge, setLocation
    // then evaluate the function using eval method
    const setState = eval("set" + nameAttribute);

    /**do extra validations before seting state*/
    /** Finally we set state, IMPORTANT */
    setState(value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ firstName, lastName, age, gender, location });
    /*
    {
      age: "61",
    firstName: "Brendan",
    gender: "Male",
    lastName: "Eich",
    location: "Pitsburrg, PA"} */

    //send payload to server
  };
  return (
    <form
      onSubmit={handleSubmit}
      style={{
        display: "flex",
        flexDirection: "column",
        background: "#ccc",
        gap: 10,
        maxWidth: 500,
        padding: "1rem",
      }}
    >
      <div>
        <label htmlFor="FirstName">First Name</label>
        <input
          type="text"
          name="FirstName"
          id="FirstName"
          value={firstName}
          onChange={handleChange}
        />
      </div>

      <div>
        <label htmlFor="LastName">Last Name</label>
        <input
          type="text"
          name="LastName"
          id="LastName"
          value={lastName}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="Age">Age</label>
        <input
          type="text"
          name="Age"
          id="Age"
          value={age}
          onChange={handleChange}
        />
      </div>
      <p>Gender</p>
      <div style={{ display: "flex", gap: 10 }}>
        <label htmlFor="Male">
          {" "}
          <input
            type="radio"
            name="Gender"
            id="Male"
            value="Male"
            onChange={handleChange}
          />
          Male
        </label>

        <label htmlFor="Female">
          {" "}
          <input
            type="radio"
            name="Gender"
            id="Female"
            value="Female"
            onChange={handleChange}
          />
          Female
        </label>
      </div>
      <div>
        <label htmlFor="Location">Location</label>
        <input
          type="text"
          name="Location"
          id="Location"
          value={location}
          onChange={handleChange}
        />
      </div>
      <button
        type="submit"
        style={{
          padding: 10,
          background: "skyblue",
          borderRadius: 8,
          border: "none",
          margin: "0 auto",
          width: "100%",
          color: "white",
        }}
      >
        Submit
      </button>
      <style jsx>{`

      label{
        display:block;
        margin:5px
        font-weight:600;
      }
      input[type="text"],input[type="number"]{
        width:100%;
        padding:8px;
        margin:5px;
        border-radius:5px
      }
      input[type="radio"]{
        display:inline-block;
      }
      `}</style>
    </form>
  );
}

Enter fullscreen mode Exit fullscreen mode

You'll now see that there is now a single handleChange function that manages all form inputs.And it still works!

This function takes the name attribute, evaluates it to a setState function, and then captures the value of the element under change. Every input element can be validated adding blur events then using eval method inside blur event handlers. See how the eval approach simplifies the task at hand?

Top comments (0)