On pressing 'Add' button in the Scene, navigation bar should be set to hidden and the component in 'renderAddCategory' need to be set to visible.
When Add button is pressed, Actions.refresh({hideNavbar:true}) will set the navigation bar to hidden. This inturn calls componentWillReceiveProps, where the flag showAddCategory is set. Based on the value set in 'showAddCategory' flag, the component in 'renderAddCategory' need to show/hide the component.
Kindly assist what should i need to replace in "<<< showAddCategory >>>>>" to achieve the requirement.
<Scene key="CategoryContainer" component={CategoryContainer} title="Category" initial
rightTitle="Add" onRight={() => Actions.refresh({hideNavBar: true})}/>
Component:
componentWillReceiveProps(nextProps){
if(nextProps.hasOwnProperty('hideNavBar') && nextProps.hideNavBar){
if(!nextProps.showAddCategory){
nextProps.onCategoryAddMenu();
console.log(nextProps.showAddCategory); // returns new value: true
console.log(this.props.showAddCategory); // returns old value: false
}
}
}
render() {
return (
<View style={styles.container}>
{this.renderAddCategory()}
</View>
);}
renderAddCategory(){
if(<<< showAddCategory >>>>>){
return (
<View>
<TextInput/>
<TouchableHighlight>
<Text>Add</Text>
</TouchableHighlight>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
showAddCategory: state.categoryReducer.showAddCategory,
};
}
Action:
export function categoryAddMenu(){
return {
type: "CATEGORY_ADD_MENU",
};
}
Reducer:
const initialState = {
showAddCategory:false,
}
export default function categoryReducer (state = initialState, action) {
case "CATEGORY_ADD_MENU":
return Object.assign({}, state, {
showAddCategory: true
});
}
I am not sure if what your are doing is the right approach. But I think your issue can be solved using the local state. Do you really need to use redux for storing showAddCategory?
componentWillReceiveProps(nextProps){
if(nextProps.hasOwnProperty('hideNavBar')){
this.setState({ showAddCategory: nextProps.hideNavBar });
}
}
then you should be able to replace <<< showAddCategory >>>>> with this.state.showAddCategory
renderAddCategory(){
if(this.state.showAddCategory) {
return (
<View>
<TextInput/>
<TouchableHighlight>
<Text>Add</Text>
</TouchableHighlight>
</View>
);
}
}
You might also need to bind parent "this" to renderAddCategory function in your constructor.
constructor(props) {
super(props);
this._renderPage = this._renderPage.bind(this);
}
Related
I'm new in RN. When I want to navigate between screens I create this function:
displayScreen2 = () => {
this.props.navigation.navigate("screen2")
}
and I call it in onPress={this.displayScreen2}
with TouchableOpacity or any Touchable when the user clicks he has to wait 1 second or 2 before displaying the screen. So what I want is to change the Touchable icon to an loader.
It's simple if I use a conditional rendering but I don't know how to do it now, when I have to change my state? Any suggestions?
this is my approach:
<TouchableOpacity
style={Styles.topButton}
onPress= {() => {
this.setState({loading: 'load'},
() => {
displayScoreListView()
// this.setState({loading: 'icone'})
}
)
}}
>
<Text style={Styles.scoreListButtonTextRed}>{this.state.loading}</Text>
that not work, tha state change but visualy not because if I return to the first screen I have 'load' in the text component
You could create a custom component wrapping whatever Touchable you prefer, I've used this technique in my production apps before. The button has it's own state which allows you to automatically display a loading indicator when necessary.
export class ButtonWorker extends Component {
state = {
working: false
}
onButtonPress = () => {
this.setState(
{ working: true },
() => {
this.props.onPress(this.onWorkFinished);
}
);
}
onWorkFinished = () => {
this.setState({ working: false });
}
render() {
return (
<TouchableOpacity>
{this.state.working ? (
<ActivityIndicator />
) : (
this.props.children
)}
</TouchableOpacity>
);
}
}
And then use it like a normal button with additional logic!
export class NavigationScreen extends Component {
navigate = (done) => {
// ... Asynchronous logic goes here
done();
this.props.navigation.navigate("Screen2");
}
render() {
return (
<Fragment>
{/* ... */}
<ButtonWorker onPress={this.navigate} />
</Frament>
);
}
}
I have tried many ways but non of these worked. I am trying from 5 days.
I used redux,props,then ref . Non of these helped.
I need the modal to be visible when I call it from another class.
// this is the parent class
export default class Search1 extends React.Component {
constructor(props) {
super(props);
this.setModalVisible1 = this.setModalVisible1.bind(this);
this.state = {
modalVisible1: false,
};
this.closeModal = this.closeModal.bind(this);
}
setModalVisible1(visible) {
this.setState({ modalVisible1: visible });
// this.setModalVisible2(visible);
}
closeModal() {
console.log("modalvi1 value is in closemodal ", this.state.modalVisible1);
this.setState({ modalVisible1: false });
}
render() {
return (
{/* it renders the screen again when I call the */}
<Modal
closeModal={() => this.closeModal}
animationType="slide"
transparent={true}
visible={this.state.modalVisible1}
</Modal>
<NavStack />
);
}
}
// this is the child class
class HomeScreen extends React.Component {
render() {
<TouchableOpacity
style={styles.firstStyle}
onPress={() => {
this.props.closeModal();
);
}}
>
return (
}
The modal should be visible when called from outer child class. It needs to be close when called from the parent class.
I tried using redux. that does not worked. Then I used props. Then I used the ref. None of these helped. Just get tired of this. Help me out of this.
Pass the function to modal
closeModal() {
this.setState({ modalVisible1: false });;
}
<Modal
closeModal={this.closeModal}
animationType="slide"
transparent={true}
visible={this.state.modalVisible1} />
and run it on child component as this.props.closeModal()
I am working on a React-Native app that has multiple screens and uses react-native-router-flux for navigation. One of the screens is supposed to change the background image of the main screen so in this background settings screen I have a list of images with a switch button. Currently it looks like this:
If i try to click on any of the other switches I get the error below:
And here is the code for the main screen:
class MainScreen extends Component {
changedBackground(){
switch(this.props.backgroundImage){
case 'straw':
return (
<Image source={require('../assets/img/paie.png')} style={mainScreenStyle.bgImg}/>
);
case 'rabbit fur':
return (
<Image source={require('../assets/img/rabbit_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
case 'bear fur':
return(
<Image source={require('../assets/img/bear_fur.jpeg')} style={mainScreenStyle.bgImg}/>
);
case 'fox fur':
return (
<Image source={require('../assets/img/fox_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
default:
return ''
}
}
render() {
return (
<View style={mainScreenStyle.container}>
<View style={mainScreenStyle.menu}>
{this.changedBackground()}
</View>
<TargetComponent style={mainScreenStyle.targetD}/>
<ScoreBadges/>
</View>
);
}
}
const mapStateToProps = state => {
return {
backgroundImage: state.mainScreen.backgroundImage,
};
};
export default connect(mapStateToProps, {changeBackground})(MainScreen);
And the code for the background settings screen:
const straw = () => {
return <Image source={require('../../assets/img/paie.png')}
style={[background_list_img.background_img_icon]}/>;
};
const rabbit = () => {
return <Image source={require('../../assets/img/rabbit_fur.jpg')}
style={[background_list_img.background_img_icon]}/>;
};
const bear = () => {
return <Image source={require('../../assets/img/bear_fur.jpeg')}
style={[background_list_img.background_img_icon]}/>;
};
const fox = () => {
return <Image source={require('../../assets/img/fox_fur.jpg')}
style={[background_list_img.background_img_icon]}/>;
};
const backgrounds_list = [
{id:'0', name:'straw', img:straw()},
{id:'1', name:'rabbit', img:rabbit()},
{id:'2', name:'bear', img:bear()},
{id:'3', name:'fox', img:fox()}
];
class BackgroundSettings extends Component {
render(){
return <FlatList data={backgrounds_list} keyExtractor={item=>item.id}
renderItem={({item})=>{return(
<ListItem leftIcon={item.img}
title={" " + item.name}
hideChevron
switchButton
switched={this.props.currentBackground === item.name}
onSwitch={()=>{this.props.changeBackground(item.name)}}/>);}}
/>;
}
}
mapStateToProps = state => {
return {
currentBackground: state.mainScreen.backgroundImage,
};
};
export default connect(mapStateToProps, {changeBackground})(BackgroundSettings);
The reducer is very simple:
const INITIAL_STATE = {backgroundImage:'straw'};
export default MainScreenReducer = (state = INITIAL_STATE, action) => {
switch (action.type){
case BACKGROUND_CHANGE:
return { ...state, backgroundImage:action.payload};
default:
return state;
}
}
And the action creator is simple as well:
export const changeBackground = (imageName) =>{
return{
type:BACKGROUND_CHANGE,
payload:imageName
};
};
Any idea what am I missing? I spent two days trying to figure this out...
change the switch statement default to any component
changedBackground(){
switch(this.props.backgroundImage){
case 'straw':
return (
<Image source={require('../assets/img/paie.png')} style={mainScreenStyle.bgImg}/>
);
case 'rabbit fur':
return (
<Image source={require('../assets/img/rabbit_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
case 'bear fur':
return(
<Image source={require('../assets/img/bear_fur.jpeg')} style={mainScreenStyle.bgImg}/>
);
case 'fox fur':
return (
<Image source={require('../assets/img/fox_fur.jpg')} style={mainScreenStyle.bgImg}/>
);
/**
here change like this:
return <View/>
or
return null
*/
default:
return ''
}
}
Instead of returning '', you should try returning null. Because '' is a string and needs Text around it. However null is an object and can be used instead of DOM objects.
Most simplified working example provided in github !!!
I have a simple app to learn building apps with react native and redux. From my understanding if you display data from the redux state in your render method and then values of this state is changed, then the value will be changed as well and react rerenders all components which needs to be rerendered due to the state change.
I have the application available on github: https://github.com/schingeldi/checklist
Its really simple. I have an overview, if you click on the status of an entry, you get to a detailed page. If you click on "Mark xxx" the status in changed in the redux state (according to logs) but its not refreshed in the overview scene.
Basically I have an Overview.js:
class Overview extends Component {
constructor(props) {
super(props);
this.state = {fetching:false};
}
entries() {
// console.log("Overview");
// console.log(this.props);
// console.log(this.props.entries);
return Object.keys(this.props.entries).map(key => this.props.entries[key]);
}
componentDidMount() {
this.setState({fetching:true});
this.props.actions.getEntries()
.then( (res) => {
this.setState({fetching: false});
})
}
handleChange(entryId) {
Actions.detail({id: entryId});
}
render() {
return (
<View>
<ScrollView>
{ !this.state.fetching && this.entries().map((entry) => {
return (
<TouchableHighlight key={entry.id}>
<View >
<Text>{entry.name}</Text>
<TouchableHighlight onPress={(entryId ) => this.handleChange(entry.id)}><Text>{entry.status}</Text></TouchableHighlight>
<Text>---------------------------</Text>
</View>
</TouchableHighlight>
)
}
)
}
{this.state.fetching ? <Text>Searching </Text> : null }
</ScrollView>
</View>
)}
}
function mapStateToProps(state) {
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect(mapStateToProps, mapDispatchToProps)(Overview);
When clicking on the Status ( {entry.status} ) I open another Scene Details.js:
class Detail extends Component {
constructor(props) {
super(props);
this.state = {}
}
componentWillMount() {
this.setState({
entry: this.props.entries[this.props.id]
})
}
patchEntry(newStatus) {
console.log("Details: patchEntry with " + this.props.id +" and " + newStatus );
this.props.actions.patchEntry(this.props.id, newStatus);
}
render() {
return (
<View>
<Text>{this.state.entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
}
function mapStateToProps(state) {
console.log(state);
return {entries: state.default.entries };
}
function mapDispatchToProps(dispatch) {
return {actions: bindActionCreators(actions,dispatch)};
}
export default connect( mapStateToProps, mapDispatchToProps)(Detail);
And I have an action and a reducer which are called perfectly fine when one of the TouchableHighlights are pressed. I even see in the logs that the state is changed when outputting the whole state.
But my question is, how do I get the status refreshed on the Overview scene, once I got back (pop) from the Detail scene?
If you need anymore information let me know, but it should be simple to reproduce as I wrote a whole working app. Just clone, npm install and run it.
Thanks a lot for your help.
I did a quick look into your code and here are some suggestions/information.
In you Detail.js file you're setting your state once the component is mounted.
When you update your redux store and get the refreshed props, it won't update your UI because it's reflecting your state, and your state won't get the new value because you're only setting it on componentWillMount method. Check more information here in the docs.
Also it seems it's not very clear for you when to use the React component's state.
In this example, from Detail.js file you don't need the component's state at all. You can compute that value directly from the properties.
Ex:
render() {
const entry = this.props.entries[this.props.id];
return (
<View>
<Text>{entry.name}</Text>
<TouchableHighlight onPress={() => this.patchEntry('done')}><Text>Mark done</Text></TouchableHighlight>
<TouchableHighlight onPress={() => this.patchEntry('cancelled')}><Text>Mark cancelled</Text></TouchableHighlight>
</View>
)
}
You could even do that inside your mapStateToProps function. More info here.
Ex:
function mapStateToProps(state, ownProps) {
return {
entries: state.default.entries,
entry: state.default.entries[ownProps.id],
};
}
It seems your Overview.js file is OK regarding the UI being updated, because it's render method is reflecting the props and not it's state.
UPDATE 06/27
I've just checked your reducers and you may have some fixes to do there as well.
case ENTRY_PATCHING:
let patchedEntries = state.entries;
patchedEntries[action.data.entryId].status = action.data.newStatus;
return {...state,
entries: patchedEntries
}
In this reducer you're mutation your state, and you must not do that. The redux store can't be mutated. You can check more details about it here http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html
So, fix example:
case ENTRY_PATCHING:
const patchedEntry = {
...state.entries[action.data.entryId],
status: action.data.newStatus
}
return {
...state,
entries: {
...state.entries,
[action.data.entryId]: patchedEntry,
}
}
I am using a ListItem onPress to navigate to a different route using the code below.
onPress(item) {
this.props.navigator.push({
component: Areas,
passProps: {
new_id: item.new_id,
}
});
}
_renderItem(item) {
return (
<ListItem item={item} onPress={ () => this.onPress(item) }/>
);
}
However, the this.props.new_id is undefined in the next component.
export default class areas extends Component {
constructor(props) {
super(props);
console.log(" UUUU ");
console.log(this.props.new_id);
}
render(){
return (
<Text style={styles.liText}>AAA {this.props.new_id} BBB</Text>
);
}
AppRegistry.registerComponent('areas', () => areas);
Is there anything I am doing incorrectly?
Actually, I found the answer. I looked in renderScene which was not passing the props properly.
I fixed it in the following way:
Initial:
return React.createElement(route.component, { navigator });
To:
return React.createElement(route.component, { ...this.props, ...route.passProps, route, navigator } )
This seems the pass the props.