change component property which is mapped - reactjs

I am using redux to track which screen user is at.
I have a button tab component:
Tab.js:
class Tab extends Component {
render() {
return (
<Button onPress={() => navigation.navigate(route)}>
<Icon
style={selected ? deviceStyle.tabSelectedColor : deviceStyle.tabDefaultColor}
type="Ionicons"
name={icon}
/>
<Text style={selected ? deviceStyle.tabSelectedColor : deviceStyle.tabDefaultColor}>
{title}
</Text>
</Button>
);
}
}
Then I call it from another component:
const items = [
{
screen: 'home',
title: 'home',
icon: 'apps',
route: 'Home'
}]
renderTab = () => {
return items.map((tabBarItem, index) => {
return (
<Tab
key={index}
title={tabBarItem.title}
icon={tabBarItem.icon}
route={tabBarItem.route}
/>
);
});
};
Now, when user change screen, how can I change the selected props in tab component?

There are two ways to achieve this.
1) Onclick / onpress of your Tab you can dispatch an action, which will update user's current screen, as currently, you are navigating user on that page.
2) On load of screen, which is going to load after tab click/press.
You just need an action which will update user's current screen

Related

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`

React navigation not going to other screen

I'm trying to navigate to other screen using TouchableHighlight / button but it does not navigate to the other screen .
there are no error's .
Nothing happens when i press the TouchableHighlight/button its in the same screen .
trackButton() {
const navigation = this.props.navigation;
return (
<View style={styles.footer}>
{/* <TouchableHighlight underlayColor='transparent' onPress={() => this.gotoTrackingScreen()}> */}
<TouchableHighlight underlayColor='transparent' onPress={() => this.props.navigation.navigate('faq')}>
<Text style={this.trackingButtonStyleOS()}>
START TRACKING
</Text>
</TouchableHighlight>
{/* <Button
onPress={() => this.props.navigation.navigate('Tracking')}
title="Chat with Lucy"
/> */}
</View>
);
}
we are currently using react navigation:1.0.1 beta
StackNavigator is implemented in index.android.js
const BeaconApp = StackNavigator({
Home: {
screen: App,
navigationOptions: {
header: null
}
},
Tracking: {
screen: TrackingScreen,
navigationOptions: {
header: null
}
}})
I think that the naming of your component didn't come with the standards.
I saw that you try to make trackButton component which later will be implemented by TrackingScreen.
React will determined function as a component if it written in UpperCamelCase, not lowerCamelCase. Props will not be received in your component because it will be assumed as regular function and regular function don't have props passing feature.
Because of that, your trackButton component didn't receive the props.navigation and nothing happen when you click the button

Switch view depending on button click

I am new to react native.
My question is pretty simple: my screen contains 5 buttons. Each one opens the same < Modal > component. I need to dynamically change the content of the modal, depending on the button clicked.
For example:
if I click the first button, a text input will be shown into the modal.
If I click the second button, checkboxes will be shown into the modal.
Here's my modal :
<Modal
visible={this.state.modalVisible}
animationType={'slide'}
onRequestClose={() => this.closeModal()}>
<View style={style.modalContainer}>
<View style={style.innerContainer}>
<Text>This is content inside of modal component</Text>
<Button
onPress={() => this.closeModal()}
title="Close modal"
>
</Button>
</View>
</View>
</Modal>
Here I open it :
openModal() {
this.setState({ modalVisible: true });
}
Here I call the function (on button press) :
onPress={() => this.openModal()}
I've heard about using props/children, but I don't know how to use them is this case.
Can anyone please help ?
Here is quick example to show who to render different content based on input you provide.
Modal Content
renderModalContent(type, data) {
switch(type) {
1: {
return (
<View>{..data}</View>
)
}
2: {
return (
<Button>...</Button>
)
}
default: (<CustomComponent data={data} />)
}
}
Modal
<Modal>
<View>
{this.renderModalContent(this.state.type, this.state.modalContentData)}
</View>
</Modal>
Here you decide which view you want to render and pass its data.
openModal() {
this.setState({ modalVisible: true, type: 1, data: {...} });
}
You should modify your Modal component so that it renders the base layout with space for dynamic content to be rendered. The content will be passed in as children, via Props. This will mean the modal is dynamic and will / should support future requirements. Try to avoid the switch case in the modal render suggestion unless you have very specific requirements that are unlikely to change in the future, or if you want to do things the React way.
Then for each variant of your Modal (TextInput, Checkbox etc.) create a new Component that wraps the Modal component and have each button initiate rendering the specific component.
If you're using Redux then you would be creating containers, connecting to Redux and passing dynamic state variables. You don't have to use Redux but the principle is the same.
Here's a very basic example to illustrate my point.
// Basic modal that renders dynamic content
const Modal = props => {
const { children } = props;
render (
<View style={styles.modal} >
{children}
</View>
);
}
// Specific modal implementation with TextInput
const ModalWithTextInput = props => (
<Modal>
<TextInput
value={props.someValue}
/>
</Modal>
)
// Specific modal implementation with Switch
const ModalWithSwitch = props => (
<Modal>
<Switch
value={props.someValue}
/>
</Modal>
)
Then in your component that launches the modals, do something like this...
class MyComponent extends Component {
openTextModal = () => {
this.setState({ modalType: 'text' });
}
openSwitchModal = () => {
this.setState({ modalType: 'switch' });
}
renderModal = (type) => {
if (type === 'text') {
return(<ModalWithTextInput />)
}
if (type === 'switch') {
return(<ModalWithSwitch />)
}
}
render() {
const { modalType } = this.state;
render (
<View>
<View>
<TouchableWithX onPress={this.openTextModal} />
<TouchableWithX onPress={this.openSwitchModal} />
</View>
<View>
{this.renderModal(modalType)}
</View>
</View>
);
}
}
Please note this code has not tested but the principle is sound.

React Navigation Show/Hide Drawer Items

React Native using React Navigation - Show/Hide Drawer Item
I was wondering if you guys or maybe someone have come up of a better Idea of showing or hiding some menu or Drawer Item under DrawerNavigator.
Basically I have user roles and I want to show/hide selected menu's based on user's role.
My setup now is that I have A DrawerNavigator nested within a StackNavigator.
Menu That Contains My Drawer Items
Hide some drawer items based on user role
Currently Using:
react version 16.0.0-alpha.12
react-native version 0.46.0
react-navigation version 1.0.0-beta.11
You can create your own custom component that will be rendering drawer items
contentComponent: CustomDrawerContentComponent
inside that component you can define logic on show hide your items
example:
const CustomItems = ({
navigation: { state, navigate },
items,
activeItemKey,
activeTintColor,
activeBackgroundColor,
inactiveTintColor,
inactiveBackgroundColor,
getLabel,
renderIcon,
onItemPress,
style,
labelStyle,
}: Props) => (
<View style={[styles.container, style]}>
{items.map((route: NavigationRoute, index: number) => {
const focused = activeItemKey === route.key;
const color = focused ? activeTintColor : inactiveTintColor;
const backgroundColor = focused
? activeBackgroundColor
: inactiveBackgroundColor;
const scene = { route, index, focused, tintColor: color };
const icon = renderIcon(scene);
const label = getLabel(scene);
return (
<TouchableOpacity
key={route.key}
onPress={() => {
console.log('pressed')
onItemPress({ route, focused });
}}
delayPressIn={0}
>
<View style={[styles.item, { backgroundColor }]}>
{icon ? (
<View style={[styles.icon, focused ? null : styles.inactiveIcon]}>
{icon}
</View>
) : null}
{typeof label === 'string' ? (
<Text style={[styles.label, { color }, labelStyle]}>{label}</Text>
) : (
label
)}
</View>
</TouchableOpacity>
);
})}
</View>
);
So in the code above you can define which route will be shown, for instance you can have store with list of items show or hide and from here you can make comparison.
Hope it helps

Keeping Material UI tabs and React Router in sync

Is there a non hacky way to keep Material UI tabs and React router in sync?
Basically, I want to change the URL when the user clicks on a tab [1] and the tabs should change automatically when the user navigates to a different page with a non-tab link or button, and of course on direct access [2] and page refresh too.
Also, it would be nice to have the react router's non exact feature too, so the /foo tab should be active both for /foo and /foo/bar/1.
[1] Other SO answers recommend using the history api directly, is that a good practice with react-router?
[2] I'm not sure what it's called, I meant when the user loads for example /foo directly instead of loading / and then navigating to /foo by a tab or link
Edit:
I created a wrapper component which does the job, but with a few problems:
class CustomTabs extends React.PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
setActiveTab(id) {
this.setState({
activeTab: id
});
return null;
}
render() {
return (
<div>
{this.props.children.map((tab,index) => {
return (
<Route
key={index}
path={tab.props.path||"/"}
exact={tab.props.exact||false}
render={() => this.setActiveTab(index)}
/>
);
})}
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
style={{paddingLeft: '10px', paddingRight: '10px', height: '64px'}}
onActive={() => {
this.props.history.push(tab.props.path||"/")
}}
/>
);
})}
</Tabs>
</div>
);
}
}
And I'm using it like this:
<AppBar title="Title" showMenuIconButton={false}>
<CustomTabs history={this.props.history}>
<Tab label="Home" path="/" exact/>
<Tab label="Foo" path="/foo"/>
<Tab label="Bar" path="/bar"/>
</CustomTabs>
</AppBar>
But:
I get this warning in my console:
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
I think it's because I set the state immediately after render() is called - because of Route.render, but I have no idea how to solve this.
The tab changing animations are lost: http://www.material-ui.com/#/components/tabs
Edit #2
I finally solved everything, but in a bit hacky way.
class CustomTabsImpl extends PureComponent {
constructor() {
super();
this.state = {
activeTab: 0
}
}
componentWillMount() {
this.state.activeTab = this.pathToTab(); // eslint-disable-line react/no-direct-mutation-state
}
componentWillUpdate() {
setTimeout(() => {
let newTab = this.pathToTab();
this.setState({
activeTab: newTab
});
}, 1);
}
pathToTab() {
let newTab = 0;
this.props.children.forEach((tab,index) => {
let match = matchPath(this.props.location.pathname, {
path: tab.props.path || "/",
exact: tab.props.exact || false
});
if(match) {
newTab = index;
}
});
return newTab;
}
changeHandler(id, event, tab) {
this.props.history.push(tab.props['data-path'] || "/");
this.setState({
activeTab: id
});
}
render() {
return (
<div>
<Tabs
style={{height: '64px'}}
contentContainerStyle={{height: '100%'}}
tabItemContainerStyle={{height: '100%'}}
onChange={(id,event,tab) => this.changeHandler(id,event,tab)}
value={this.state.activeTab}
>
{this.props.children.map((tab,index) => {
return (
<Tab
key={index}
value={index}
label={tab.props.label||""}
data-path={tab.props.path||"/"}
style={{height: '64px', width: '100px'}}
/>
);
})}
</Tabs>
</div>
);
}
}
const CustomTabs = withRouter(CustomTabsImpl);
Firstly, thanks for replying to your very question.
I have approached this question differently, I decided to post here for the community appreciation.
My reasoning here was: "It would be simpler if I could tell the Tab instead the Tabs component about which one is active."
Accomplishing that is quite trivial, one can do that by setting a known fixed value to the Tabs component and assign that very value to whatever tab is supposed to be active.
This solution requires that the component hosting the tabs has access to the props such as location and match from react-router as follows
Firstly, we create a function that factory that removes bloated code from the render method. Here were are setting the fixed Tabs value to the Tab if the desired route matches, other wise I'm just throwing an arbitrary constant such as Infinity.
const mountTabValueFactory = (location, tabId) => (route) => !!matchPath(location.pathname, { path: route, exact: true }) ? tabId : Infinity;
After that, all you need is to plug the info to your render function.
render() {
const {location, match} = this.props;
const tabId = 'myTabId';
const getTabValue = mountTabValueFactory(location, tabId);
return (
<Tabs value={tabId}>
<Tab
value={getTabValue('/route/:id')}
label="tab1"
onClick={() => history.push(`${match.url}`)}/>
<Tab
value={getTabValue('/route/:id/sub-route')}
label="tab2"
onClick={() => history.push(`${match.url}/sub-route`)}
/>
</Tabs>
)
}
You can use react routers NavLink component
import { NavLink } from 'react-router-dom';
<NavLink
activeClassName="active"
to="/foo"
>Tab 1</NavLink>
When /foo is the route then the active class will be added to this link. NavLink also has an isActive prop that can be passed a function to further customize the functionality which determines whether or not the link is active.
https://reacttraining.com/react-router/web/api/NavLink

Resources