react functional component let variable doesn't changed

Viewed 5922

I wrote functional component with react native. but I'm in trouble because of a strange problem that seems to be a bug.

My purpose is changing variable in react functional component, but variable not changed.

--I know It will be solved with change let isDirty to cosnt [isDirty,setIsDirty] = useState(false)

Why doesn't "isDirty" change when the handleTitleChange event occurs?

In pure js, it works very well

update.tsx

import React, { useState } from 'react';
import { View, TextInput, CheckBox, Button } from 'react-native';
import { NavigationInjectedProps } from 'react-navigation';
import TodoItem from '../models/todoItem';
import { updateItem } from '../DataProvider';

interface UpdateProps extends NavigationInjectedProps {
}

const Update: React.FC<UpdateProps> = (props) => {
  const [item, setItem] = useState(props.navigation.getParam("item") as TodoItem)
  let isDirty=false

  const handleSave=()=>{
    updateItem(item)
    
    if(isDirty&&props.navigation.state.params)
    {
      props.navigation.state.params.onUpdated(item);
    }
    props.navigation.goBack();
  }

  const handleCompleteChange= (value:boolean)=>{
    isDirty=true
    setItem({...item,isComplete:value})
  }

  const handleTitleChange=(txt:string)=>{
    isDirty=true
    setItem({...item,title:txt})
  }

  const handleContentChange=(txt:string)=>{
    isDirty=true
    setItem({...item,contents:txt})
  }
  
  return <View>
    <CheckBox value={item.isComplete} onValueChange={handleCompleteChange}></CheckBox>
    <TextInput defaultValue={item.title} onChangeText={handleTitleChange}></TextInput>
    <TextInput defaultValue={item.contents} onChangeText={handleContentChange}></TextInput>
    <Button title="Save" onPress={handleSave}></Button>
  </View>;
};

export default Update

pure js

const Update = () => {
  let isDirty=false;

  const handleTitleChange=(txt)=>{
    console.log(isDirty);
    isDirty=true;
    console.log(isDirty);
  }
  handleTitleChange("a");
  handleTitleChange("b");
};


Update();
3 Answers

isDirty is neither a prop nor a state. So it doesn't persist. On re-render, it will initialize back to false.

Make it a state instead.

const [isDirty, setDirty] = useState(false)

When writing functional React code, useRef is the actual candidate to hold mutable values that holds the data and does not cause the rerender as well when using functional components.

Straight from the react docs:

useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.

To solve your problem using useRef, you want to do this:

const isDirty = useRef(false);

...

const handleCompleteChange= (value:boolean)=>{
    isDirty.current = true;
    setItem({...item, isComplete:value})
}


The motivation React team has is to completely remove the class based concepts which would create confusions for developers. Here is you can read about it: https://reactjs.org/docs/hooks-intro.html#motivation

React does not re-render the component if any of the values other than the state changes, so, basically even if isDirty is updated, you won't see it in effect since the component doesn't realize there has been an update to one of its variables.

When the component re-renders, it executes the let isDirty=false again and this overwrites your update

converting as state like so, and updating the functions can solve your problem

const [isDirty, setIsDirty] = useState(false);

 const handleCompleteChange= (value:boolean)=>{
    setIsDirty(true);
    setItem({...item,isComplete:value})
  }

  const handleTitleChange=(txt:string)=>{
    setIsDirty(true);
    setItem({...item,title:txt})
  }

  const handleContentChange=(txt:string)=>{
    setIsDirty(true);
    setItem({...item,contents:txt})
  }

Read more about state

Related