Accessing context in RouteMapper - javascript

Is there a way to get this.context in the RouteMapper?
I am able to call this.context.openMenu in Playground.
I have the following:
class App extends Component {
...
renderScene(route, navigator) {
return (
<Playground navigator={navigator} />
)
}
render() {
return (
<Navigator
ref="nav"
renderScene={this._renderScene.bind(this)} />
)
}
}
App.childContextTypes = {
openMenu: React.PropTypes.func
}
This is Playground:
class Playground extends Component {
...
render() {
return (
<Navigator
renderScene={this.renderScene.bind(this)}
navigator={this.props.navigator}
navigationBar={
<Navigator.NavigationBar style={Styles.navBar}
routeMapper={PlaygroundMapper} />
} />
)
}
}
Playground.contextTypes = {
openMenu: React.PropTypes.func
}
Then PlaygroundMapper:
export default {
LeftButton(route, navigator, index, navState) {
return (
<TouchableOpacity onPress={() => this.context.openMenu}>
<Text>Menu</Text>
</TouchableOpacity>
)
},
...
}

I've reached this conclusion.
Since I have access to this.context in Playground, I did this:
<Navigator.NavigationBar
style={Styles.navBar}
routeMapper={ArticlesRouteMapper(this.context)} />
} />
Then in the RouteMapper, I did this:
export default (context) => ({
LeftButton(route, navigator, index, navState) {
return <Hamburger _onPress={() => context.openMenu()} />
},
})
Works, but not sure if this is the proper way to do it.

Related

Why is my React Native class component returning the error: this.setState is not a function?

Here's the code for my class component:
export class RenderButtons extends React.Component {
constructor(props) {
super(props);
this.state = {
isCollapsed: false,
};
}
render() {
var buttonText = this.state.isCollapsed === true ? "Expand" : "Collapse";
// helper components
function _renderSquareButton({ item }) {
return (
<SquareButton
name={item.name}
onPress={() => this.props.pressHandler()}
/>
);
}
function _header(name) {
return (
<View style={views.nameAndPriceContainer}>
<Text style={text.headerTwo}>{name}</Text>
<Button title={buttonText} onPress={() => toggleCollapsed()} />
</View>
);
}
function toggleCollapsed() {
this.setState((currentState) => {
return { isCollapsed: !currentState.isCollapsed };
});
}
return (
<FlatList
listKey={this.props.name}
ListHeaderComponent={_header(this.props.name)}
data={this.props.data}
extraData={this.props.data}
renderItem={_renderSquareButton}
numColumns={4}
refreshing={true}
extraData={this.props.data}
keyExtractor={(item, index) => item.name + index}
ListEmptyComponent={null}
/>
);
}
}
The problem arises when I try to press the expand/collapse button and says "undefined is not a function (evaluating 'this.setState({ ... })" so I assume it's with the toggleCollapsed function.
Edit, thanks everyone the problem has been solved. Here's the working code for anyone interested:
export class RenderButtons extends React.Component {
constructor( props ){
super( props );
this.state = {
isCollapsed:false,
}
this._renderSquareButton = this._renderSquareButton.bind(this);
this._header = this._header.bind(this);
this.toggleCollapsed= this.toggleCollapsed.bind(this);
}
// helper components
_renderSquareButton({ item }){
return <SquareButton name={item.name} onPress={()=>this.props.pressHandler()} />
}
_header( name ){
var buttonText = this.state.isCollapsed === true ? 'Expand' : 'Collapse';
return(
<View style={ views.nameAndPriceContainer }>
<Text style={ text.headerTwo }>{ name }</Text>
<Button title= { buttonText } onPress={()=> this.toggleCollapsed() } />
</View>
)
}
toggleCollapsed(){
this.setState( currentState => {
return { isCollapsed: !currentState.isCollapsed };
});
}
render(){
switch( this.state.isCollapsed ){
case false:
return (
<FlatList
listKey={ this.props.name }
ListHeaderComponent={this._header( this.props.name )}
data={ this.props.data } extraData={ this.props.data }
renderItem={ this._renderSquareButton }
numColumns={ 4 }
refreshing={ true }
extraData={ this.props.data }
keyExtractor={( item, index )=> item.name + index }
ListEmptyComponent={ null }
/>
)
break;
case true:
return this._header( this.props.name )
}
}
}
It's just necessary to bind function toggleCollapsed in constructor like this:
constructor( props ){
super( props );
this.state = {
isCollapsed:false,
}
this.toggleCollapsed = this.toggleCollapsed.bind(this);
}
This should solve your problem.
Anyway I would suggest you to refactor your code in this way:
export class RenderButtons extends React.Component {
constructor( props ){
super( props );
this.state = {
isCollapsed:false,
}
this._renderSquareButton = this._renderSquareButton.bind(this);
this._header = this._header.bind(this);
this.toggleCollapsed= this.toggleCollapsed.bind(this);
}
// helper components
_renderSquareButton({ item }){
return <SquareButton name={item.name} onPress={()=>this.props.pressHandler()} />
}
_header( name ){
var buttonText = this.state.isCollapsed === true ? 'Expand' : 'Collapse';
return(
<View style={ views.nameAndPriceContainer }>
<Text style={ text.headerTwo }>{ name }</Text>
<Button title= { buttonText } onPress={()=> this.toggleCollapsed() } />
</View>
)
}
toggleCollapsed(){
this.setState( currentState => {
return { isCollapsed: !currentState.isCollapsed };
});
}
render(){
return (
<FlatList
listKey={ this.props.name }
ListHeaderComponent={this._header( this.props.name )}
data={ this.props.data } extraData={ this.props.data }
renderItem={ this._renderSquareButton }
numColumns={ 4 }
refreshing={ true }
extraData={ this.props.data }
keyExtractor={( item, index )=> item.name + index }
ListEmptyComponent={ null }
/>
)
}
}
Change your ToggleCollapsed into following
function toggleCollapsed(){
this.setState({
isCollapsed: !this.state.isCollapsed;
});
}
You need to update a few things:
Move toggleCollapsed to outside render.
Update onPress={this.toggleCollapsed}
Add this.toggleCollapsed = this.toggleCollapsed.bind(this) in constructor, after this.state
import React from "react";
const [collapsed, setCollapsed] = React.useState(false);
Maybe this can help you
also no need to compare a bool to === true.
let buttonText = this.state.isCollapsed ? 'Expand' : 'Collapse';

