React Navigation Global FAB - reactjs

I am using react-navigation v5 and ended up with App.js like this
const MainStack = createStackNavigator();
export default () => (
<NavigationNativeContainer>
<MainStack.Navigator
screenOptions={{
header: () => <AppbarHeader />,
}}
>
<MainStack.Screen name="index" component={index} />
<MainStack.Screen name="alternate" component={alternate} />
</MainStack.Navigator>
</NavigationNativeContainer>
);
I would like to add a Floating Action(FAB) Button that would be visible on the bottom of both the index and alternate page that would allow the user to show a Modal that is currently omitted. Right now I feel like the only solution is to put my FAB component inside both the index and alternate components but this doesn't seem right because it shouldn't re-render and transition in with the page. It should float above the page transition. I feel like it should be more of a global navigator of some sort but I am not sure how that should work with the existing StackNavigator shown above.
I am Looking forward to any solutions provided.

As #satya164 noted, you can put FAB in your root component. In addition, to access to navigation actions, you can use a ref to your navigation container.
const App = () =>{
const ref = React.useRef(null);
return (
<>
<NavigationNativeContainer ref={ref}>
// ...
</NavigationNativeContainer>
<FAB onPress={() => ref.current && ref.current.navigate('A SCREEN')}/>
</>
);

If you really want it to be global, put it in your root component and not inside a screen.
const App = () => (
<>
<NavigationNativeContainer>
// ...
</NavigationNativeContainer>
<FAB />
</>
);

Related

`Popover` (as React component) with `OverlayTrigger`

I'm trying to create rich React component as popover content.
If I use example with simple const popover (https://react-bootstrap.netlify.app/components/overlays/#examples-1) everything works fine.
Problem
But custom react component fails to position itself. It appears on top left of the screen
const MyPopover = React.forwardRef((props, ref) => {
return <Popover ref={ref} placement={"bottom"}>
<Popover.Header as="h3">
<Form.Control></Form.Control>
</Popover.Header>
<Popover.Body>
<strong>Holy guacamole!</strong> Check this info.
</Popover.Body>
</Popover>
})
const PopoverChooser = ({children, container}) => {
const _refTarget = useRef(null)
return <>
<OverlayTrigger
trigger="click"
placement="bottom"
overlay={<MyPopover ref={_refTarget}/>}
target={_refTarget.current}
>
{children}
</OverlayTrigger>
</>
}
export default PopoverChooser;
As you can see, I'v tried to use ref's, but it's doesn't help.
Question
How can it link popover to target button (in image as dropdown button and in code as {children}).
Or should I position MyPopover manually (by checking button ID and assigning position.. etc.?)
Completely different approach to dig in..?
Your approach to forward the ref was right. What you actually forgot is to also inject props. According to the documentation:
The and components do not position themselves.
Instead the (or ) components, inject ref and
style props.
https://react-bootstrap.netlify.app/components/overlays/#overview
So what you need to do is to spread the props like this:
const MyPopover = React.forwardRef((props, ref) => {
return (
<Popover ref={ref} {...props}>
https://codesandbox.io/s/trusting-sid-0050g9

Unexpected behavior of React render props

I've come up with the following code using React Transition Group:
const Transition = ({ elements, selectKey, render: Element, ...props }) => (
<TransitionGroup {...props}>
{elements.map((element) => (
<CSSTransition
key={selectKey(element)}
timeout={1000}
className="transition-slide"
>
<Element {...element} />
</CSSTransition>
))}
</TransitionGroup>
)
The key part here is that Transition component receives the render prop and renders it applying some transitions.
The way I expected for this to work:
<Transition render={(props) => <Toast {...props} />} />
But this code does not work as I expected: the transition of the next element interrupts the transition of the previous one.
However, this code works just fine:
const Element = (props) => <Toast {...props} />
// ...
<Transition render={Element} />
How can I fix this issue without putting the desired render-prop into a separate component?
Codesandbox: Example sandbox. The sandbox presents a non-working option with animation interruption. To get a working version, you need to uncomment lines 16 and 30 in the /Toasts/index.js file
P.S. I can't just use render={Toast} because I need to do ({id}) => <Toast dismiss={() => {deleteToast(id)}} />. I omitted this detail in order to simplify the understanding of the problem.
If you don't want to put the render function into another component, putting it into a useCallback() solved it for me.
const Toasts = () => {
const [toasts, addToast] = useToasts();
const Element = useCallback((props) => <Toast {...props} />, []);
return (
<div>
<button onClick={addToast}>Add toast</button>
<List>
<Transition
elements={toasts}
selectKey={({ id }) => id}
render={Element}
/>
</List>
</div>
);
}
(I don't quite understand the origin of the issue, but it has to do something with the function references.)

React navigation drawer doesn't open consistently when dynamically setting drawer position using the Context API

I'm trying to implement a single drawer whose drawer position and content can be dynamically changed.
I have a drawer navigator with a stack navigator inside. The header of the stack navigator has two buttons. The left button sets the drawerPosition to "left" and calls navigations.openDrawer() and the right button sets the drawerPosition to "right" and calls navigation.openDrawer().
My current implementation looks like this:
const Stack = createStackNavigator();
const Drawer = createDrawerNavigator();
const DrawerPositionContext = React.createContext([{}, () => {}]);
const DrawerPositionProvider = (props) => {
const [drawerPosition, setDrawerPosition] = useState({});
return (
<DrawerPositionContext.Provider value={[drawerPosition, setDrawerPosition]}>
{props.children}
</DrawerPositionContext.Provider>
);
};
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
{props.drawerPosition === 'left' ? (
<DrawerItem label="Left" />
) : (
<DrawerItem label="Right" />
)}
</DrawerContentScrollView>
);
}
const Screen1 = () => {
return <Text>Screen1</Text>;
};
const useDrawerPosition = () => {
const [drawerPosition, setDrawerPosition] = React.useContext(
DrawerPositionContext
);
return {
drawerPosition,
setDrawerPosition
};
};
const DrawerNavigator = () => {
const { drawerPosition, setDrawerPosition } = useDrawerPosition();
return (
<Drawer.Navigator
drawerPosition={drawerPosition}
drawerContent={(props) => (
<CustomDrawerContent {...props} drawerPosition={drawerPosition} />
)}
>
<Drawer.Screen name="stack navigator" component={StackNavigator} />
</Drawer.Navigator>
);
};
const StackNavigator = ({ navigation }) => {
const { setDrawerPosition } = useDrawerPosition();
return (
<Stack.Navigator
screenOptions={{
headerLeft: () => (
<Button
title="left"
onPress={() => {
setDrawerPosition("left");
navigation.openDrawer();
}}
/>
),
headerRight: () => (
<Button
title="right"
onPress={() => {
setDrawerPosition("right");
navigation.openDrawer();
}}
/>
)
}}
>
<Stack.Screen name="screen1" component={Screen1} />
</Stack.Navigator>
);
};
export default function App() {
return (
<DrawerPositionProvider>
<NavigationContainer>
<DrawerNavigator />
</NavigationContainer>
</DrawerPositionProvider>
);
}
So I use react context to share and update the current drawer position.
The behavior I'm experiencing is that opening the left drawer will always open the drawer correctly, but opening the right drawer will not open the drawer correctly most of the time. Instead of the drawer I only see the backdrop shadow.
snack
My first guess was that the context isn't updated before the drawer is opened, but converting the components to class-based components and using a setState callback gave the same result, so I'm not sure what is happening here.
I know the usual implementation for doing something like this is to create two drawers nested in a certain way, but it is it possible to do it with the approach I've tried?
Update
I think this is a bug. The problem seems to be inside Drawer.tsx (https://github.com/react-navigation/react-navigation/blob/main/packages/drawer/src/views/Drawer.tsx).
I'm not that familiar with Animated (https://reactnative.dev/docs/animated), but I think the problem is this code in componentDidUpdate:
if (prevProps.drawerPosition !== drawerPosition) {
this.drawerPosition.setValue(
drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT,
);
}
From messing around with the snack you linked, it seems like the context is actually updated. The problem only appears if you switch from "right" to "left". (This is somewhat supported by what I saw in the inspector, but I didn't dig deep).
See this updated snack that logs the position as it updates/bubbles and defaults to the "right" position. If you click the right button first, you can see it working, but if you click left it will break.
All in all, this is a reactnavigation bug IMHO. This is a problem that only seems to appear with drawerType="front", so as a workaround you could try adding a drawerType="back" prop to Drawer.Navigator.

Handling events across different exports with React

So I wanted to create a popup div that would slide from the side when an object has been selected and then exit when the object is re selected. I also want to create an exit button that would also close the div. I can pretty much understand how to do this except that I want to reuse this div component which is why I have kept it as an export in a different javascript file. This is where the issue is as I am having trouble handling the events across the files.
Here is my code:
/*Popup div export*/
export default () => {
const [toggle, set] = useState(true);
const { xyz } = useSpring({
from: { xyz: [-1000, 0, 0] },
xyz: toggle ? [0, 0, 0] : [-1000, 0, 0]
});
return (
<a.div
style={{
transform: xyz.interpolate(
(x, y, z) => `translate3d(${x}px, ${y}px, ${z}px)`
)
}}
className="expand"
>
<Link to={link}>
<a.button>Next</a.button>
</Link>
<button onClick={() => set(!toggle)}>Exit</button>
</a.div>
);
};
/*This is where I am implementing the export*/
<Canvas>
<Suspense fallback={<Fallback />}>
<Cube position={[-1.2, 0, 0]} onClick={e => <Toggle />} /> <---/*Here is the click event where I call the div*/-->
<Cube position={[1.2, 0, 0]} />
</Suspense>
</Canvas>
);
}
I have tried changing the styling to make the display 'hidden' and 'block' but this doesn't work as it doesn't show the slide in animation it just pops up. Furthermore, If I try to manipulate this separately, for example, create a click event within the canvas to make the div appear with react-spring, if I try to use the exit button, the click event doesn't work anymore.
Here is my sandbox to show what is happening. : (p.s sorry if this all seems confusing)
The codes are within Page1.js and toggle.js
https://codesandbox.io/s/sad-goldberg-pmb2y?file=/src/toggle.js:250-326
Edit:
simpler sandbox visual:
https://codesandbox.io/s/happy-chatelet-vkzjq?file=/src/page2.js
Your example is a bit confusing to follow, a simpler reproduction would be nice. That said, if I understand the overall goal, I think you want to store some global state (perhaps in your App.js component) that has some sort of state about the sidebar being visible.
For example:
function App() {
const [sidebarVisible, setSidebarVisible] = React.useState(false)
const toggleSidebar = () => setSidebarVisible(!sidebarVisible)
return (
<Router>
<Switch>
<Route path="/page1">
<Page1 toggleSidebar={toggleSidebar} />
</Route>
<Route path="/page2">
<Page2 toggleSidebar={toggleSidebar} />
</Route>
<Route path="/">
<Start toggleSidebar={toggleSidebar} />
</Route>
</Switch>
</Router>
)
}
function Page1({ toggleSidebar }) {
return <Toggle toggleSidebar={toggleSidebar} />
}
function Toggle({ toggleSidebar }) {
return <button onClick={toggleSidebar}>Toggle</button>
}
This is just to give you ideas, you could of course pass the setSidebarVisible function or make another function that stores some sort of state about what should show on the sidebar.
You could also use something like Redux or React Context to pass down state/actions into your components.
Hope this helps somewhat 👍🏻

Question about custom header in React Navigation v5 (Not clickable back button)

Hi I am using React navigation v5.
I am trying to implement custom header for specific screen. So my custom header looks like this
CustomHeader.js
export function CustomHeader({props}) {
const {scene, previous, navigation} = props;
const opacity = scene.route.params.opacity;
return (
<React.Fragment>
<Animated.View style={[styles.headerStyle, {opacity}]}>
<View style={styles.influencerNameContainer}>
<Text style={styles.influencerName}>
{scene.route.params.influencer.user.name}
</Text>
</View>
</Animated.View>
{previous ? (
/* This is a back button */
<Button
style={[styles.iconButton, {left: 0}]}
icon={BackIcon}
onPress={() => {navigation.goBack}
/>
) : (
undefined
)}
</React.Fragment>
);
}
Navigator looks like this
export function HomeStack() {
return (
<Stack.Navigator
initialRouteName="Home"
headerMode="screen"
style={{backgroundColor: 'yellow'}}>
<Stack.Screen
name="InfluencerScreen"
component={InfluencerScreen}
options={{
header: props => <CustomHeader props={props} />,
}}
/>
</Stack.Navigator>
);
}
It renders custom headers and custom back button but back button is not clickable.
So I tried.
<Stack.Screen
name="InfluencerScreen"
component={InfluencerScreen}
options={{
header: props => <CustomHeader props={props} />,
headerLeft: () => (
<Button title="Back Button" onPress={() => alert('Pressed')} />
)
}}
/>
With this code, it doesn't show back button at all with custom header. But it shows custom back button when I remove custom header(header: props => ).
What am I missing?
You're showing a custom header, how you render the button is up-to you. React Navigation cannot show a back button since React Navigation is no longer rendering the header.
Instead of using headerLeft option, you need to put the back button inside your custom header.
Also your destructuring is incorrect. function CustomHeader({props}) should be function CustomHeader(props) (without the curly braces).
You're destructuring props twice.. Try:
export function CustomHeader({scene, previous, navigation}) {
// const {scene, previous, navigation} = props; // remove this
...
}
Edit:
Also goBack is a method so call it with parentheses:
onPress={() => navigation.goBack()} // remove unnecessary `curly braces`

Resources