skip to Main Content

A way to get around react rendering method?

i.e When a new component is conditionally rendered, the components below move further down to accommodate the new entry, but the animation feels forceful, as opposed to something like Transform: translateY etc.

e.g
Home.js


return (
<View>

   <View>
      <Text>Render Something </Text>
   </View>

   { showmore ? <Tasks /> : null}


   <TouchableOpacity>
        <Text> Button </Text>
   </TouchableOpacity>


   </View>
)

Tasks.js

return(
  <View> 
     <Text> render some  more things </Text>
     <Text> render some  more things </Text>
     <Text> render some  more things </Text>
     <Text> render some  more things </Text>
     <Text> render some  more things </Text>
</View> 

)

In the above code, on "Showmore is truthy", Touchableopacity is forced down to accommodate the tasks, i understand the need to rerender the layout, as that space was precviously assigned to = null. But i need it a lot more fluid than it is.

I have tried tried hard coded values like:

 { showmore ? <Tasks /> : <View style ={{marginVertical: 50}} />}

which provides better entry for the Tasks without the "forceful" need to push down components below as the space was already accounted for (but bad practise i am guessing)

— I also tried onLayout in <Tasks> to perhaps transform : translateY the position of components below, from the derived height of height but the output of the has been rendered before i could even get the value to work within Home.js.

I am lost as to another alternative to work around this. Any suggestions???

2

Answers


  1. Chosen as BEST ANSWER

    I found a straightforward approach using the Layout Animation API that handles all use cases I have tried thus far.

    import { 
       Animated,
       Easing, 
       withTiming, 
       useSharedValue, 
       useAnimatedStyle 
       }  from 'react-native-reanimated';
    
      const translateYValue = useSharedValue(0);
    
      useEffect(() => {
        LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
        translateYValue.value = withTiming(50, {
          duration: 100,
          easing: Easing.ease,
        });
    
      }, [showmore])
    
    

    Add all states that you would typically like to cause a change to the layout of the screen in the dependencies of the useEffect.


  2. I spent a bit too long trying to figure out why animating the height caused the components under Tasks to behave unexpectedly. The problem was that I was trying to both animate and measure the height of the same view. Once I separated the two everything worked as expected:

    Tasks.js

    import { View, Text, StyleSheet } from 'react-native';
    import { useEffect } from 'react';
    import Animated, {
      useSharedValue,
      withTiming,
      useAnimatedStyle,
      interpolate,
    } from 'react-native-reanimated';
    import useLayout from '../hooks/useLayout';
    
    // used with interpolate to simulate a delay in the animation
    const delayedInputRange = [0, 0.5, 1];
    
    export default function ({ showMore }) {
      const [layout, onLayout] = useLayout();
      const anim = useSharedValue(0);
      const animStyle = useAnimatedStyle(() => {
        return {
          height: interpolate(
            anim.value,
            [0, 1],
            // on mobile devices if height is set to 0
            // the View wont  get rendered to screen even
            // with height changes
            [StyleSheet.hairlineWidth, layout.height]
          ),
          // interpolate to simulate delay opacity animation
          opacity: interpolate(anim.value, delayedInputRange, [0, 0, 1]),
          overflow: 'hidden',
          // interpolate to simulate delay marginVertical animation
          marginVertical: interpolate(anim.value, delayedInputRange, [20, 19, 0]),
        };
      });
    
      useEffect(() => {
        // update sharedValue when toggle changes
        anim.value = withTiming(showMore ? 1 : 0);
      }, [showMore, anim]);
      return (
        <Animated.View style={animStyle}>
          <View onLayout={onLayout}>
            <Text> render some more things </Text>
            <Text> render some more things </Text>
            <Text> render some more things </Text>
            <Text> render some more things </Text>
            <Text> render some more things </Text>
          </View>
        </Animated.View>
      );
    }
    

    Parent container:

    import { Text, View, TouchableOpacity } from 'react-native';
    import useToggle from './hooks/useToggle';
    import Tasks from './components/Tasks';
    
    export default function App() {
      const [showMore, toggleShowMore] = useToggle(false);
    
      return (
        <View>
          <View>
            <Text>Render Something </Text>
          </View>
          <Tasks showMore={showMore} />
          <TouchableOpacity onPress={toggleShowMore}>
            <Text>Show {showMore ? 'less' : 'more'}</Text>
          </TouchableOpacity>
        </View>
      );
    }
    

    useLayout.js

    import { useState, useCallback } from 'react';
    
    export default function useLayout() {
      const [layout, setLayout] = useState({
        width: 0,
        height: 0,
        x: 0,
        y: 0,
      });
      const onLayout = useCallback((e) => setLayout(e.nativeEvent.layout), []);
      return [layout,onLayout]
    }
    

    useToggle.js

    import { useState, useCallback } from 'react';
    
    export default function useToggle(val) {
      const [bool, setBool] = useState(val);
      const toggle = useCallback(() => setBool(prev=>!prev), []);
      return [bool,toggle]
    }
    
    

    demo

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search