React-Native Router Flux with Netwoking and Sceenchooser

I have a problem.
My App work right now but not like how I want.
App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: true,
};
}
componentDidMount() {
fetch('https://reactnative.dev/movies.json')
.then((response) => response.json())
.then((json) => {
this.setState({ data: json.movies });
})
.catch((error) => console.error(error))
.finally(() => {
this.setState({ isLoading: false });
});
}
render() {
const { data, isLoading } = this.state;
const goToPageTwo = () => Actions.sw({text: 'Hello World!'});
return (
<View style={{ flex: 1, padding: 24 }}>
{isLoading ? <ActivityIndicator/> : (
<FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => { Actions.hp({text: item.id}) }}>
<Text>{item.title}</Text>
</TouchableOpacity>
)}
/>
)}
</View>
);
}
};
index.js / Route.js in my case sm.js
import app from './App';
import sw from './StarWars'
import bttf from './BacktotheFuture'
import hP from './handlePage';
import tM from './theMatrix';
import iN from './Inception';
const SceneManager = () => (
<Router>
<Scene>
<Scene key='home'
component={app}
title='BTour'
initial
/>
<Scene key='sw'
component={sw}
title='star wars'
/>
<Scene key='hp'
component={hP}
title='BTour'
/>
<Scene key='bttf'
component={bttf}
title='Back to the future'
/>
<Scene key='tM'
component={tM}
title='MAtrix'
/>
<Scene key='iN'
component={iN}
title='Inception'
/>
</Scene>
</Router>
)
export default SceneManager;
and my Handlepage.js
import StarWars from './StarWars';
import BTTF from './BacktotheFuture';
import TM from './theMatrix';
import IN from './Inception';
export default class handlePage extends Component {
renderElement() {
if(this.props.text ==1) {
return <StarWars/>
}
if (this.props.text ==2) {
return <BTTF/>
}
if (this.props.text ==3) {
return <TM/>
}
if(this.props.text == 4) {
return <IN/>
}
return null;
}
render(){
return(
this.renderElement()
)
}
};
NOW MY PROBLEM:
I want to use the Scene what I have defined in my Router class. For example when I press the first button in the Home Screen then "Star Wars" will be open but not the Component what I write in the route class.
Can I take the Component Scene from Route.js to the HandlePage.js in the If-Statemant OR can I put the If Statemant in the Router class.
Right now only the script in the IF-Statemant will open.
I don't think defining handlePage as class is necessary, you can just declare a function to navigate to your scene depending on item.id
Handlepage.js
import { Actions } from 'react-native-router-flux';
export const navigateByItemId = (id) => {
switch (id) {
case 1:
Actions.jump('sw');
break;
case 2:
Actions.jump('bttf');
break;
case 3:
Actions.jump('tM');
break;
default:
Actions.jump('iN');
}
};
And in App.js you can call this utility function to decide where to navigate
import { navigateByItemId } from './Handlepage';
...
<TouchableOpacity onPress={() => { navigateByItemId(item.id) }}>
<Text>{item.title}</Text>
</TouchableOpacity>
You can delete handlePage declaration in your router:
<Scene key='hp' //you can delete this code
component={hP}
title='BTour'
/>

How to pass action down as a prop in React Native and Redux?

