0

I am working on a screen which has a button to record audio. And depending on the state of the recording I'd like to hide a text input box which is on the same screen. To hide it I am changing the opacity and making it not editable.

I am setting 2 states (isTextInputEditable and textInputOpacity) based on 2 other ones (isRecording and isRecorded). I am having issues with my current code because useState is asynchronous and the state of isRecording and isRecorded are not updated when I perform the checks in setTextInputVisibility function.

Any tips would be helpful, thanks.

    const [isTextInputEditable, setIsTextInputEditable] = useState(true);
    const [textInputOpacity, setTextInputOpacity] = useState(1);
    const [isRecording, setIsRecording] = useState(false);
    const [isRecorded, setIsRecorded] = useState(false);

    const onRecord = () => {
        if (!isRecording) {
            console.log("Started recording audio")
            setIsRecording(true)
            setIsRecorded(false)
        }
        else {
            console.log("Stopped recording audio")
            setIsRecording(false)
            setIsRecorded(true)
        }
        setTextInputVisibility();
    }

    const setTextInputVisibility = () => {
        if (!isRecording && !isRecorded){  
            setIsTextInputEditable(true)
            setTextInputOpacity(1)
            console.log("Text input visible")
        }
        else{
            setIsTextInputEditable(false)
            setTextInputOpacity(0)
            console.log("Text input invisible")
        }
    }
Enfield Li
  • 1,952
  • 1
  • 8
  • 23
Kronax
  • 69
  • 7
  • 3
    Does this answer your question? [The useState set method is not reflecting a change immediately](https://stackoverflow.com/questions/54069253/the-usestate-set-method-is-not-reflecting-a-change-immediately) – David Aug 19 '22 at 14:48
  • 1
    *"because useState is asynchronous"* - That's exactly what's happening. If `setTextInputVisibility` should respond to changes to `isRecording` and `isRecorded` then you can make use of `useEffect` to respond to those changes, rather than invoking the function directly. – David Aug 19 '22 at 14:49
  • 3
    Also, why have so many states that are seemingly responsible for pretty much the same stuff? `isTextInputEditable` & `textInputOpacity` seem to depend upon each other, so why not leave just `isTextInputEditable` and then use it to determine both if the input is editable and it's opacity value? You'd probably be able to find a better abstraction for both recording states as well. Basically, avoid adding redundant states unless there is any clear justification for their separation. – tomleb Aug 19 '22 at 14:58

3 Answers3

1

I think your problem is not caused by the asynchronous nature of state, but rather the conflicting conditions:

if (!isRecording && !isRecorded){ ... } // this is the problem

Previously, in the onRecord function, you set isRecording and isRecorded as such:

setIsRecording(true)
setIsRecorded(false)
// or the other way around

This cause !isRecording && !isRecorded never evaluate to true, because, there will always be the case that either one of them to will be false! So isTextInputEditable and textInputOpacity will always be false and 0!

Possible fix:

const setTextInputVisibility = () => {
  if (!isRecording) { // <- only depends on one state condition
    setIsTextInputEditable(true);
    setTextInputOpacity(1);
    console.log("Text input visible");
  } else {
    setIsTextInputEditable(false);
    setTextInputOpacity(0);
    console.log("Text input invisible");
  }
};

Again, as tomleb rightful pointed out in the comment, how to manage state is the real issue here, for instance, you may not need textInputOpacity state at all, since it is basically the same as isTextInputEditable, and they both serves the same purpose.

Check out sandbox.

Enfield Li
  • 1,952
  • 1
  • 8
  • 23
1

You don't have to use state for hiding text input since hiding text input is dependent on isRecording and isRecorded

You can do something like this

    const [isRecording, setIsRecording] = useState(false);
    const [isRecorded, setIsRecorded] = useState(false);

    const onRecord = () => {
        if (!isRecording) {
            console.log("Started recording audio")
            setIsRecording(true)
            setIsRecorded(false)
        }
        else {
            console.log("Stopped recording audio")
            setIsRecording(false)
            setIsRecorded(true)
        }
        setTextInputVisibility();
    }

    const isTextInputEditable = !isRecording && !isRecorded
    const textInputOpacity = (!isRecording && !isRecorded) ? 1 : 0


    return (
        <View>

            {/* Your code goes here */}

            <TextInput 
                disabled={!isTextInputEditable}
                style={{ ...styles.inputStyles, opacity : textInputOpacity }}
            />

        </View>
    )
poojan bhatt
  • 161
  • 5
0

Thank you all for your answers which have helped me understand what was going on.

As mentioned by @Enfield, the problem was not the asynchronous nature of UseState. Some conditions were wrong and I had too many states defined which were unnecessary.

I solved it by using only one state which makes use of an enum for the different cases which comes in handy for conditional rendering. Then, as @poojan suggested, I base my text input visibility variables on the recording state I previously created. No need for useEffect here for re-rendering.

You can find a working example here

enum RecordingStatus {
  RECORDING = "recording",
  RECORDED = "recorded",
  NOTRECORDED = "notRecorded",
  PLAYING = "playing"
}

export default function App() {
  const [recordingStatus, setRecordingStatus] = React.useState<RecordingStatus>(
    RecordingStatus.NOTRECORDED
  );
  const isTextInputEnabled = recordingStatus === RecordingStatus.NOTRECORDED;
  const textInputOpacity = isTextInputEnabled ? 1 : 0;

  const onRecord = () => {
    if (recordingStatus === RecordingStatus.NOTRECORDED) {
      console.log("Started recording audio");
      setRecordingStatus(RecordingStatus.RECORDING);
    } else if (recordingStatus === RecordingStatus.RECORDING) {
      console.log("Stopped recording audio");
      setRecordingStatus(RecordingStatus.RECORDED);
    } else if (recordingStatus === RecordingStatus.RECORDED) {
      console.log("Audio playing");
      setRecordingStatus(RecordingStatus.PLAYING);
    } else if (recordingStatus === RecordingStatus.PLAYING) {
      console.log("Audio stopped");
      setRecordingStatus(RecordingStatus.RECORDED);
    }
  };

  const onCancel = () => {
    console.log("Cancel recording");
    setRecordingStatus(RecordingStatus.NOTRECORDED);
  };

  return (
    <div>
      <div>isTextInputEditable: {isTextInputEnabled ? "true" : "false"}</div>
      <div>textInputOpacity: {textInputOpacity}</div>
      <div>recordingStatus: {recordingStatus}</div>
      <button onClick={onRecord}>change</button>
      <button
        disabled={!(recordingStatus === RecordingStatus.RECORDED)}
        onClick={onCancel}
      >
        cancel
      </button>
    </div>
  );
}

Kronax
  • 69
  • 7