oflcad
oflcad

Reputation: 515

Reactjs How to update the component after store updates

I am using nachos-ui in my react native application, the Switcher component

in my configScreen.js like so:

<Switcher
     onChange={((difficulty) => this.onLevelSelect(difficulty) )}
     defaultSelected={this.props.difficulty} >
     <TabButton value='medium' text='medium'  />
     <TabButton value='easy' text='easy'/>
 </Switcher>

and I have a reset button that resets the value of this.props.difficulty from the store.

everything works fine the store gets reseted and the value but the Switcher keeps indicating to the old value, unless i move to another screen then goBack to configScreen.js, at that time the change takes effect(ie: the value of defaultSelected indicates the store value.

Can someone guide me on how to use componentShouldUpdate or componentWillRecieveProps ??

action/index.js :

exports.changeLevel = (select) => {
      return {
            type: 'CHANGE_LEVEL',
            select
      }  

}

exports.resetGame = () => {
      return{
            type: 'RESET_GAME',
      }
}

reducer/index.js :

module.exports = ( state = {}, action ) => {
    switch(action.type) {
        case 'RESET_GAME' : 

            return {
                ...state,
                difficulty: 'easy',
            }
        case 'CHANGE_LEVEL':
            return {
                ...state,
                difficulty: action.select
            }
        default:
            return state
    }
}

store/index.js :

import { createStore, compose } from 'redux';
import { AsyncStorage } from 'react-native';
import { persistStore, autoRehydrate } from 'redux-persist'; 

import reducer from '../reducer';

const defaultState = {
    difficulty: 'easy',

}


export const configureStore = (intialState = defaultState, action) =>{

    var store = createStore(reducer, intialState,compose(
        autoRehydrate()
    ));
    persistStore(store, {storage: AsyncStorage});

    return store;

}

configScreen.js :

import React, { Component } from 'react';
import {connect} from 'react-redux';
import { View, Text, StyleSheet, ImageBackground, TouchableOpacity, Dimensions } from 'react-native';
import { Container, Header, Content, Card, CardItem,  Button, Icon, Left, Body } from 'native-base';
import { AdMobBanner } from "expo";
import DropdownAlert from 'react-native-dropdownalert';
import {changeLevel, reset, resetGame} from '../actions';
import {Switcher, TabButton, themeManager} from 'nachos-ui';
import { configureStore } from '../store';

const buttonTheme = themeManager.getStyle('TabButton');

const newButtonTheme = {
  ...buttonTheme,
  BUTTON_BACKGROUND:'#fff',
  BUTTON_BORDER_WIDTH:1,
  BUTTON_BORDER_COLOR:'#4bb29e',
  BUTTON_BORDER_RADIUS:5,
  BUTTON_HEIGHT:25,
  BUTTON_WIDTH: 10,
  BUTTON_FONT_COLOR:'black',
  BUTTON_FONT_SIZE:14,
  BUTTON_FONT_WEIGHT:'normal',
  BUTTON_FONT_FAMILY:'jf',
  BUTTON_SELECTED_BACKGROUND:'#4bb29e',
  BUTTON_SELECTED_FONT_COLOR:'white',
  BUTTON_SELECTED_BORDER_COLOR:'#4bb29e',

}
themeManager.setSource('TabButton', () => (newButtonTheme))

const screenWidth = Dimensions.get('window').width / 12;


class AboutScreen extends Component {

  _goBack = () => {
    this.props.navigation.goBack(null)
  }

  onClose(data) {
    // data = {type, title, message, action}
    // action means how the alert was closed.
    // returns: automatic, programmatic, tap, pan or cancel
  }

  // RESET THE STORE HERE 
  onReset() { 
    this.gameReset() 
    console.log(configureStore().getState())
  }

  onLevelSelect(selected) {

    this.props.dispatch(changeLevel(selected))

  }

  gameReset() {
    this.props.dispatch(resetGame())
  }    

  render() {
    return (
      <Container style={{backgroundColor: 'white',}}>
          <Content>
            <Card>
            <CardItem>
                          <Switcher
                            onChange={((difficulty) => 
                             this.onLevelSelect(difficulty) )}
                            defaultSelected={this.props.difficulty}
                            >
                                <TabButton value='medium' text='medium'  />
                                <TabButton value='easy' text='easy'/>
                          </Switcher>
                     <CardItem>
                        <Button title='reset' onPress={() => this.gameReset()} />
            </Card>
          </Content>

      </Container>
    );
  }
}


function mapStateToProps(state) {
  return {
    difficulty: state.difficulty,
  }
}

function mapDispatchToProps(dispatch) {
  return {
      changeLevel: () => dispatch(changeLevel(this.props.difficulty))
  }
}

export default connect(mapStateToProps)(AboutScreen)

Upvotes: 2

Views: 4327

Answers (2)

oflcad
oflcad

Reputation: 515

The problem was with the switcher component from nachos-ui.

react-native-element buttonGroup or built-in component solved the issue,

nachos-ui: v0.2.0-beta.1

react-native :forked version from facebook v27 expo SDK :v28

Upvotes: 0

You shouldn't use componentWillRecieveProps, since it will be deprecated soon enough.
It will be replaced with getDerivedStateFromProps (Note, doesn't work the same way as componentWillRecieveProps).

Also, if you are using it correctly you shouldn't have to use those lifecycle-methods because

  1. A Prop change will trigger a rerender
  2. componentWillRecieveProps(nextProps)'s purpose is/was to update state on props change.

If you want more help, post more code :)

Update

A few notes:

componentWillUpdate(nextProps, nextState) {
    if(nextProps.difficulty !== this.props.difficulty ){
        this.setState({difficulty: this.props.difficulty})
    }
}

What are you trying to achieve here? It's quite dangerous to perform this.setState({...}); here. Because you can trigger an endless loop of rerenders. Also you are not doing anything but assigning components local state to what the old props is. And you are not using this.state.difficulity anywhere in the application. Effectively you are only triggering another rerender (which will happen anyway).

Also:

function mapDispatchToProps(dispatch) {
    return {
        changeLevel: () => dispatch(changeLevel(this.props.difficulty))
    }
}

You don't have access to this because it's outside class context. You need to take in this.props.difficulty (although you are passing it selected) as a parameter to the dispatch call. You are not connecting your resetGame() here, which you should do. Lastly try to use different naming inside dispatch:

function mapDispatchToProps(dispatch) {
    return {
        onDispatchChangeLevel: (difficulty) => dispatch(changeLevel(difficulty)),
        onDispatchResetGame: () => dispatch(resetGame())
    }
}

Then when you are dispatching changeLevel pass it this.props.difficulty and use the connected dispatch instead of this.props.dispatch:

onLevelSelect(selected) {
    this.props.onDispatchChangeLevel(selected)
}

gameReset() {
    this.props.onDispatchResetGame();
}

Finally:

I´m not sure about this one:

<Switcher onChange={((difficulty) => this.onLevelSelect(difficulty) )} defaultSelected={this.props.difficulty}>

Usually you onChange is passing an event and you are treating it like a difficulity string easy. But as I said, I don´t know how Switcher works.

Hope this will be useful, I think the error lies somewhere on what I've commented on. Otherwise I can have another look :)

Upvotes: 1

Related Questions