简体   繁体   中英

Dynamic Opacity not changing when component rerenders in react native

I'm starting to learn React Native, and for my project I created a simple Button component to reuse in my project. I set the opacity value dynamically according to the variable 'disabled', however, the look of the button is not changing with the value of the opacity variable. I searched around and I have not found an explanation..
Any help will be appreciated.

Here is my source code:

import React from 'react'
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
import PropTypes from 'prop-types'

//TODO: arrumar o problema com a opacidade
export default function Button({text, onPress, style, disabled, textStyle}) {
    let opacity = disabled === true ? 0.5 : 1
    // console.log('opacity', opacity)
    return (
        <TouchableOpacity onPress={onPress} style={[defaultStyles.button, style, {opacity: opacity}]} 
            disabled={disabled}>
            <Text style={[defaultStyles.text, textStyle]}>{text}</Text>
        </TouchableOpacity>
    )

}

const defaultStyles = StyleSheet.create({
    text: {
        color: 'white'
    },
    button: {
        backgroundColor: 'black',
        margin: 15,
        padding: 15,
        borderRadius: 10
    },
})

Button.propTypes = {
    text: PropTypes.string,
    onPress: PropTypes.func,
    style: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array,
        PropTypes.object
    ]),
    disabled: PropTypes.bool,
    textStyle: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.array,
        PropTypes.object
    ])
}

EDIT: Here is the code the calls the button

class NewDeck extends Component {

    state={
        title: null
    }

    submit = () => {
        const { add, goBack } = this.props
        let deck = {...this.state}
        if(!deck['deckId']){
            deck['deckId'] = Date.now()
            deck['logs'] = []
        }

        !deck['cardsId'] && (deck['cardsId'] = [])

        add(deck).then(() => {
            this.props.navigation.navigate('Deck', {deckId: deck.deckId, title: deck.title})
            this.setState({title: null})
            }
        )
    }

    render(){
        const disabled = this.state.title === null || this.state.title.length === 0
        return (
            <KeyboardAwareScrollView resetScrollToCoords={{ x: 0, y: 0 }}
                contentContainerStyle={styles.container}>
                <Text style={textStyles.title2}>Whats the title of your deck?</Text>
                    <TextInput editable={true} style={[styles.input, textStyles.body]}
                    placeholder='Type title here'
                    maxLength={25}
                    value={this.state.title}
                    onChangeText={(text) => {
                        this.setState({title: text})
                    }}
                    />
                <Button
                    onPress={this.submit}
                    text='Submit'
                    style={{backgroundColor: colors.pink}}
                    textStyle={textStyles.body}
                    disabled={!this.state.title} 
                />
              </KeyboardAwareScrollView>
            )
    }
}

The disabled variable is true if the title of the newDeck component is empty or null. When this variable is true, the opacity of the button should be only 0.5. When the value goes to false, then the opacity changes to 1 again. If I log the value of the opacity in the component, I can see it going from 0.5 to 1, but the look of the component doesn't change.

not sure if it's a bug on the TouchableOpacity component, but the opacity won't update on a re-render until the component is clicked

to fix your problem just wrap the content of the touchable in a View and apply the opacity to the view instead of the touchable

export default function Button({text, onPress, style, disabled, textStyle}) {
    const opacity = disabled === true ? 0.5 : 1
    // console.log('opacity', opacity)
    return (
        <TouchableOpacity onPress={onPress} disabled={disabled} 
          style={[defaultStyles.button, style]}>
          <View style={{opacity}}>
            <Text style={[defaultStyles.text, textStyle]}>{text}</Text>
          </View>
        </TouchableOpacity>
    )

}

In my opinion correct solution is to use setOpacityTo method.

In your render :

render() {
  const opacityValue = this.props.disabled ? 0.5 : 1;
  return (
    <TouchableOpacity style={{ opacity: opacityValue }} ref={(btn) => { this.btn = btn; }} onPress={this.onPress}>
      <Text>{this.props.text}</Text>
    </TouchableOpacity>
  );
}

And next you can use setOpacityTo method in componentDidUpdate on disabled props change:

  componentDidUpdate(prevProps) {
    const { disabled } = this.props;
    if (disabled !== prevProps.disabled) {
      const opacityValue = disabled ? 0.5 : 1;
      this.btn.setOpacityTo(opacityValue);
    }
  }

If you are using React Native version 0.63 onwards then Pressable is more elegant solution to go with and its Opacity change works as expected.

<Pressable
    style={{
       opacity: item.isSelected ? 0.5 : 1
    }}>
       //Your button content goes here
</Pressable>

For me it worked when I also changed the disabled prop together with the opacity.

I guess the issue is that the opacity in TouchableOpacity is an Animated.Value that overrides the value in the style prop and doesn't change, when the style prop changes...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM