Passing property through router - reactjs

This is for a react-native app
I'm trying to pass a property to a component through a router like so:
I have a button with an onPress like so in separate component:
_onPress() {
this.props.navigator.push(Router.getDealList(this.state.categoryName, this.state.categoryId));
}
With the router defined as:
const Router = {
getDealList(categoryName, categoryId) {
return {
renderScene() {
let DealList = require('./components/DealList').default;
debugger;
return <DealList categoryId={categoryId} />;
},
getTitle() {
return categoryName;
},
};
},
};
However, when I try to access the property in the DealList component, it seems to be undefined. I am attempting to do this like so:
_onFetch(page = 1, callback) {
const categoryId = this.props.categoryId;
...
}
I've attempted to debug this and it seems to have the variables up until the point where I try to call _onFetch

I have used a different approach in my React native app. Please be patient, this might give you a clue or you can adopt the same way altogether.
In main.js I have this structure which takes care of everything smoothly.
Top level views:
var Splash = require('./views/splash');
var Signin = require('./views/signin');
var Signup = require('./views/signup');
var Home = require('./views/home');
Routes:
var ROUTES = {
splash: Splash,
signin: Signin,
signup: Signup,
home: Home,
};
And navigator looks something like this. Observe "data" being passed as a prop to respective Component in renderScene()
render: function(){
return(
//Render first view through initialRoute
<Navigator
style={styles.container}
initialRoute={{ name: 'home', index: 0 }}
renderScene={ this.renderScene }
configureScene={ () => { return Navigator.SceneConfigs.PushFromRight; }}
>
</Navigator>
)
},
renderScene: function(route, navigator){
//ROUTES['signin'] => SignIn
var Component = ROUTES[route.name];
return (
<Component
route={route}
navigator={navigator}
data={route.data}
/>
);
}
When I want to push a new View in my app I simply do this-
onPressNewBooking: function(){
this.props.navigator.push({ name: 'newbooking', data: this.state.rawData })
}

Related

How to call a function from react-navigation header?

I am actually using react navigation https://reactnavigation.org/, I have a Component with a method:
class Sing extends Component {
singASong = () => {
console.log('hello i am singing');
}
}
this component will be rendered with react-navigation and a custom header, the question is: how to call the method singASong from the custom header?:
const routeStackNavigation = {
SingingMode: {
screen: Sing,
navigationOptions: ({ navigation }) => ({
header: (
<CustomNav onPressAction={() =>{
// how to call the method singASong from here ?
}
}
/>
),
}),
},
};
UPDATE
I set and test the value with this:
this.props.navigation.setParams({'onPress': 'akumen' });
setTimeout(() => {
console.log(this.props.navigation.getParam('onPress')); // akumen
}, 2000);
And i tests values with:
onPress={() =>{
console.log('NAVIGATION',navigation);
console.log('ON PRESS',navigation.getParam('onPress'));
console.log('PARAMS',navigation.state.params);
return navigation.getParam('onPress');
}
}
but get undefined
FOR STACK NAVIGATION
Below I share pic with my real world example, it was a stack navigation and I did not notice before, but param it just set under the right stack, so take this as motivation to explore:
You can use navigation params:
In componentDidMount
this.props.navigation.setParams({onPressAction: ()=>this.singASong()})
In navigationOptions
<CustomNav onPressAction={navigation.getParam("onPressAction") }/>

How do I get state or props into TabNavigator

I'd like to get user image into the tabBarIcon just like Instagram does it. I can't figure out the way to do it.
I tried getting the state but on the init of the app the state is empty.
I've tried like
const store = store.getState().user
but it's undefined on app init
I have MainTabNavigator.js
ProfileStack.navigationOptions = {
tabBarLabel: () => {
return null
},
tabBarIcon: ({focused}) => (
<Image source={{uri: ???}}/>
)
}
const TabNavigator = createBottomTabNavigator({
HomeStack,
SearchStack,
DashboardStack,
ProfileStack,
});
TabNavigator.path = '';
export default TabNavigator;
I can't get the props or state since this isn't a class
The solution was quite simple.... I don't know why I didn't think of it earlier.
The trick was to create a component that would get the state from redux and when the state changes after the init, the state would finally have the Object and there the image path
ProfileStack.navigationOptions = {
tabBarLabel: () => {
return null
},
tabBarIcon: ({focused}) => (
<BottomTabImage focused={focused} />
)
}
See i changed it to BottomTabImage which is just a component
And this is the component file
BottomTabImage.js
import {connect} from 'react-redux';
class BottomTabImage extends Component {
render() {
const uri = this.props.auth.image !== undefined ?`http://localhost/storage/creator_images/${this.props.auth.image)}`: '';
return <Image style={styles.profileImage} source={{uri}} />
}
}
const styles = StyleSheet.create({
profileImage: {
...
}
});
function mapStateToProps(state) {
return {
auth: state.user.auth
}
}
export default connect(mapStateToProps, {})(BottomTabImage)

How to add if condition in "initialRouteName" drawer React Native

i'm new in react native. i want to add if condition in initialRouteName like code below. when "notification" variable is null move to "MemberProfile" page. but if "notification" variable is not null move to "ReviewMember" page. i try it code but still move to "MemberProfile" page. any solution?.
this is my code
var notification = null;
class DrawerMember extends Component {
constructor(props) {
super(props);
this.state = {
notifData: null
};
this.callCheck();
}
async callCheck() {
await AsyncStorage.getItem("#tryCode:notification", (err, result) => {
if (result != null) {
this.setState({
notifData: "testing data"
});
}
});
}
render() {
notification = this.state.notifData;
return <Root />;
}
}
const Root = createDrawerNavigator(
{
MemberProfile: {
screen: MemberProfileScreen
},
ReviewMember: {
screen: ReviewScreen
}
},
{
drawerPosition: "right",
initialRouteName: notification == null ? "MemberProfile" : "ReviewMember",
contentComponent: props => <SideBar {...props} />,
}
);
export default DrawerMember;
I think Root is created before the async function returns so notification is always null.
A possible way to solve this problem would be to use a SwitchNavigator as the first screen in your drawer. This navigator would be responsible for loading the notification and redirecting to the right screen.
Something along the lines of:
import React from 'react';
import { View, AsyncStorage, ActivityIndicator, StatusBar } from 'react-native';
export default class DummySwitch extends React.Component {
async componentDidMount() {
this.listener = this.props.navigation.addListener('willFocus', async () => {
const notification = await AsyncStorage.getItem('#tryCode:notification');
if (notification === null) {
this.props.navigation.navigate('MemberProfile');
}
else {
this.props.navigation.navigate('ReviewMember');
}
});
}
render() {
return (
<View>
<ActivityIndicator />
<StatusBar barStyle='default' />
</View>
);
}
}
As you can see, the switch screen just displays a loading button while accessing the async storage and deciding which route to take.
Then you define the drawer as usual but you add the switch screen as the initial route. You can also hide the label if you want by defining your own drawerLabel:
export default createDrawerNavigator({
Switch: {
screen: Switch,
navigationOptions: () => ({
drawerLabel: () => null,
}),
},
MemberProfile: {
screen: MemberProfileScreen,
},
ReviewMember: {
screen: ReviewScreen,
},
}, { initialRouteName: 'Switch' });
This is it, the drawer now selects the route based on your async storage.

How do you navigate to another component that does not receive the props of react navigation?

I'm working with React Native and React Navigation.
I have a component called App.js in which I declare the Drawer Navigation of React-Navigation.
In this I have an option to log out but I can not navigate to another component after removing the AsyncStorage
Does anyone know how to achieve it?
Thank you.
This is my code:
App.js
import { createDrawerNavigator, DrawerItems, NavigationActions } from 'react-navigation';
const customDrawerComponent = (props) => (
<SafeAreaView style={{ flex: 1 }}>
<ScrollView>
<DrawerItems
{...props}
/>
<TouchableOpacity style={styles.button} onPress={this.logOut} >
<Text> Logout </Text>
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
logOut = () => {
// NOT WORKS
// this.props.navigation.navigate('Login')
//NOT WORKS:
this.myAction();
}
myAction = () => {
const nav = NavigationActions.navigate({
routeName: 'App',
});
return nav;
};
const AppDrawNavigator = createDrawerNavigator(
{
MainComponent: { screen: MainComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
Login: { screen: LoginComponent,
navigationOptions: ({navigation}) => ({
drawerLockMode: 'locked-closed'
}) },
User: { screen: UsersComponent }
},
{
contentComponent: customDrawerComponent,
}
);
make this as a class like
export default class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
From your question I understand that either you want to :-
navigate from outside the components
navigate from components which do not have navigation prop.
For this I have tried 2 solutions and both work extremely fine though I based towards the second one.
First Solution
Use withNavigation from react-navigation package. If your components are deeply nested they wont have navigation prop unless u specify them manually or put them in context ;passing navigation prop becomes a real pain. So instead use withNavigation and your component would have navigation prop.
import {withNavigation} from "react-navigation";
const Component = ({navigation}) => {
const onPress = () => {
navigation.navigate(//ROUTE_NAME//)
}
return (
<TouchableOpacity onPress={onPress}>
<Text>Navigate</Text>
</TouchableOpacity>
)
}
export default withNavigation(Component);
Second Solution
Create a helper script and use that.
"use strict";
import React from "react";
import {NavigationActions} from "react-navigation";
let _container; // eslint-disable-line
export const navigation = {
mapProps: (SomeComponent) => {
return class extends React.Component {
static navigationOptions = SomeComponent.navigationOptions; // better use hoist-non-react-statics
render () {
const {navigation: {state: {params}}} = this.props;
return <SomeComponent {...params} {...this.props} />;
}
}
},
setContainer: (container) => {
_container = container;
},
reset: (routeName, params) => {
_container.dispatch(
NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
]
})
);
},
goBack: () => {
_container.dispatch(NavigationActions.back());
},
navigate: (routeName, params) => {
_container.dispatch(
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName,
params
})
);
},
navigateDeep: (actions) => {
_container.dispatch(
actions.reduceRight(
(prevAction, action) =>
NavigationActions.navigate({
type: "Navigation/NAVIGATE",
routeName: action.routeName,
params: action.params,
action: prevAction
}),
undefined
)
);
},
getCurrentRoute: () => {
if (!_container || !_container.state.nav) {
return null;
}
return _container.state.nav.routes[_container.state.nav.index] || null;
}
};
In your parent component when you mount the navigation call following:-
"use strict";
import React from "react";
import App from "./routes";
import {navigation} from "utils";
class Setup extends React.Component {
render () {
return (
<App
ref={navigatorRef => {
navigation.setContainer(navigatorRef);
}}
/>
);
}
}
export default App;
Now, in your components you can directly use helpers from this script itself and navigation would be accessibly globally now.
import {navigate} from "utils/navigation";
For more details you can this thread
Your logout function is declared outside of the Navigator. This means you don't have access to the navigation prop there. However, your customDrawerComponent is a screen of your Navigator and it should have access to it.
So you can try something like this (props here are the props passed to the customDrawerComponent):
onPress={()=> {props.navigation.navigate("Login")}}
Plus your App.js seems kind of strange since you're not exporting any component. Have you pasted the whole code of App.js or just parts of it?

Bypass fetch call in React component

I am attempting to write a test for a react component that sends out a fetch GET request after mounting. The test just needs to check if the component has rendered and when fetch runs I get this: ReferenceError: fetch is not defined. I have searched around and cant seem to find anything that will fix my problem. I am using jest and Test Utils for testing the components.
My component code:
export class Home extends Component {
constructor(props) {
...
}
componentDidMount() {
fetch('/some/path', {
headers: {
'Key1': 'Data1',
'Key2': Data2
}
}).then(response => {
if (response.status == 200) {
response.json().then((data) => {
this.context.store.dispatch(setAssets(data))
}
);
} else {
return (
<Snackbar
open={true}
message={"ERROR: " + str(response.status)}
autoHideDuration={5000}
/>
);
}
}).catch(e => {});
...
);
}
componentWillUnmount() {
...
}
logout(e) {
...
}
render() {
return (
<div>
<AppBar
title="Title"
iconElementLeft={
<IconButton>
<NavigationClose />
</IconButton>
}
iconElementRight={
<IconMenu
iconButtonElement={
<IconButton>
<MoreVertIcon />
</IconButton>
}
targetOrigin={{
horizontal: 'right',
vertical: 'top'
}}
anchorOrigin={{
horizontal: 'right',
vertical: 'top'
}}
>
<MenuItem>
Help
</MenuItem>
</IconMenu>
}
/>
{
this.context.store.getState().assets.map((asset, i) => {
return (
<Card
title={asset.title}
key={i+1}
/>
);
})
}
</div>
);
}
}
Home.contextTypes = {
store: React.PropTypes.object
}
export default Home;
My Test Code:
var home
describe('Home', () => {
beforeEach(() => {
let store = createStore(assets);
let a = store.dispatch({
type: Asset,
assets: [{
'id': 1,
'title': 'TITLE'
}],
});
store.getState().assets = a.assets
home = TestUtils.renderIntoDocument(
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Provider store={store}>
<Home />
</Provider>
</MuiThemeProvider>
);
});
it('renders the main page, including cards and appbar', () => {}
It errors when trying to render Home into document.
I have tried fetch-mock but this only allows for mock calls for API testing, which I'm not trying to do and doesn't mock the fetch calls in my component.
Mocking Home wont work because its the component I am trying to test. Unless there's a way to mock the componentDidMount() function that I have missed.
I just need a workaround for the fetch call. Any ideas??
EDIT: I'm using React's JSX for the component and JS for the test
Try https://github.com/jhnns/rewire:
rewire adds a special setter and getter to modules so you can modify
their behaviour for better unit testing
var fetchMock = { ... }
var rewire = require("rewire");
var myComponent = rewire("./myComponent.js");
myComponent.__set__("fetch", fetchMock);
Unfortunately I'm using babel, which is listed as a limitation for rewire, but I tried it anyways...
I Added:
...
store.getState().assets = a.assets
var fetchMock = function() {return '', 200}
var rewire = require("rewire");
var HomeComponent = rewire('../Home.jsx');
HomeComponent.__set__("fetch", fetchMock);
home = TestUtils.renderIntoDocument(
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Provider store={store}>
<Home />
...
And received the error:
Error: Cannot find module '../Home.jsx'
at Function.Module._resolveFilename (module.js:440:15)
at internalRewire (node_modules/rewire/lib/rewire.js:23:25)
at rewire (node_modules/rewire/lib/index.js:11:12)
at Object.eval (Some/Path/Home-test.js:47:21)
I'm assuming this is because babel:
rename[s] variables in order to emulate certain language features. Rewire will not work in these cases
(Pulled from the link chardy provided me)
However the path isn't a variable, so I wonder if babel is really renaming it, and the path is 100% correct for my component's location. I don't think it's because I'm using JSX because it just cant find the component, its not a compatibility issue... Rewire still may not work even if it finds the file though, unfortunately, but I'd like to give it a shot all the same.
I found an answer that worked for me, and it was very simple without including any other dependencies. It was a simple as storing the main function into a variable and overwriting it, then restoring the proper function after the testcase
SOLUTION:
var home
describe('Home', () => {
const fetch = global.fetch
beforeEach(() => {
let store = createStore(assets);
let a = store.dispatch({
type: Asset,
assets: [{
'id': 1,
'title': 'TITLE'
}],
});
store.getState().assets = a.assets
global.fetch = () => {return Promise.resolve('', 200)}
home = TestUtils.renderIntoDocument(
<MuiThemeProvider muiTheme={getMuiTheme()}>
<Provider store={store}>
<Home />
</Provider>
</MuiThemeProvider>
);
});
it('renders the main page, including cards and appbar', () => {
...
});
afterEach(() => {
global.fetch = fetch;
});
});
Using the global context to store components can be brittle and is probably not a good idea for any sizable project.
Instead, you can use the dependency injection (DI) pattern which is a more formalized method for switching out different component dependencies (in this case, fetch) based on your runtime configuration.
https://en.wikipedia.org/wiki/Dependency_injection
One tidy way to employ DI is by using an Inversion of Control (IoC) container, such as:
https://github.com/jaredhanson/electrolyte
What I did is I moved the fetch calls out of the components into a repository class then called that from within the component. That way the components are independent of the data source and I could just switch out the repository for a dummy repository in order to test or change the implementation from doing fetch to getting the data from localStorage, sessionStorage, IndexedDB, the file system, etc.
class Repository {
constructor() {
this.url = 'https://api.example.com/api/';
}
getUsers() {
return fetch(this.url + 'users/').then(this._handleResponse);
}
_handleResponse(response) {
const contentType = response.headers.get('Content-Type');
const isJSON = (contentType && contentType.includes('application/json')) || false;
if (response.ok && isJSON) {
return response.text();
}
if (response.status === 400 && isJSON) {
return response.text().then(x => Promise.reject(new ModelStateError(response.status, x)));
}
return Promise.reject(new Error(response.status));
}
}
class ModelStateError extends Error {
constructor(message, data) {
super(message);
this.name = 'ModelStateError';
this.data = data;
}
data() { return this.data; }
}
Usage:
const repository = new Repository();
repository.getUsers().then(
x => console.log('success', x),
x => console.error('fail', x)
);
Example:
export class Welcome extends React.Component {
componentDidMount() {
const repository = new Repository();
repository.getUsers().then(
x => console.log('success', x),
x => console.error('fail', x)
);
}
render() {
return <h1>Hello!</h1>;
}
}

Resources