Conditional rendering cannot be seen due to a return before them

Viewed 644

Please help give me a better title, I couldn't word what I am asking.

To understand my question I have to provide some context of my project. Originally I wanted to conditionally render two pages through two buttons. Button A rendering screen A and button B rendering screen B. After figuring out how to pass the state from a parent component to a child and its child etc, I changed my button to a sliding animation for better design.

This causes issues because now when a new screen is rendered, the animation does not show because it is simply re-rendered with the original starting place in the animation(I rendered the slider through each screen). I thought about providing two different sliders, each starting in the opposing opposition but that would still lose the entire slide effect.

I have now resulted to rendering the Slider so it is there all the time and is not re-rendered. However I have realized now that if I return it before my conditionals, that code is never reached. I have provided a working demo that shows my problem perfectly as well as the code below(I only provided App.js, the rest is on the demo if needed). I want to render Slider in App.js.

The working demo is here, you can see the slider does not slide, it just changes screens. I need it to slide. Also the sliding animation only works on iphone so I would use that emulator rather than the web.

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      whichComponentToShow: "Screen1"
    };
  }

  goToMap = () => {
    this.setState({ whichComponentToShow: "Screen2" });
  };
  goToList = () => {
    this.setState({ whichComponentToShow: "Screen1" });
  };

  render() {
    const { whichComponentToShow } = this.state;
    
    /* This is how I thought I could render this, but obv it makes the rest of the code unreachable.
       How can I render this and then have the conditional page below? Each time the new page renders, 
       it stops the animation from working due to rendering the new page.
    return(
      <Slider/>
    )*/

    if(this.state.whichComponentToShow === 'Screen1'){
        return(
          <View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>

            <ListHome
              renderMap = {this.goToMap.bind(this)}
              renderList = {this.goToList.bind(this)}
            />
          </View>
      );
    }
    else if(this.state.whichComponentToShow === 'Screen2'){
      return(
       <View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
       
        <MapHome
          renderMap = {this.goToMap.bind(this)}
          renderList = {this.goToList.bind(this)}
        />
  
       </View>
      );
    }

Slider.js (wont show up on the snack apparently

const Slider = (props) => {
  
  const [active, setActive] = useState(false)
  let transformX = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    if (active) {
      Animated.timing(transformX, {
        toValue: 1,
        duration: 300,
        useNativeDriver: true
      }).start()
    } else {
      Animated.timing(transformX, {
        toValue: 0,
        duration: 300,
        useNativeDriver: true
      }).start()
    }
  }, [active]);

  const rotationX = transformX.interpolate({
    inputRange: [0, 1],
    outputRange: [2, Dimensions.get('screen').width / 4]
  })


  return (
    <SafeAreaView style={{
      flex: 1,
      alignItems: 'center'
    }}>
      <View style={{
        flexDirection: 'row',
        position: 'relative',
        height: 45,
        width: 240,
        borderRadius: 10,
        backgroundColor: 'white',
        marginHorizontal: 5
      }}>
        <Animated.View
          style={{
            position: 'absolute',
            height: 45 - 2*2,
            top: 2,
            bottom: 2,
            borderRadius: 10,
            width: Dimensions
            .get('screen').width / 3 - 3.5 ,
            transform: [
              {
                translateX: rotationX
              }
            ],
            backgroundColor: '#d1cfcf',
          }}
        >
        </Animated.View>
        <TouchableOpacity style={{
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center'
        }} onPress={() => {setActive(false); props.renderList() }}>
          <Text>
            List
        </Text>
        </TouchableOpacity>
        <TouchableOpacity style={{
          flex: 1,
          justifyContent: 'center',
          alignItems: 'center'
        }} onPress={() => {setActive(true); props.renderMap() }}>
          <Text>
            Map
        </Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
}


export default Slider

1 Answers

I tried your expo snack and saw no code related to animation, so I'm assuming the code in the snack isn't your current code, and that you really do already have a working, animated, <Slider ... />.

In your situation, what you could do to keep your Slider rendered and not unmounted, is to use variables in the render() method.

Basically, you can assign the <Slider .../> JSX to a variable, and you can use that variable in another JSX part later.

Assigning a key to the specific JSX also helps guide React that this is the same component between render calls, so it also prevents unintentional rerenders of that component.

Here's an edit with comments from what you wrote in your post. I hope this makes sense.

export default class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            whichComponentToShow: "Screen1"
        };
    }

    goToMap = () => {
        this.setState({ whichComponentToShow: "Screen2" });
    };
    goToList = () => {
        this.setState({ whichComponentToShow: "Screen1" });
    };

    render() {
        const { whichComponentToShow } = this.state;

        /* 
        Keep the slider JSX in a variable to be used.
        Setting a specific key also helps prevent it from being accidentally re-rendered in some conditions.
        )*/
        const sliderRender = <Slider key='slider' />;

        if (this.state.whichComponentToShow === 'Screen1') {
            return (
                <View style={{ backgroundColor: '#d1cfcf', flex: 1 }}>

                    <ListHome
                        renderMap={this.goToMap.bind(this)}
                        renderList={this.goToList.bind(this)}
                    />
                    {/* Put the rendered slider into the render tree */}
                    {sliderRender}
                </View>
            );
        }
        else if (this.state.whichComponentToShow === 'Screen2') {
            return (
                <View style={{ backgroundColor: '#d1cfcf', flex: 1 }}>

                    <MapHome
                        renderMap={this.goToMap.bind(this)}
                        renderList={this.goToList.bind(this)}
                    />
                    {/* Put the rendered slider into the render tree */}
                    {sliderRender}
                </View>
            );
        }
    }
}

Edit : Expo Snack demonstrating it working

enter image description here

Related