I've tried to change the radio value in ReactNative App with NativeBase template. I want to get or set value from the radio after click it, exactly checked or not. But couldn't find a way to get or set value to it. Even the radio button never changed on the screen after click. The codes are like as below:
import React, { Component } from 'react';
import { TouchableOpacity, Image, View } from 'react-native';
import { connect } from 'react-redux';
import { actions } from 'react-native-navigation-redux-helpers';
import {
Container,
Header,
Title,
Content,
Text,
Button,
Icon,
InputGroup,
Input,
List,
ListItem,
Radio, } from 'native-base';
import { openDrawer } from '../../actions/drawer';
import { Col, Row, Grid } from 'react-native-easy-grid';
import styles from './styles';
import dimension from './global';
import Swiper from 'react-native-swiper';
const imgBoy = require('../../../images/icon_boy.png');
const imgGirl = require('../../../images/icon_girl.png');
const {
popRoute,
} = actions;
class SessionPage extends Component {
static propTypes = {
name: React.PropTypes.string,
index: React.PropTypes.number,
list: React.PropTypes.arrayOf(React.PropTypes.string),
openDrawer: React.PropTypes.func,
popRoute: React.PropTypes.func,
navigation: React.PropTypes.shape({
key: React.PropTypes.string,
}),
}
popRoute() {
this.props.popRoute(this.props.navigation.key);
}
constructor(props) {
super(props);
// console.log(this.props.navigation);
this.state = {
sliderCount : parseInt(this.props.navigation.behavior.length / 5) + 1,
sliderArray : [],
selected : false,
}
this.getSliderArray();
console.log(this.state);
}
getSliderArray() {
for (var i = 0; i < this.state.sliderCount; i++) {
var childArray = [];
for (var j = i * 5; j < 5 * (i + 1); j++) {
if (this.props.navigation.behavior[j] != null){
var unit = this.props.navigation.behavior[j];
unit.selected = true;
childArray.push(unit);
}
}
this.state.sliderArray.push({
index : i,
behaviors : childArray
})
}
}
selectRadio(i, j){
this.state.sliderArray[i].behaviors[j].selected = true;
}
render() {
const { props: { name, index, list } } = this;
return (
<Container style={styles.container}>
<Swiper style={styles.wrapper}
height={dimension.Height - 400}
width={dimension.Width - 40}
showsButtons={false}
showsPagination={true}>
{this.state.sliderArray.map((item, i) =>
<View style={styles.slide1} key={i}>
{item.behaviors.map((subitem, j) =>
<ListItem key={i + "-" + j} style={styles.cardradio}>
<Radio selected={this.state.sliderArray[i].behaviors[j].selected} onPress={() => this.selectRadio(i, j)} />
<Text>{subitem.behaviorName}</Text>
</ListItem>
)}
</View>
)}
</Swiper>
</Content>
</Container>
);
}
}
function bindAction(dispatch) {
return {
openDrawer: () => dispatch(openDrawer()),
popRoute: key => dispatch(popRoute(key)),
};
}
const mapStateToProps = state => ({
navigation: state.cardNavigation,
name: state.user.name,
index: state.list.selectedIndex,
list: state.list.list,
});
export default connect(mapStateToProps, bindAction)(SessionPage);
selectRadio(i, j){
this.state.sliderArray[i].behaviors[j].selected = true; <== This is the problem
}
When you call this.state = something after the component has mounted, it doesn't trigger update method of component life cycle. Hence view will not be updated.
You should be using this.setState() to update your views
this.setState({
slider = something
})
For more info, refer docs
this.setState() is an async method. After you make changes in getSliderArray(), it may not be reflected in immediate console.log
this.getSliderArray();
console.log(this.state);
You can pass callback to this.setState() to perform any action only after state is changed
this.setState({
// new values
}, function() {
// Will be called only after switching to new state
})
Related
Hello I have a react component like which either display a list of items or opens another component which allowes me to select some value. Here is the code
import React, { PureComponent } from "react";
import { Text, View } from "react-native";
import { withNavigationFocus } from "react-navigation-is-focused-hoc";
import { NavigationActions } from "react-navigation";
import { connect } from "react-redux";
import ClassListing from '../../components/ClassListing/ClassListing';
import Actions from "../../state/Actions";
import { default as stackStyles } from "./styles";
import {
StyleCreator
} from "../../utils";
let styles = StyleCreator(stackStyles.IndexStyles)();
#withNavigationFocus
#connect(() => mapStateToProps, () => mapDispatchToProps)
export default class ClassList extends PureComponent {
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
return {
header: null,
tabBarOnPress({ jumpToIndex, scene }) {
params.onTabFocus();
jumpToIndex(scene.index);
},
};
};
constructor(props) {
super(props);
this.state = {};
console.ignoredYellowBox = ["Warning: In next release", "FlatList"];
}
componentDidMount() {
console.log("componentDidMount");
this.props.navigation.setParams({
onTabFocus: this.handleTabFocus
});
}
componentDidUpdate() {
console.log("I am updated");
}
_handleNavigationBar = () => (
<View style={styles.headerContainer}>
<Text style={styles.headerLeftTitle}>Klasselister</Text>
</View>
);
handleTabFocus = () => {
this.props.resetClassList();
// setTimeout(() => {
// this._openChildSchoolSelector();
// },500);
};
_openChildSchoolSelector = () => {
if (
!this.props.childrenSelection.selectedDependant ||
!this.props.childrenSelection.selectedSchool
) {
console.log("navigate to child selector");
this.props.navigation.dispatch(
NavigationActions.navigate({
routeName: "ChildSchoolSelector",
})
);
}
};
render() {
return (
<View>
{this._handleNavigationBar()}
{this.props.classList &&
this.props.classList.classList &&
this.props.classList.classList.length > 0 ? (
<ClassListing list={this.props.classList.classList} />
) : (
null
// this._openChildSchoolSelector()
)}
</View>
);
}
}
const mapStateToProps = (state) => {
const { classList, childrenSelection } = state.appData;
console.log("[Classlist] mapStateToProps", classList);
console.log("[Classlist] mapStateToProps", childrenSelection);
return {
classList,
childrenSelection
};
};
const mapDispatchToProps = (dispatch) => ({
resetClassList: () => {
dispatch(Actions.resetClassList());
},
});
My issue is that when I come to this tab, I want to reset the list which came from server and then open the school selector again, which I am doing in handleTabFocus. But there I have to first call
this.props.resetClassList();
this._openChildSchoolSelector()
The issue is that this._openChildSchoolSelector() is called while this.props.resetClassList() hasn't fininshed.
this.props.resetClassList() works like this
it calls an action like this
static resetClassList() {
return {
type: ActionTypes.RESET_CLASS_LIST
};
}
which then cleans the classlist like this
case ActionTypes.RESET_CLASS_LIST:
return createDefaultState();
in ClassListReducer.js
and also in ChildrenSelectionRedcuer.js
case ActionTypes.RESET_CLASS_LIST:
return createDefaultState();
Any hints to solve this? My project uses very old React navigation v1
One thing which I did was to use this
componentWillReceiveProps(nextProps) {
console.log(this.props);
console.log(nextProps);
if (
nextProps.isFocused &&
nextProps.focusedRouteKey == "ClassList" &&
(!nextProps.childrenSelection.selectedDependant ||
!nextProps.childrenSelection.selectedSchool)
) {
console.log("componentWillReceiveProps");
this._openChildSchoolSelector();
}
if (
this.props.isFocused &&
this.props.focusedRouteKey === "ClassList" &&
nextProps.focusedRouteKey !== "ClassList"
) {
console.log("reset");
this.props.resetClassList();
}
}
But not sure if this is an elegant way of doing this
I am new in react native I am trying to render the count of unread notification for that I called my API in HOC it is working fine for initial few seconds but after that, I started to get the below error
func.apply is not a function
below is my code
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Modal, View } from "react-native";
import { themes } from "./constants";
import { AsyncStorage } from "react-native";
export default (OriginalComponent, animationType) =>
class extends Component {
static propTypes = {
handleFail: PropTypes.func,
theme: PropTypes.string,
visible: PropTypes.bool
};
state = {
modalVisible: true
};
static getDerivedStateFromProps({ visible }) {
if (typeof visible === "undefined") {
setInterval(
AsyncStorage.getItem("loginJWT").then(result => {
if (result !== null) {
result = JSON.parse(result);
fetch(serverUrl + "/api/getUnreadNotificationsCount", {
method: "GET",
headers: {
Authorization: "Bearer " + result.data.jwt
}
})
.then(e => e.json())
.then(function(response) {
if (response.status === "1") {
if (response.msg > 0) {
AsyncStorage.setItem(
"unreadNotification",
JSON.stringify(response.msg)
);
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}
})
.catch(error => {
alert(error);
// console.error(error, "ERRRRRORRR");
});
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}),
5000
);
return null;
}
return { modalVisible: visible };
}
handleOpenModal = () => {
this.setState({ modalVisible: true });
};
handleCloseModal = () => {
const { handleFail } = this.props;
this.setState({ modalVisible: false }, handleFail);
};
render() {
const { modalVisible } = this.state;
const { theme } = this.props;
return (
<View>
<Modal
animationType={animationType ? animationType : "fade"}
transparent={true}
visible={modalVisible}
onRequestClose={this.handleCloseModal}
>
<View style={themes[theme] ? themes[theme] : themes.transparent}>
<OriginalComponent
handleCloseModal={this.handleCloseModal}
{...this.props}
/>
</View>
</Modal>
</View>
);
}
};
I have not used getDerivedStateFromProps but, according to the docs, it is called on initial component mount and before each render update.
Thus your code is creating a new interval timer on each update without clearing any of the earlier timers, which could be causing a race condition of some sort.
You may want to consider using the simpler alternatives listed in the docs, or at a minimum, insure that you cancel an interval before creating a new one.
I'm getting a floating number from sqlite db and setting up it in redux state. Than I'm showing this property to TextInput component. To do this, I have to convert floating number to string. While editing value, I trigger event onChangeText, convert string to floating number and update redux state.
When I clear last char after point in a TextInput, my point also clearing because of converting property value from number to string. How can I save point in this case? And what's the wright way to work with floating values in react-redux?
My custom component code:
import React from 'react';
import PropTypes from 'prop-types';
import { View, TextInput } from 'react-native';
class FormFieldNumeric extends React.PureComponent {
constructor() {
super();
this.state = {
textValue: ''
}
}
componentWillReceiveProps(nextProps, nextContext) {
if (parseFloat(this.state.textValue) !== nextProps.value) {
this.setState({
textValue: nextProps.value ? String(nextProps.value) : ''
})
}
}
onChangeText = text => {
if (!text) {
this.changeValue('');
return;
}
if (text.length === 1) {
if (!'0123456789'.includes(text)) {
return;
}
}
const lastSymbol = text[text.length - 1];
if ('1234567890.,'.includes(lastSymbol)) {
if (text.split('').filter(ch => ch === '.' || ch === ',').length > 1) {
return;
}
if (lastSymbol === ',') {
this.changeValue(this.state.textValue + '.');
return;
}
this.changeValue(text);
}
};
changeValue = text => {
this.setState({
textValue: text
});
this.props.onChange(text ? parseFloat(text) : 0);
};
render() {
const { caption, value, onChange, placeholder } = this.props;
return (
<View>
<TextInput
value={this.state.textValue}
keyboardType="numeric"
onChangeText={this.onChangeText}
placeholder={placeholder}
maxLength={10}
/>
</View>
)
}
}
FormFieldNumeric.propType = {
placeholder: PropTypes.string,
value: PropTypes.number,
onChange: PropTypes.func.isRequired
};
export default FormFieldNumeric;
One option would be to only valid and update the value in your redux store when the user is finished editing vs on every keystroke. You might use the onEndEditing TextInput callback to accomplish this.
Understood what was my mistake. I fell into the anti-pattern trap when I try to keep the same state in two places. This article describes in detail this. According to the recommendations from the article, I used an uncontrolled component and stored the state directly in the component, and I only pass the property in order to initialize the value.
import React from 'react';
import PropTypes from 'prop-types';
import { View, TextInput } from 'react-native';
class FormFieldNumeric extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
defaultValue: props.defaultValue,
textValue: this.floatToTextValue(props.defaultValue)
}
}
floatToTextValue = value => {
return value ? String(value) : '';
};
render() {
const { placeholder } = this.props;
return (
<View>
<TextInput
value={this.state.textValue}
keyboardType="numeric"
onChangeText={this.onChangeText}
placeholder={placeholder}
/>
</View>
)
}
}
FormFieldNumeric.defaultValue = {
placeholder: '',
defaultValue: 0
};
FormFieldNumeric.propType = {
placeholder: PropTypes.string,
defaultValue: PropTypes.number,
onChange: PropTypes.func.isRequired
};
export default FormFieldNumeric;
And for the component to update the values after loading the redux state, I made the _isLoading field in the parent component, which is true by default, but becomes false after the data is loaded. I passed this value as the key property of the parent component:
class ParentComponent extends React.PureComponent {
_isLoading = false;
async componentDidMount() {
await this.props.onCreate();
this._isLoading = false;
}
render() {
return (
<View key={this._isLoading}>
<FormFieldNumeric
defaultValue={this.props.cashSum}
onChange={this.onChangeCashSum}
placeholder="0.00"
/>
</View>
)
}
}
I'm trying to add a filter to my app, but for some reason selectedValue in the <Picker> component doesn't stick with the option I select. I can see the filter text changing from "all" to "lobby" in the top left, however as soon as the player list fully renders, it changes back to "all." and playerListFilterType prop is set to undefined. I stepped through the code in a debugger, and it stays "lobby" until the list re-renders. The action itself works, so the list is showing accurate results.
Here's what my code looks like:
import React from 'react'
import { View, Picker } from 'react-native'
import PlayerList from '../components/PlayerList'
import { fetchPlayerListAsync, filterPlayers } from '../redux/actions/player_actions';
import NavigationHeaderTitle from '../components/NavigationHeaderTitle'
import PlayerStatusFilterPicker from '../components/pickers/PlayerStatusFilterPicker'
import { connect } from 'react-redux'
class PlayerListScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
const playerStatusFilterPicker = (
<PlayerStatusFilterPicker
playerListFilterType={navigation.getParam('playerListFilterType')}
filterPlayers={navigation.getParam('filterPlayers')}
playerList={navigation.getParam('playerList')}
/>
)
return {
headerTitle: navigation.getParam('headerButton'),
headerRight: playerStatusFilterPicker
}
}
async componentDidMount() {
await this.fetchPlayersAsync();
}
setNavigationParams = () => {
this.props.navigation.setParams({
headerButton: this.headerButton,
playerList: this.props.playerList,
playerListFilterType: this.props.playerListFilterType,
filterPlayers: this.props.filterPlayers
})
}
// navigation header element
headerButton = () => (
<NavigationHeaderTitle
handleDataRequest={this.fetchPlayersAsync}
titleMessage={(this.props.fetchingData) ? 'fetching list of players' : `${this.props.playerList.length} online`}
/>
)
fetchPlayersAsync = async () => {
await this.props.fetchPlayerListAsync();
this.setNavigationParams()
}
render() {
return (
<View>
<PlayerList
playerList={this.props.playerList}
fetchingData={this.props.fetchingData}
handleDataRequest={this.fetchPlayersAsync}
/>
</View>
)
}
}
const mapStateToProps = state => {
return {
fetchingData: state.player.fetchingData,
playerList: state.player.playerList,
unfilteredPlayerList: state.player.unfilteredPlayerList,
playerListFilterType: state.player.playerListFilterType
}
};
export default connect(mapStateToProps, { fetchPlayerListAsync, filterPlayers })(PlayerListScreen)
and here's what the filter component looks like, but I don't think the problem lies here:
import React, { Component } from "react";
import {
View,
Picker
} from "react-native";
import * as constants from '../../constants'
class PlayerStatusFilterPicker extends Component {
render() {
return (
<View>
<Picker
selectedValue={this.props.playerListFilterType}
onValueChange={(itemValue) => this.props.filterPlayers(itemValue, this.props.playerList)}
style={{ height: 40, width: 100 }}
>
<Picker.Item label='all' value='all' />
<Picker.Item label="lobby" value={constants.IN_LOBBY} />
<Picker.Item label="in game" value={constants.IN_GAME} />
</Picker>
</View>
);
}
}
export default PlayerStatusFilterPicker;
Here's what the reducer looks like:
// show only the players that are waiting in the main lobby
case actionTypes.SHOW_PLAYERS_IN_LOBBY: {
const filteredList = action.payload.filter(player => player.status === constants.IN_LOBBY)
return { playerList: filteredList, playerListFilterType: constants.IN_LOBBY, fetchingData: false }
}
// show only the players that are currently playing
case actionTypes.SHOW_PLAYERS_IN_GAME: {
const filteredList = action.payload.filter(player => player.status === constants.IN_GAME)
return { playerList: filteredList, playerListFilterType: constants.IN_LOBBY, fetchingData: false }
}
Fixed it by using componentDidUpdate lifecycle method. Like so:
componentDidUpdate(prevProps) {
if (this.props.playerListFilterType != prevProps.playerListFilterType) {
this.props.navigation.setParams({
playerListFilterType: this.props.playerListFilterType
})
}
}
In my application, I created a timer component. This is a smart component because I wanted to handle the counter state inside the component.
this is my code
import React, { Component } from "react";
import {
View,
Text,
StyleSheet
} from "react-native";
import { RoundedButton} from "../../mixing/UI";
class Timer extends Component {
constructor(props) {
super(props);
this.state = {
counter: 30
}
this.interval = null;
}
componentWillUnmount() {
cleanUp();
}
cleanUp = () => {
clearInterval(this.interval);
}
decreaseCounter = () => {
if (this.state.counter === 0) {
return this.cleanUp();
}
this.setState({counter: this.state.counter - 1});
}
startCounter = () => {
this.interval = setInterval(this.decreaseCounter, 1000);
}
render() {
return (
<View>
<RoundedButton text='Log in' onPress={() => this.startCounter()} />
<Text>{this.state.counter}</Text>
<RoundedButton text='Log in' onPress={() => this.cleanUp()} />
</View>
);
}
}
// styles
const styles = StyleSheet.create({
});
export default Timer;
Now I want to call this from my parent screen. If I pass the counter as a prop,
Now the counter state can't be handled from Timer component. How can I handle the state of the child based on the parent prop.
you can use react component lifecycle componentDidMount()
componentDidMount(){
this.setState({counter: this.props.counter});
}
There after you can use this.setState({counter: this.state.counter - 1})