Vishal DS
Vishal DS

Reputation: 175

React state hooks array element not updating state

I'm trying to reduce the code and/or optimize the use of React state hooks in a form with the rn-material-ui-textfield module. Normally, for a single text field, you could do something like this

import { OutlinedTextField } from 'rn-material-ui-textfield'
// ...and all the other imports

const example = () => {
    let [text, onChangeText] = React.useState('');
    let [error, set_error] = React.useState('');

    const verify = () => {
        if(!text) set_error('Enter a text');
        else console.log('Finished');
    }

    return(
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            <OutlinedTextField
                onChangeText={onChangeText}
                value={text}
                onSubmitEditing={verify}
                error={error}
              />
        </View>
    );
}

and it would surely work no problem. But as you keep on adding more and more fields, setting a separate error and text hooks for each of them seem tedious and generates a lot of code. So, in order to prevent this, I tried to write this in a different way

// ...all imports from previous snippet

const example = () => {
    let [inputs, set_input_arr] = React.useState(['', '', '', '', '', '']);
    let [error, set_error_arr] = React.useState(['', '', '', '', '', '']);
    const error_names = ['your name', 'an email ID', 'a password', 'your phone number', "your favourite color", 'your nickname'];

    const set_input = (index, text) => {
        let temp = inputs;
        temp[index] = text;
        set_input_arr(temp);
    };

    const set_error = (index, err) => {
        let temp = error;
        temp[index] = err;
        set_error_arr(temp);
        // this logs the array correctly after running the loop each time
        console.log(`This was set as error: ${error}`);
    };

    const verify = () => {
        for (let i = 0; i < inputs.length; i++) {
            if (!inputs[i]) set_error(i, `Please enter ${error_names[i]}`);
        }
    };
 
    return(
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            <OutlinedTextField
                onChangeText={text => set_input(0, text)}
                value={inputs[0]}
                error={error[0]}
             />
             <OutlinedTextField
                onChangeText={text => set_input(1, text)}
                value={inputs[1]}
                error={error[1]}
             />
             <OutlinedTextField
                onChangeText={text => set_input(2, text)}
                value={inputs[2]}
                error={error[2]}
             />
             <OutlinedTextField
                onChangeText={text => set_input(3, text)}
                value={inputs[3]}
                error={error[3]}
             />
             <OutlinedTextField
                onChangeText={text => set_input(4, text)}
                value={inputs[4]}
                error={error[4]}
             />
             <OutlinedTextField
                onChangeText={text => set_input(5, text)}
                value={inputs[5]}
                error={error[5]}
                onSubmitEditing={verify}
             />
             <Button onPress={verify} title='Verify' />
        </View>
    );
}

and it doesn't work. To be clear, the console.log() in the set_error() does print the out as I expected. It adds all the values to the array and prints out the complete array. But then, the state in the elements doesn't change. I strongly believe that this has got something to do with React's way of handling hooks rather than a bug in the <OutlinedTextField /> or something. That's why I'm leaving this here.

If such an approach is impossible with React, please suggest another way to efficiently write code to declare and use these many textfields without declaring all these error hooks.

Upvotes: 1

Views: 1523

Answers (2)

Hans Krohn
Hans Krohn

Reputation: 433

To fix this change set_error_arr(temp); to set_error_arr([...temp]);.

The reason React does not trigger a re-render when you write set_error_arr(temp); is because of how JS arrays work. temp is holding a reference to the array. This means, that even though the values may be changing the reference has not. Since, the reference has not changed React does not acknowledge a change has occurred to your array. By writing [...temp] you are creating a new array (new reference to point too) thus React acknowledges a change occurred and will trigger a re-render.

This will also occur when working with JS objects

Upvotes: 5

Vincent Sastra
Vincent Sastra

Reputation: 11

It's because React doesn't think that the Array has changed because it is pointing to the same reference. The content of the array itself has changed, but React only checks if it is the same Array, not the content.

There are two different solution, the first one is to create a new Array with the same content like:

 const set_error = (index, err) => {
        let temp = [...error];
        temp[index] = err;
        set_error_arr(temp);
        // this logs the array correctly after running the loop each time
        console.log(`This was set as error: ${error}`);
    };

Or you can checkout the useReducer hook which might be more aligned to what you're trying to implement

Upvotes: 1

Related Questions