in parent Browse.js
import { usersFetch, onToggleFollow } from '../actions';
class Browse extends Component {
render() {
return (
<ListView
....
renderRow={(user) =>
<UserItem user={user}
onPress={() => this.props.onToggleFollow()}
/>}
/>
);
}
}
export default connect(mapStateToProps, { usersFetch, onToggleFollow })(Browse);
in the child UserItem.js
class UserItem extends Component {
render() {
followButton = <Button title='Follow' onPress={() => {
this.props.onToggleFollow();
}}/>;
return (
<View style={styles.cardContainerStyle}>
<View>
{ followButton }
</View>
</View>
);
}
}
in the action creator file:
export const onToggleFollow = () => {
console.log('onToggleFollow method!');
return (dispatch) => {
dispatch({ type: ON_TOGGLE_SUCCESS });
};
};
I get an error says: "this.props.onToggleFollow is not a function, this.props.onToggleFollow is undefined" in UserItem.js, any helps thanks!
Your issue is on this line:
onPress={() => { this.props.onToggleFollow(); }}
in UserItem. You passed in the function as onPress prop so you should be calling this.props.onPress instead.

Use Navigator in React Native + Redux

I'm new to React Native.
I have a React Native app using the Redux framework. I'm using the Navigator component to handle navigation but this.props.navigator is undefined in component. How can I pass "this.props.navigator" to component?
I'm not able to find any good examples of how to do it correctly so I'm looking for some help and clarification.
index.anroid.js
...
import App from './app/app';
AppRegistry.registerComponent('RnDemo', () => App);
app.js
...
import App from './containers/app';
const logger = createLogger();
const createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore);
const store = createStoreWithMiddleware(rootReducer);
const rootApp = () => {
return (
<Provider store={store}>
<App />
</Provider>
)
}
export default rootApp;
containers/app.js
...
class App extends Component {
renderScene(route, navigator) {
let Component = route.component
return (
<Component navigator={navigator} route={route} />
)
}
configureScene(route) {
if (route.name && route.name === 'Home') {
return Navigator.SceneConfigs.FadeAndroid
} else {
return Navigator.SceneConfigs.FloatFromBottomAndroid
}
}
_selectPage(page) {
if(page === 'detail') {
this.props.navigator.push({
component: Detail,
name: 'Detail'
}) // **this.props.navigator.push is not a function**
}
}
render() {
const { navigator } = this.props;
console.log(navigator); // **>>>>>> Undefined**
return (
<View style={styles.container}>
<View style={styles.menu}>
<TouchableOpacity onPress={() => this._selectPage('detail')}><View style={styles.menuItem}><Text>Detail</Text></View></TouchableOpacity>
</View>
<View style={styles.content}>
<Navigator
ref='navigator'
style={styles.navigator}
configureScene={this.configureScene}
renderScene={this.renderScene}
initialRoute={{
component: Home,
name: 'Main'
}}
/>
</View>
</View>
)
}
}
renderScene(route, navigator) {
let Component = route.component
return (
// <Component navigator={navigator} route={route} />
<Component {...this.props} navigator={navigator} route={route} />
)
}
Try it

Pass state through Navigator

I'm using the React Native core Navigator component but having trouble figuring out how to pass data between components when pressing buttons in the Navigation Bar. Here is some example code of the setup that I have.
const NavigationBarRouteMapper = {
Title: (route, navigator) => {
let title;
switch (route.component.displayName) {
case 'FirstScreen':
title = 'First Screen';
break;
}
return (
<Text>
{title}
</Text>
)
},
LeftButton: (route, navigator) => {
let onButtonPress, buttonTitle;
switch (route.component.displayName) {
case 'SecondScreen':
buttonTitle = 'Close';
onButtonPress = () => navigator.pop();
break;
}
return (
<TouchableOpacity
onPress={onButtonPress}>
<Text>
{buttonTitle}
</Text>
</TouchableOpacity>
)
},
RightButton: (route, navigator) => {
let onButtonPress, buttonTitle;
switch (route.component.displayName) {
case 'SecondScreen':
buttonTitle = 'Save';
onButtonPress = () => {}; // #TODO Call onButtonPress in SecondScreen component
break;
}
return (
<TouchableOpacity
onPress={onButtonPress}>
<Text>
{buttonTitle}
</Text>
</TouchableOpacity>
);
}
};
const App = React.createClass({
renderScene(route, navigator) {
return <route.component navigator={navigator} {...route.props} />;
},
render() {
return (
<Navigator
style={styles.appContainer}
initialRoute={{component: SplashScreen}}
renderScene={this.renderScene}
navigationBar={
<Navigator.NavigationBar
routeMapper={NavigationBarRouteMapper}
/>
}
/>
);
}
});
const FirstScreen = React.createClass({
onButtonPress() {
this.props.navigator.push({component: SecondScreen});
},
render() {
return (
<View>
<TouchableHighlight onPress={this.onButtonPress}>
<Text>
Click Me
</Text>
</TouchableHighlight>
</View>
);
}
});
const SecondScreen = React.createClass({
getInitialState() {
return {
input: ''
}
},
onButtonPress() {
if (this.state.input.length) {
// Do something with this.state.input such as POST to remote API
this.props.navigator.pop();
}
},
render() {
return (
<View style={styles.container}>
<TextInput
onChangeText={(input) => this.setState({input})}
value={this.state.input}
/>
</View>
);
}
});
You can see from the comments that I have a value stored in the state of SecondScreen that I want to do something with when someone hits the Save button. Any ideas?
In your renderScene function, while returning 'route.component' you can pass props and read them in the component.

Categories