Home > Net >  React getting empty state
React getting empty state

Time:10-24

I am trying to create an advanced regular expression search application to let me search for some content and replace it in other lines but I am struggling with a much easier task: Cannot delete the rules since the rules state seems to be empty:

export default function App() {
  const [displayNewRule, setDisplayNewRule] = useState(false);
  const [replaceType, setReplaceType] = useState("line");
  const [rules, setRules] = useState([]);

  function handleClick() {
    setDisplayNewRule(true);
  }

  function handleSubmit(e) {
    e.preventDefault();

    const rule = {
      name: e.target.name.value,
      sourceSearchPattern: e.target.sourceSearchPattern.value,
      replaceType,
      value: e.target.value.value
    };

    if (replaceType === "occurrence") {
      rule.targetSearchPattern = e.target.targetSearchPattern.value;
      rule.location = e.location.value;
    }

    let $rules = rules;
    $rules.push(rule);
    setRules($rules);

    e.target.reset();
    handleReset();
  }

  function handleReset() {
    setDisplayNewRule(false);
  }

  function deleteRule(i) {
    let $rules = rules;
    $rules.splice(i, 1);
    setRules($rules);

    // this gets an empty rules
  }

  return (
    <div className="App">
      <form
        onSubmit={handleSubmit}
        onReset={handleReset}
        style={{ display: displayNewRule || "none" }}
      >
        <h3>Create new rule</h3>

        <div className="input-group">
          <label htmlFor="name">Name:</label>
          <input type="text" id="name" name="name" required />
        </div>

        <div className="input-group">
          <label htmlFor="sourceSearchPattern">Source search pattern:</label>
          <input
            type="text"
            id="sourceSearchPattern"
            name="sourceSearchPattern"
            required
          />
        </div>

        <div className="input-group">
          <label htmlFor="replaceType">Replace type:</label>
          <select
            id="replaceType"
            name="replaceType"
            onChange={(e) => {
              setReplaceType(e.target.value);
            }}
            required
          >
            <option value="">Select item</option>
            <option value="line">Replace line</option>
            <option value="occurrence">Replace occurrence</option>
          </select>
        </div>

        {replaceType === "occurrence" && (
          <div className="input-group">
            <label htmlFor="targetSearchPattern">Target search pattern:</label>
            <input
              type="text"
              id="targetSearchPattern"
              name="targetSearchPattern"
              required
            />
          </div>
        )}

        <div className="input-group">
          <label htmlFor="value">Value:</label>
          <input type="text" id="value" name="value" required />
        </div>

        {replaceType === "occurrence" && (
          <div className="input-group">
            Occurrence location:
            <div className="option-group">
              <input
                type="radio"
                id="next-occurrence"
                name="location"
                value="next"
                required
              />
              <label htmlFor="next-occurrence">Next occurrence</label>
            </div>
            <div className="option-group">
              <input
                type="radio"
                id="previous-occurrence"
                name="location"
                value="previous"
              />
              <label htmlFor="previous-occurrence">Previous occurrence</label>
            </div>
          </div>
        )}

        <button type="submit">Submit</button>
        <button type="reset">Cancel</button>
      </form>

      <button onClick={handleClick}>Add rule</button>

      <div className="rules">
        <h3>Rules</h3>
        {rules.map((rule, i) => ( // These rules are being displayed.
          <div className="rule" key={i   1}>
            <h5>
              {rule.name}
              <button
                onClick={() => {
                  deleteRule(i);
                }}
              >
                Delete rule
              </button>
            </h5>
            <p>Replace type: {rule.replaceType}</p>
            <p>Source search pattern: {rule.sourceSearchPattern}</p>
          </div>
        ))}
      </div>
    </div>
  );
}

Any clues?

CodePudding user response:

Instead of

let $rules = rules;
$rules.push(rule);
setRules($rules);

try

setRules(oldRules => [...oldRules, rule]);

You're mutating state in your current code, which is a huge anti-pattern. I'm not sure if it's causing the issue you're seeing, but changing to this pattern will make it easier to debug and eliminated a variable. Also change your deleteRule function so that it's likewise not mutating state (make a copy of state with [...rules] and operate on that before setting the new rules).

CodePudding user response:

tl;dr - your component does not re-render at all.

More description - $rules keeps the reference of state object rules (it's the same instance). When calling setRules shallow comparison goes in and prevents you from updating the component (the new value is exactly the same as the current one, because of the direct mutation).

Someone would ask why shallow comparison prevents component from re-rendering since it works only with primitives (which an array is not). The thing is shallow comparison still works on objects that holds the same reference.

const array = [];
array === array // true;

Code with step-by-step description:

function handleSubmit() {
  let $rules = rules; // $rules holds the reference and is the same instance as rules
  $rules.push(rule); // directly updated state (mutated) 
                     // value of $rules and rules is again exactly the same!

  setRules($rules); // the component is not even updated because of the 
                    // shallow comparison - $rules and rules is the same instance
                    // with exactly the same value
}

Solution is to avoid direct mutation.

setRules((oldRules) => [...oldRules, rule]); as @Nathan posted above.

  • Related