I'm trying to integrate opening app intro sliders to my app, but failing to connect the points to get from the intro to my main app body.
The library i'm using says use 'react-native-app-intro-slider' as such, where an _onDone() function is called to finish the intro and show the 'real' app:
export default class App extends React.Component {
_onDone = () => {
// User finished the introduction. Show "real" app
}
render() {
return (
<AppIntroSlider
slides={slides}
onDone={this._onDone}
/>
);
}
}
With the main body of my app (it works when I run it without the intro-slider addition) being this:
render() {
const contents = collection.map((item, index) => {
return (
<Card key={index}>
[[there's stuff here omitted]]
</Card>
)
});
return (
<View style={{flex:1}}>
<CardStack>
{contents}
</CardStack>
</View>
);
How can I get this to render after the introslider? Do I put all that inside the _onDone() function? (this doesn't work). Or is there a way write _onDone so that after the component, the regular part of the main app would proceed to go as before?
export default class App extends React.Component {
_onDone = () => {
// Something that lets me pass onto the main part of my app
}
render() {
return (
<AppIntroSlider
slides={slides}
onDone={this._onDone}
/>
// The main body of the app that I want React to get to after the <AppIntroSlider> component.
const contents = collection.map((item, index) => {
return (
<Card key={index}>
[[there's stuff here omitted]]
</Card>
)
});
return (
<View style={{flex:1}}>
<CardStack>
{contents}
</CardStack>
</View>
);
);
}
}
If you're not using a navigation library, I would suggest to simply use state:
constructor(props) {
super(props);
this.state = {
showRealApp: false
}
}
_onDone = () => {
this.setState({ showRealApp: true });
}
render() {
if (this.state.showRealApp) {
const contents = collection.map((item, index) => (
<Card key={index}>
{...}
</Card>
));
return (
<View style={{flex:1}}>
<CardStack>
{contents}
</CardStack>
</View>
);
} else {
return <AppIntroSlider slides={slides} onDone={this._onDone}/>;
}
}
You can also consult issue #18 on the react-native-app-intro-slider repo.
Related
I managed to fetch data and show to UI with this code:
export default class BoxGarage extends Component {
render() {
let garage = this.props.garage;
garage.name = garage.name.replace('strtoreplace', 'My Garage');
let cars = garage.cars.length ?
garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
)
}) : (
<View style={styles.boxEmpty}>
<Text style={styles.textEmpty}>(No Cars)</Text>
</View>
);
return (
<View style={styles.boxGarage}>
<Text>{ garage.name }</Text>
{ cars }
</View>
)
}
}
Then I tried to change with a function, but no cars shown. What is missing?
export default class BoxGarage extends Component {
render() {
let garage = this.props.garage;
garage.name = garage.name.replace('strtoreplace', 'My Garage');
cars = function(garage) {
if (garage.cars.length) {
garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
);
});
}
else {
return (
<View style={styles.boxEmpty}>
<Text style={styles.textEmpty}>(No Cars)</Text>
</View>
);
}
}
return (
<View style={styles.boxGarage}>
<Text>{ garage.name }</Text>
{ cars(this.props.garage) }
</View>
)
}
}
And I think I should refactor for best practice either using constructor or just move the function outside render, but I don't know what it is. Please advice.
The reason your second code doesn't work is that you're not returning anything from your function if garage.cars.length > 0.
if (garage.cars.length) {
// Added a return statement on the next line
return garage.cars.map((val, key) => {
return (
<Car key={key} car={val} />
);
});
}
That said, i think your first version of the code was much cleaner. If a piece of code got complicated enough that i was tempted to make an inline function to do calculations, i'd either pull that out to another class method, or to another component. In your case though, just doing a ternary or an if/else will be much better.
I am using Context API to use themes in my React Native project. To consume the Theme Context, I made a Higher Order Component and passed them into the component as props.
This worked fine for the most of the app. But when I started using refs, it started crashing, because as per the documentation, the refs will not be forwarded automatically, and we need to use the React.forwardRef API. But something when wrong with my implementation and I am not able to resolve it.
Here's the code I have been working on:
// Higher order component, to wrap my component with theme (withTheme.js)
export const withTheme = (Component, areRefsUsed = false) => {
// Logic to check if refs are being used
if (areRefsUsed) {
const ThemedComponent = (props, ref) => {
return (
<ThemeContext.Consumer>
{theme => (<Component {...props} theme={theme} ref={ref} />)}
</ThemeContext.Consumer>
)
};
return React.forwardRef(ThemedComponent);
}
return (props) => {
return (
<ThemeContext.Consumer>
{theme => <Component {...props} theme={theme} />}
</ThemeContext.Consumer>
)
}
};
// My component where I am using refs (ExampleComponent.js)
class ExampleComponent extends Component {
constructor(props) {
super(props);
this.refForSomeComponent = React.createRef();
}
render() {
const { theme } = this.props;
return (
<Fragment>
<Text style={{color: theme.primaryColor}}>It is working</Text>
<TextInput ref={this.refForSomeComponent} value={'Test'} />
</Fragment>
)
}
}
export default withTheme(ExampleComponent, true);
When I try to run the app, this error is being thrown:
The component for route 'ExampleComponent' must be a React component.
I have 2 screens, my Home Screen
class Home extends Component {
constructor(props) {
super(props)
this.state = {
myDebts: 745.8455656,
debts: 1745.54555
}
}
addFriendsHandler = () => {
Alert.alert('You tapped the button!')
}
render () {
return (
<View style={{flex: 1}}>
<Header
text={"Splitwise"} />
<Debts
myDebts={this.state.myDebts}
debts={this.state.debts}/>
<Buttons text={"+ ADD FRIENDS ON SPLITWISE"}
clicked={() => this.props.navigation.navigate("AddFriend")}/>
</View>
)
}
}
export default Home
and my second Screen
class AddFriendPage extends Component{
state = {
name: ''
}
addFriendHandler = () => {
this.props.navigation.navigate("MainPage")
}
render() {
return (
<View>
<Header text={"Add a friend"}/>
<Sae
label={'Your friends name'}
labelStyle={{ color: '#47AE4f' }}
iconClass={FontAwesomeIcon}
iconName={'pencil'}
iconColor={"#47AE4f"}
inputStyle={{ color: '#000' }}
onBlur={(e) => this.setState({name: e.nativeEvent.text})}
/>
<Buttons text={"+ ADD FRIEND"}
disable={this.state.name === ''}
clicked={this.addFriendHandler}/>
</View>
)
}
}
and my Navigator
export default class App extends React.Component {
render() {
return (
<AppStackNavigator />
);
}
}
const AppStackNavigator = createStackNavigator({
MainPage: Home,
AddFriend: AddFriendScreen
})
I want to send a function to the AddFriendPage screen from Home screen, and inside that function i want to get value from input and return the name back into Home screen, but unfortunately i have no idea how to share data between 2 screens
https://reactnavigation.org/docs/en/params.html#docsNav
You want to pass params during navigation:
() => this.props.navigation.navigate("AddFriend", {name: "Alan"})
Then in the parent method (if you want to display it, you could just put it in render):
const name = this.props.navigation.getParam(name, null)
If null, you know that the screen was reached from a different screen, and can handle that case normally. You can add whatever params you want.
I have a problem with refs on React Native. This is a simplified version of my code:
class Main extends React.Component {
constructor(props) {
...
this.refs = {};
}
render() {
if(this.state.page=="index") {
return(
<View>
<FlatList ref={flatlist => this.refs.flatlist = flatlist}> ... </FlatList>
<MyActionButton flatlist={this.refs.flatlist}/>
</View>
)
} else if (this.state.page="text"=){
return(
<Text> ... </Text>
)
}
}
}
class MyActionButton extends React.Component {
render(
return(
<ActionButton>
<ActionButtonItem onPress={() => {
console.log("AB props", this.props)
}} />
</ActionButton>
)
)
}
The app starts with this.state.page = "index" so when I press MyActionButton I see the log as expected, and things seem to work:
'AB props', {flatlist: {A LOT OF STUFF HERE}}
However If I change the state.page to "text" and then come back to "index" again, when I press MyActionButton I get:
'AB props', {flatlist: undefined}
I'm not sure why that prop gets undefined and how to fix it to make it point to the actual FlatList.
I don't like very much, but I managed to get it working by changing the reference to a getter funcion
class Main extends React.Component {
constructor(props) {
...
this.refs = {};
}
getFlatList() {
return this.refs.flatlist;
}
render() {
if(this.state.page=="index") {
return(
<View>
<FlatList ref={flatlist => this.refs.flatlist = flatlist}> ... </FlatList>
<MyActionButton flatlist={this.getFlatList.bind(this)}/>
</View>
)
} else if (this.state.page="text"=){
return(
<Text> ... </Text>
)
}
}
}
I want to call a function called 'test()' that is stated inside Main.js. Especially test() function will update the state of somestate inside Main.js and I put console log to see what's going on.
When the index of route is 1, as stated the below, the leftButton configuration(that is done in index.ios.js) is applied ( so it becomes 'click!' ). When I click this, then an error message is displayed, saying, Main.test is not a function.
Is there way that I can access test() function inside Main.js when clicking 'leftButton' in the navigator while staying in Main.js?
Do I have to do something inside index.ios.js or inside Main.js? Please share any idea with me!! (:
The below is snippet of my code ( both index.ios.js and Main.js )
////////////////////////////////////////////////////////////////
...
var Main = require('./Main');
...
render() {
return (
<Navigator
initialRoute={{ title: 'Log In Page', index: 0, component: FirstPage }}
configureScene={() => {
return Navigator.SceneConfigs.FloatFromRight;
}}
navigationBar={
<Navigator.NavigationBar
routeMapper={{
LeftButton: (route, navigator, index, navState) =>
{
if (route.index === 0) {
return null;
} else if(route.index ===1) {
return (
<TouchableHighlight onPress={() => { Main.test() } } >
<View style={styles.back}>
<Text>click!</Text>
</View>
</TouchableHighlight>
);
} else{
return (
<TouchableHighlight onPress={() => {navigator.pop(); console.log('ended..'); }}>
<View style={styles.back}>
<Text>Click</Text>
</View>
</TouchableHighlight>
);
}
},
Title: (route, navigator, index, navState) =>
{ return (<Text style={styles.route_title}> {route.title} {route.index} </Text>); },
}}
style={{backgroundColor: '#28b496'}} />
}
renderScene={(route, navigator) => React.createElement(route.component, { ...route.passProps, navigator })}
style={{padding: 0}} />
);
}
////////////////////////////////////////////////////////////////
..
class Main extends Component {
constructor(props) {
super(props);
..
}
test(){
console.log('I wish this will be triggered!');
console.log('This is the function that I want to call');
this.setState({
somestate: 'change to this man'
});
}
render(){
return(
<Text>HI</Text>
);
}
...
module.exports = Main;
////////////////////////////////////////////////////////////////
Looks like Main is a React component - in that case you cannot call test method directly unless you instantiate that component and access a reference to it (e.g. this.refs.main.test()).
I'd strongly suggest extracting test method out so that it's not component specific or making it static.
In case none of the above are possible, you could try rendering Main in below the <Navigator /> and using refs:
render() {
return (
<View>
<Navigator />
<Main ref="main" />
</View>
);
}
and accessing the method using refs as proposed at the beginning of the answer.
However, if your Main component is already in the tree (e.g. it's your parent component), you could use context and pass the method as a part of it.