React Navigation 6 Hide Drawer Item - reactjs

How can I hide screens from appearing as items in the drawer of #react-navigation/drawer version 6.
In React Navigation 5 it was achieved by creating custom drawer content like this
const CustomDrawerContent = (props: DrawerContentComponentProps) => {
const { state, ...rest } = props;
const includeNames = ["ScreenOne", "ScreenTwo"];
const newState = { ...state }; //copy from state before applying any filter. do not change original state
newState.routes = newState.routes.filter(
(item) => includeNames.indexOf(item.name) !== -1
);
return (
<DrawerContentScrollView {...props}>
<DrawerItemList state={newState} {...rest} />
</DrawerContentScrollView>
);
};
As was described in this other answer. I have just upgraded to react-navigation 6 and with this technique I get an error TypeError: Cannot read property 'key' of undefined when I try to navigate one of these hidden screen. Can anyone help me out?
Update - Solution Found
I found a solution by myself so will put it here for anyone else looking. Set the drawer item style display hidden like this:
<DrawerStack.Screen
name="ScreenName"
component={ScreenComponent}
options={{
drawerItemStyle: {
display: "none",
},
}}
/>

Related

How to prevent components from refreshing when state in context is changed (React)

Im shooting my shot at making a tiktok clone as a first project to learn React. I want to have a global isVideoMuted state. When you toggle it, it should mute or unmute all sound of all videos.
Except something is not working properly. I understand that react re-renders everything when you change one thing within the contextprovider parent component from a childcomponent. This resets the tiktok "scrollprogress" to zero, since its a simple vertical slider. Is there anyway I can prevent this from happening?
This is my VideoContextProvider:
const VideoContextProvider = ({ children }: any) => {
const [isVideoMuted, setIsVideoMuted] = useState(true);
const [videos, setVideos] = useState([] as Video[]);
return (
<VideoContext.Provider
value={{
videos,
setVideos,
isVideoMuted,
setIsVideoMuted,
}}
>
{children}
</VideoContext.Provider>
);
};
And this is the VideoCard.tsx (one single video):
const VideoCard: FC<Props> = ({ video }) => {
const router = useRouter();
const [isLiked, setIsLiked] = useState(false);
const [isVideoPlaying, setIsVideoPlaying] = useState(false);
const { isVideoMuted, setIsVideoMuted } = useContext(VideoContext);
return (
.... (all the remaining videocode is here, including the <video>)
<Box p="xs">
{isVideoMuted ? (
<VscMute
fontSize="28px"
color="white"
onClick={() => setIsVideoMuted(false)}
/>
) : (
<VscUnmute
fontSize="28px"
color="white"
onClick={() => setIsVideoMuted(true)}
/>
)}
</Box>
...
);
};
export default VideoCard;
// export const VideoCardMemoized = memo(VideoCard);
)
See this video for an example of the bug: https://streamable.com/8ljkhl
Thanks!
Edit:
What I've tried so far:
Making a memoized version of the VideoCard and using that, refreshing still occurs
Moving const { isVideoMuted, setIsVideoMuted } = useContext(VideoContext); to a seperate component (VideoSidebar). Problem still occurs, since I have to 'subscribe' to the isVideoMuted variable in the video element
It rerenders only components that are subscribed to that context.
const { isVideoMuted, setIsVideoMuted } = useContext(VideoContext);
To prevent rerendering child components of the subscribed components you can use React.memo
export default React.memo(/* your component */)

React - Leaflet MarkerCluster with Popup and Tabs

