Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v6][Feature]: Supporting stack navigation for react-native. #7943

Closed
CooCooCaCha opened this issue Aug 12, 2021 · 2 comments
Closed

[v6][Feature]: Supporting stack navigation for react-native. #7943

CooCooCaCha opened this issue Aug 12, 2021 · 2 comments

Comments

@CooCooCaCha
Copy link

CooCooCaCha commented Aug 12, 2021

What is the new or updated feature that you are suggesting?

I'm using react-router-native v6 and I'm trying to implement a stack navigation hook. However, I've run into issues because useRoutes doesn't wrap its children in a location context. Because of this, cards lower in the stack will render blank because the current route no longer matches.

Details

The hook looks like this:

  const route = useStack([
    { path: "/", element: <Home /> },
    { path: "/posts/:id", element: <Post /> },
    { path: "/library/:id", element: <LibraryItem /> }
  ]);

If I'm on the home route and I navigate to a post then the post route should appear on top of the home route. If I start to swipe from the edge of the screen then the post route should slide away and reveal the home route again.

Internally this works by storing a list of locations like so:

  const [stack, setStack] = useState([]);

When the location changes it gets added to the stack. Each item in the stack gets rendered as a <StackCard /> component:

const StackCard = ({ routes, location }: any) => {
  const route: any = useRoutes(routes, "", location);
  return route;
};

Why should this feature be included?

Stack navigation is important for achieving native look and feel in mobile apps. Supporting this kind of behavior would make react-router even more viable for react-native apps.

@CooCooCaCha
Copy link
Author

CooCooCaCha commented Aug 12, 2021

For reference, here's my code. You should be able to drop this into the latest expo with react-router, react-router-native, react-native-reanimated, and react-native-gesture-handler installed.

import React, { useState, useEffect } from "react";
import { StyleSheet, useWindowDimensions } from "react-native";
import {
  useLocation,
  matchRoutes,
  useRoutes,
  useNavigate,
} from "react-router-native";
import Animated, {
  useAnimatedGestureHandler,
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  runOnJS,
} from "react-native-reanimated";
import { PanGestureHandler } from "react-native-gesture-handler";

const useStack = (routes: any) => {
  const windowDimensions = useWindowDimensions();
  const location = useLocation();
  const navigate = useNavigate();

  const [stack, setStack]: any[] = useState([]);

  useEffect(() => {
    const matches = matchRoutes(routes, location);
    if (!matches) {
      return;
    }

    console.log("MATCHES", matches);
    const newStackItem = {
      key: matches[0].pathname,
      location: { ...location },
    };

    if (stack.length === 0) {
      setStack([newStackItem]);
      return;
    }

    const index = stack.findIndex(
      (stackItem: any) => stackItem.key === newStackItem.key
    );

    if (index === stack.length - 1) {
      setStack([...stack.slice(0, -1), newStackItem]);
      return;
    }

    if (index >= 0) {
      setStack(stack.slice(0, index + 1));
      return;
    }

    setStack([...stack, newStackItem]);
  }, [location]);

  const onAnimationComplete = () => {
    x.value = 0;
    navigate(-1);
  };

  const x = useSharedValue(0);
  const gestureHandler = useAnimatedGestureHandler({
    onActive: (event, ctx) => {
      x.value = event.translationX;
    },
    onEnd: (_) => {
      if (x.value > 100) {
        x.value = withTiming(windowDimensions.width, {}, () => {
          runOnJS(onAnimationComplete)();
        });
      } else {
        x.value = withTiming(0);
      }
    },
  });

  console.log("-------------------");
  console.log("LOCATION", location);
  console.log("STACK", stack);

  return (
    <PanGestureHandler onGestureEvent={gestureHandler}>
      <Animated.View style={styles.container}>
        {stack.map(({ key, location }: any, index: number) => (
          <StackCard
            key={key}
            routes={routes}
            location={location}
            isTopCard={index === stack.length - 1}
            x={x}
          />
        ))}
      </Animated.View>
    </PanGestureHandler>
  );
};

const StackCard = ({ routes, location, isTopCard, x }: any) => {
  const route: any = useRoutes(routes, "", location);

  const animatedStyle = useAnimatedStyle(() => {
    const translateX = isTopCard ? x.value : 0;
    return {
      transform: [{ translateX }],
    };
  }, [isTopCard]);

  return (
    <Animated.View style={[styles.screen, animatedStyle]}>
      {route}
    </Animated.View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    position: "relative",
  },
  screen: {
    position: "absolute",
    backgroundColor: "#fff",
    top: 0,
    bottom: 0,
    left: 0,
    right: 0,
    shadowColor: "#000",
    shadowRadius: 10,
    shadowOpacity: 0.25,
    shadowOffset: {
      width: 0,
      height: 2,
    },
  },
});

export default useStack;

@CooCooCaCha CooCooCaCha changed the title [v6][Feature]: Please provide a way to wrap routes in a location context. [v6][Feature]: Supporting stack navigation for react-native. Aug 17, 2021
@timdorr
Copy link
Member

timdorr commented Aug 31, 2021

Since this would be resolved by #7117, I'm going to close in favor of that issue.

@timdorr timdorr closed this as completed Aug 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants