react native pass props to another component - react-native

I've been struggling passing a value from one component to another. It's a continuation of the issue from a previous question which was partially resolved: react-native tab navigator search box
I'm using tab navigator and here's my app setup:
index.js (renders tab setup)
  router.js
     searchHeader.js
     tab1.js
     tab2.js
     etc
In index.js when a tab is changed I'm getting the name of the tab. I want to pass that to searchHeader.js to update the placeholder text.
As searchHeader.js isn't imported into index.js and not a direct child how do I pass it that value?
index.js
import React, { Component } from 'react';
import { Root, Tabs } from './config/router';
import { Alert,View } from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
}
}
_getCurrentRouteName(navState) {
if (navState.hasOwnProperty('index')) {
this._getCurrentRouteName(navState.routes[navState.index])
} else {
if (navState.routeName==='One') {
this.setState({searchText:'Search One'})
}
if (navState.routeName==='Two') {
this.setState({searchText:'Search Two'})
}
if (navState.routeName==='Three') {
this.setState({searchText:'Search Three'})
}
if (navState.routeName==='Four') {
this.setState({searchText:'Search Four'})
}
}
}
render() {
return (
<Root onNavigationStateChange={(prevState, newState) => {
this._getCurrentRouteName(newState)
}} />
)
}
}
export default App;
router.js
...
export const Root = StackNavigator({
Tabs: {
screen: Tabs,
navigationOptions: {
header: <SearchHeader data={'Test'} />
}
},
}, {
mode: 'modal',
});
searchHeader.js
import React, { Component } from 'react';
import { View,Text,Dimensions,Alert } from 'react-native';
import { SearchBar } from 'react-native-elements';
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
placeholder: "Search One"
}
}
render() {
return (
<SearchBar
noIcon
containerStyle={{backgroundColor:'#fff'}}
inputStyle={{backgroundColor:'#e3e3e3',}}
lightTheme = {true}
round = {true}
placeholder={data}
placeholderTextColor = '#000'
/>
);
}
};
export default SearchHeader;

You could perhaps pass it as a navigation prop using the setParams method.
An alternative, depending on the scope of your app, would be to look at a state library such as Redux or MobX - but if it's a small app, it's overkill

For that you can use Redux, you will have a store where you can put shared properties and values,
Then your components can connect to that store and bind its props with the chosen reducer(s) and dispatch actions..

this structure may work:
class Home extends Component {
func(val) {
this.setState({value: val});
}
render() {
return (
<View>
<Two func={(val) => this.func(val)} />
</View>
)
}
}
class Two extends Component {
render() {
return (
<View>
<Button title="set" onPress={() => this.props.func('data')} />
</View>
)
}
}

Related

useTheme equivalent for class component

I would like to use the current theme in my class component.
According to the latest (RN 5.x) documentation, there is a useTheme() hook for that purposes. However, it doesn't mention any class equivalent.
I found ThemeContext.Consumer in RN 4.x but it is not available in 5.x anymore.
Is it possible to achieve the effect of useTheme() in some other way for a class component?
This is not so elegant, but it will do the job for you.
Here is my method to access the theme inside a class component:
import React from 'react'
import { SafeAreaView, Text } from 'react-native'
import { useTheme } from '#react-navigation/native'
export default class Home extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: undefined
}
}
setTheme = theme => {
this.setState({theme})
}
render () {
console.log('theme', this.state.theme)
return (
<SafeAreaView>
<SetTheme setTheme={this.setTheme} />
<Text>Hello world</Text>
</SafeAreaView>
)
}
}
const SetTheme = ({ setTheme }) => {
const theme = useTheme()
React.useEffect(() => {
setTheme(theme)
return () => null
},[])
return null
}

Undefined Unstated Container in a React Native Component using React Navigation

My problem is That I want to access a Container in a component but it seems to be undefined.
undefined alert image
I am using Unstated and as you can see this is my code in the container file (login-container.js):
import { Container } from 'unstated'
class LoginContainer extends Container {
constructor(props){
super(props)
this.state = {
stepNumber: 0,
}
}
}
export default new LoginContainer()
And this is app.js:
import React, { Component } from 'react'
import { createStackNavigator, createSwitchNavigator } from 'react-navigation'
import { Provider } from 'unstated'
import LoginContainer from './containers/login-container'
import Home from './screens/home'
import Splash from './screens/splash'
import Login from './screens/login'
import Intro from './screens/intro'
export default class App extends Component {
render() {
return (
<Provider inject={[LoginContainer]}>
<AuthStack/>
</Provider>
)
}
}
const SplashStack = createStackNavigator(...)
const AppStack = createStackNavigator(...)
const AuthStack = createStackNavigator(
{
Intro: { screen: Intro},
Login: { screen: Login}
},
{
headerMode: "none",
initialRouteName: "Intro"
}
)
const SwitchNavigator = createSwitchNavigator(...)
And this would be login.js:
import React, { Component } from 'react'
import { Text, View } from 'react-native'
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
// const { state: {stepNumber} } = this.props.loginContainer
alert(this.props.LoginContainer)
return (
<View>
<Text> someText </Text>
</View>
)
}
}
I previously tried to use Subscribe component to inject the container to my app but I got the same thing I am getting here.
Using
- react-native 0.58.6
- react-navigation 2.13.0 (due to some bugs in v3)
- unstated 2.1.1
What's really great about Unstated is how simple it is to implement.
Just wrap your render component in Unstated's <Subscribe to></Subscribe> tags and you're good to go. Whenever you setState() in the Container, all Components that Subscribe to it get re-rendered with the Container's updated state property values available to them.
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
return (
<Subscribe to={[LoginContainer, AnotherContainer]}>
{(container, another) => (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
})
</Subscribe>
);
}
}
UPDATE: Or do it in this HOC way. After creating this:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import DefaultStore from "../store/DefaultStore";
const withUnstated = (
WrappedComponent,
Stores = [DefaultStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Then wrap your component like so:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
import AnotherContainer from './containers/another-container';
class Login extends Component {
constructor(props){
super(props)
}
render() {
const {LoginContainer: container} = this.props;
return (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
);
}
}
export default withUnstated(Login, [LoginContainer, AnotherContainer])

Accessing navigation props - react-navigation

I just have a question regarding react-navigation.
I understand that the navigation props becomes accessible when a screen is rendered from the stacknavigator.
But how do you access the navigation props if the screen is not rendered by the stacknavigator?
Like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
Text,
View,
} from 'react-native';
import Swiper from 'react-native-swiper';
import Menu from './Menu';
class HomeSwiper extends Component {
static propTypes = {
navigation: PropTypes.object,
};
render() {
return (
<Swiper showsButtons>
<View>
<Menu
navigationProps={this.props.navigation}
/>
</View>
<View>
<Text>Hello Swiper</Text>
</View>
</Swiper>
);
}
}
export default HomeSwiper;
Wherein Menu is:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { AsyncStorage, TouchableOpacity, Text, BackHandler, Alert } from 'react-native';
import { StandardContainerIOS } from '../components/Container';
import { StandardButton } from '../components/Buttons/';
class Menu extends Component {
static propTypes = {
navigation: PropTypes.object,
};
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
const headerLeft = (
<TouchableOpacity
onPress={params.handleRedirect ? params.handleRedirect : () => null}
>
<Text>Logout</Text>
</TouchableOpacity>
);
return {
headerLeft,
};
};
constructor(props) {
super(props);
this.state = {
email: '',
petName: [],
numberOfPets: '',
};
}
getInitialState() {
return {
petName: ['No Name'],
};
}
async componentWillMount() {
try {
const value = await AsyncStorage.getItem('email');
if (value !== null) {
// We have data!!
}
} catch (error) {
// Error retrieving data
}
this.props.navigation.setParams({
handleRedirect: this.handlePressLogout,
});
this.getEmail();
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
render() {
return (
<StandardContainerIOS>
<StandardButton backgroundColor="#6D4C41" title="View Pet" onPress={this.handleIndexView} />
<StandardButton backgroundColor="#6D4C41" title="Register Pet" onPress={this.handlePressRegisterPets} />
<StandardButton backgroundColor="#6D4C41" title="Logout" onPress={this.handlePressLogout} />
</StandardContainerIOS>
);
}
}
export default Menu;
I've removed the other function definition to cut the post a little shorter. The Menu screen was working fine when it was rendered from the stacknavigator. Im trying to incorporate swiping in my app.
Any suggestions?

React Native Router Flux: passing params between scenes

I have a list of items (jobs) and when an item (job) is being selected, a new scene is being opened. I want the ID of the selected item to be passed from the scene with the list to the other scene with the details about the selected item (job) without using Redux.
Router
import React from 'react';
import { Scene, Router } from 'react-native-router-flux';
import JobsList from './components/JobsList';
import Job from './components/Job';
const RouterComponent = () => {
return (
<Router>
<Scene key="jobs" component={JobsList} initial />
<Scene key="Job" component={Job} title="Test" />
</Router>
);
};
export default RouterComponent;
Jobs list
import React, { Component } from 'react';
export default class JobsList extends Component {
render() {
return (
<TouchableOpacity onPress={() => { Actions.Job({ jobId: jobId }) }}>
...
</TouchableOpacity>
);
}
}
Job
import React, { Component } from 'react';
export default class Job extends Component {
constructor() {
super();
this.state = {
job: {}
};
axios.get(
// PROBLEM: this.props.jobId is empty
`http://api.tidyme.dev:5000/${this.props.jobId}.json`,
{
headers: { Authorization: 'Token token=123' }
}
).then(response => this.setState({
job: response.data
}));
}
render() {
return (
<Text>{this.state.job.customer.firstName}</Text>
);
}
}
You should call super(props) if you want to access this.props inside the constructor.
constructor(props) {
super(props);
console.log(this.props);
}
The best practice is defining Components as pure functions:
const Job = ({ job, JobId}) => {
return (
<Text>{job.customer.firstName}</Text>
);
}
otherFunctions() {
...
}

test with enzyme a react component with context: return an empty object

I'm trying to execute a dummy test with enzyme over a component. the test is about to check the context. even though I'm writing the same code as enzyme's documentation the context is always empty.
import React from 'react';
import { shallow } from 'enzyme';
import Overlay from '../../../../app/components/Overlay/Overlay';
describe('<Overlay />', () => {
it.only('return a context', () => {
const wrapper = shallow(<Overlay />, { context: { foo: 10 } });
console.log(wrapper.context());
// expect(wrapper.context().foo).to.equal(10);
});
})
the test's output is:
<Overlay />
{}
✓ return a context
where am I wrong?
Since the details of Overlay component is not given, I assume the context is not used in it (pls check childContextTypes and getChildContext are defined properly)
For example, refer the explanation for contexts in react documents
I have taken the same example to enable the test,
import React from 'react';
export default class Button extends React.Component {
render() {
return (
<button style={{ background: this.context.color }}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string,
};
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
getChildContext() {
return { color: 'purple' };
}
render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}
MessageList.childContextTypes = {
color: React.PropTypes.string,
};
I've created the test for Button component as below,
import React from 'react';
import { shallow } from 'enzyme';
import { expect } from 'chai';
import Button from '../../src/components/SampleComp';
describe.only('<Button />', () => {
it('assert for context', () => {
const wrapper = shallow(
<Button />,
{ context: { color: 'red' } }
);
expect(wrapper.context().color).to.equal('red');
expect(wrapper.context('color')).to.equal('red');
});
});
<Button />
✓ assert for context
1 passing (214ms)
This will assert it correctly.