I am having an issue using the MarkerCluster leaflet component with popup and React-Tabs.
The issue is when I try to reset selected tab inside the popup, it's causing infinite loop This seems to be only when MarkerCluster group is used, otherwise it's working fine for a single marker
My code is as below
custom marker component
const ExtendedMarker = props => {
const initMarker = ref => {
if (ref && props.isOpenMarker) {
ref.leafletElement.openPopup();
}
};
return <Marker ref={initMarker} {...props} />;
};
class CustomMarker extends React.Component {
render() {
const { icon, stop, isDisabledBtn, isOpenMarker, ...props } = this.props
return (
<ExtendedMarker
icon={icon}
position={[stop.latitude, stop.longitude]}
isOpenMarker={isOpenMarker}
>
<Popup minWidth={260} closeButton={true} onOpen={() => this.setState({ tabIndex: 0 })}>
<Tabs selectedIndex={this.state.tabIndex} onSelect={tabIndex => this.setState({ tabIndex })}>
<TabList>
.
.
.
.
index.js
<MarkerClusterGroup showCoverageOnHover={false} maxClusterRadius={50}>
{currentStops.map(stop => (
<CustomMarker
key={v4()}
icon={getCategoryIconMarker(stop.category)}
stop={stop}
{...this.props}
/>
))}
</MarkerClusterGroup>
So this code works fine when MarkerClusterGroup is removed otherwise it's causing an Error: Maximum update depth exceeded
Any help would be appreciated.
Thank You
I think that is an error pattern I encountered when trying to use a function in the following way:
{getCategoryIconMarker(stop.category)}
If you instead use an arrow function, that may improve the situation. At least in my case the error disappeared. So, just replace the function above with:
{() => getCategoryIconMarker(stop.category)}
Hope someone will find it useful.

React Material-UI menu anchor broken by react-window list

I am using Material-UI and react-window in a project. My issue is, the material-ui menu component does not anchor to the element provided when that element is within a react-window virtualized list. The menu will appear in the upper left corner of the screen instead of anchored to the button that opens it. When using it all in a non-virtualized list, it works as expected. The menu properly anchors to the button that opens it.
Here's an example sandbox. The sandbox is pretty specific to how I'm using the components in question.
Any guidance on how I can resolve this?
Here's a modified version of your sandbox that fixes this:
Here was your initial code in BigList:
const BigList = props => {
const { height, ...others } = props;
const importantData = Array(101 - 1)
.fill()
.map((_, idx) => 0 + idx);
const rows = ({ index, style }) => (
<FancyListItem
index={index}
styles={style}
text="window'd (virtual): "
{...others}
/>
);
return (
<FixedSizeList
height={height}
itemCount={importantData.length}
itemSize={46}
outerElementType={List}
>
{rows}
</FixedSizeList>
);
};
I changed this to the following:
const Row = ({ data, index, style }) => {
return (
<FancyListItem
index={index}
styles={style}
text="window'd (virtual): "
{...data}
/>
);
};
const BigList = props => {
const { height, ...others } = props;
const importantData = Array(101 - 1)
.fill()
.map((_, idx) => 0 + idx);
return (
<FixedSizeList
height={height}
itemCount={importantData.length}
itemSize={46}
outerElementType={List}
itemData={others}
>
{Row}
</FixedSizeList>
);
};
The important difference is that Row is now a consistent component type rather than being redefined with every render of BigList. With your initial code, every render of BigList caused all of the FancyListItem elements to be remounted rather than just rerendered because the function around it representing the "row" type was a new function with each rendering of BigList. One effect of this is that the anchor element you were passing to Menu was no longer mounted by the time Menu tried to determine its position and anchorEl.getBoundingClientRect() was providing an x,y position of 0,0.
You'll notice in the react-window documentation (https://react-window.now.sh/#/examples/list/fixed-size) the Row component is defined outside of the Example component similar to how the fixed version of your code is now structured.
Ryan thanks for your answer! It helped me!
There is another solution:
Defining the parent component as a class component (not a functional component).
My problem was that I was calling the 'Rows' function like so:
<FixedSizeList
height={height}
itemCount={nodes.length}
itemSize={50}
width={width}
overscanCount={10}
>
{({ index, style }) => this.renderListItem(nodes[index], style)}
</FixedSizeList>
The fix was similar to what Ryan suggested:
render() {
...
return <FixedSizeList
height={height}
itemCount={nodes.length}
itemSize={50}
width={width}
overscanCount={10}
itemData={nodes}
>
{this.renderListItem}
</FixedSizeList>
}
renderListItem = ({data,index, style}) => {
...
}
I used the itemData prop to access the nodes array inside the renderListItem function

Hiding specific tabs on react navigation bottomTabBarNavigation

Imagine I have this structure for my tab bar:
const TabBarRoute = {
Home: HomeStack,
Orders: OrdersStack,
Trending: TrendingStack,
TopSelling: TopSellingStack
}
I want to show only three tabs (Orders, Trending, TopSelling) in my bottom tab navigator, How can I achieve it by react-navigation version 3.x ?
One idea that come to my mind is that I create a top stackNavigator and nest HomeStack along with my bottomTabNavigator inside of that and set the initial route of the top stackNavigator to the Home but problem with this approach is that it doesn't show the tab bar.
This example is using createBottomTabNavigator, but I imagine the same rules apply. It requires overriding the tabBarButtonComponent with a Custom component which returns null when the user doesn't have access to the given Tab.
const Config = {
allow_locations: true,
allow_stations: true
}
LocationsStackNavigator.navigationOptions = ({navigation}) => {
return {
tabBarLabel: 'Locations',
tabBarTestID: 'locations',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={'md-settings'}/>
)
}
};
const MyTabNav = createBottomTabNavigator(
{
LocationStackNavigator,
StationsStackNavigator,
OrdersStackNavigator,
SettingsStackNavigator
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarButtonComponent: CustomTabButton
})
}
);
class CustomTabButton extends React.Component {
render() {
const {
onPress,
onLongPress,
testID,
accessibilityLabel,
...props
} = this.props;
if(testID === 'locations' && !Config.allow_locations) return null;
if(testID === 'stations' && !Config.allow_stations) return null;
return <TouchableWithoutFeedback onPress={onPress} onLongPress={onLongPress} testID={testID} hitSlop={{ left: 15, right: 15, top: 5, bottom: 5 }} accessibilityLabel={accessibilityLabel}>
<View {...props} />
</TouchableWithoutFeedback>;
}
}
if you want to have dynamic items which they can change according to your situation you can do it in 2 ways :
first, you can declare a component as a parent of your react-navigation component then
create a store for your items and set them as #observable and change them whenever you want
declare your items as a state of your parent component and pass them to your react navigation
In these two solutions, every time your items change caused your component re-render with new values

React Navigation change Tab order programmatically

I'm creating an app with react-native with using react-navigation for routing. I know we can change tab order in react-navigation initially. But in my case, I need to change tab order programmatically. Is there any way to do that?
The following is an "almost pseudo code" example. It should at least drive you in the right direction. The trick is to use a "connected" main navigation component which reacts to changes in a redux store (in my case I stored the tabs order in a "settings" reducer) and force re-render of the tabs and their order, changing the screenProps property passed down by react-navigation to each navigator. Then there is a TabSelector component which returns the correct screen based on the props passed. I'm sorry if it's not totally clear what I mean but English is not my primary language :)
import Tab1 from 'app/components/Tab1';
import Tab2 from 'app/components/Tab2';
// ... all the imports for react-navigation
const TabSelector = (props) => {
switch(props.tab) {
case 1:
return <Tab1 {...props} />;
case 2:
return <Tab2 {...props} />;
}
};
const Tabs = {
PreferredTab: {
screen: ({ screenProps }) => (
<TabSelector tab={screenProps.firstTab} />
),
navigationOptions: ({ screenProps }) => ({
// write label and icon based on screenProps.firstTab
})
},
OtherTab: {
screen: ({ screenProps }) => (
<TabSelector tab={screenProps.otherTab} />
),
navigationOptions: ({ screenProps }) => ({
// write label and icon based on screenProps.otherTab
})
},
// other tabs...
};
const Navigator = createTabNavigator(Tabs, {
initialRouteName: 'PreferredTab',
// other options...
});
const WrappedNavigator = props => {
// fetch tab index from redux state, for example
const firstTab = useSelector(state => state.settings.firstTab);
const otherTab = useSelector(state => state.settings.otherTab);
return <Navigator screenProps={{ firstTab: firstTab, otherTab: otherTab }} {...props} />;
};
WrappedNavigator.router = Navigator.router;
export default createAppContainer(WrappedNavigator);

Resources