React-Native - Dynamic State from JSON for Switch - json

Hey there :) I got following issue by adding a filter Modal to my SearchView
I constructed a SearchPage where several events can be listed. This all workes pretty fine. Now i am trying to add filter to my SearchPage. If i set the filter manually it works pretty fine -> Now my issue:
If i try to change the switch value of the Switch, it set´s back to the root because the state for the value is not set
Steps i did explained:
I am trying to open a Modal View where all my filter are listed and where i can set true/false by using a Switch. My idea was to fetch all filter Settings by creating a JSON for it:
module.exports = {
"filter":
{
"track": [
{
"id": 1,
"description": "IoT & Living tomorrow"
},
{
"id": 2,
"description": "Smart & Digital Retail"
},
{
"id": 3,
"description": "Startups, Digital Culture & Collaboration"
}
]
}
}
The JSON above is just for expample - Normally its much larger and has more topics than just track
Now i import the JSON and save it at the var filter. I checked the data is in the right format here -> filter.track -> All my JSON Objects
Now i created a my class with the filter Modal
import React, {Component} from 'react';
import {
ListView,
Modal,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
Switch
} from 'react-native';
var filter = require('../JSON/filter');
class PopoverFilter extends Component {
constructor(props) {
super();
// ds for the menu entries
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
eventTracks: ds.cloneWithRows(filter.filter.track)
}
this.show = this.show.bind(this);
}
render() {
return(
<Modal>
<ListView
style={styles.mainView}
renderRow={this.renderMenuEntries.bind(this)}
dataSource={this.state.eventTracks}/>
</Modal>
);
}
renderMenuEntries(entry) {
var switchState = entry.description;
return(
<View style={styles.switchView}>
<Text style={[styleHelper.fonts.titleSize, styles.text]}>{entry.description}</Text>
<Switch onValueChange={(value) => this.switchChanged(switchState, value)}
value={this.state.switchState}/>
</View>
);
}
switchChanged(field, value) {
var obj = {};
obj[field] = value;
this.setState(obj);
}
}
var styles = StyleSheet.create({
});
module.exports = PopoverFilter;
Please ignore the missing Style and also there are more Objects in the Modal but its not important for this case.
Most important is that i try to render the every Switch by the renderMenuEntries method and i give them all entries -> The works just the Switch is not set right. As far as i try to change the value of the switch it is instant go back to its root. And no state is set.
Maybe my solution is not possible and i have to make every state static - but this solution would be very good in case that i could set dynamic filter later without changing the whole code

The scenario you describe is possible. There were a number of issues I encountered with your code:
In renderMenuEntries the value you were assigning to the <Switch /> component was the description of the data item, instead of the expected boolean that the <Switch /> component value expects. Further, this value was also referencing a property of this.state that didn't exist.
The switchChanged function was also just updating the component state using the data item's description
Using your code sample provided I created a new class from scratch named PopoverFilter. Instead of requiring the filter data within this component, it expects the data to come in via a component prop named filterData. This will promote reusability of the component to accept different datasets.
The code is heavily commented to help explain the concepts demonstrated. Here's the PopoverFilter class:
import React from 'react';
import {
ListView,
Modal,
Switch,
Text,
TouchableOpacity,
View
} from 'react-native';
export default class PopoverFilter extends React.Component {
constructor (props) {
super(props);
// bind relevant handlers up front in the constructor
this.renderRow = this.renderRow.bind(this);
this.onPress = this.onPress.bind(this);
// process the incoming filter data to add a 'selected' property
// used to manage the selected state of its companion switch
this._filterData = this.processFilterData(this.props.filterData);
const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = {
filterDataSource: ds.cloneWithRows(this._filterData)
}
}
processFilterData (filterData) {
// don't mutate the filterData prop coming in
// use map to create a new array and use Object.assign to make
// new object instances with a new property named 'selected' initialized
// with a value of false
return filterData.map((item) => Object.assign({}, item, { selected: false }));
}
switchChanged (rowId, isSelected) {
const index = +rowId; // rowId comes in as a string so coerce to a number
const data = this._filterData;
// don't mutate this._filterData
// instead create a new array and new object instance
this._filterData = [
...data.slice(0, index), // take everything before the target index
Object.assign({}, data[index], { selected: isSelected }), // create a new object instance with updated selected property
...data.slice(index + 1) // take everything after the selected index
];
// update the listview datasource with the new data
this.setState({
filterDataSource: this.state.filterDataSource.cloneWithRows(this._filterData)
});
}
renderRow (item, sectionId, rowId) {
return(
<View>
<Text>{item.description}</Text>
<Switch
onValueChange={(value) => this.switchChanged(rowId, value)}
value={item.selected}
/>
</View>
);
}
// just a test function used to dump the current state of the _filterData
// to the console
onPress () {
console.log('data', this._filterData);
}
render () {
return (
<Modal>
<ListView
renderRow={this.renderRow}
dataSource={this.state.filterDataSource}
/>
<TouchableOpacity onPress={this.onPress}>
<Text>Get Filter Data</Text>
</TouchableOpacity>
</Modal>
);
}
}
Note this PopoverFilter class also renders a button that when pressed will dump out the current state of the data to the console so you can view it's current form.
Here's an example of how to use the component:
import React from 'react';
import {
AppRegistry,
View
} from 'react-native';
import filterData from './filter';
import PopoverFilter from './PopoverFilter';
class MyApp extends React.Component {
render () {
return (
<View>
<PopoverFilter filterData={filterData.filter.track} />
</View>
);
}
}
AppRegistry.registerComponent('MyApp', () => MyApp);

Related

Get JSON Data in multiple components using reactjs and redux

I would like to show data from a single API to different components as I want to hit the API only once and distribute the data to multiple small components. I know I can do this by using redux state but not sure how to do it. Need your help to achieve this. Below is the code done so far.
homepage/index.js
import SlidingBanner from './banner/BannerList';
import Celebslider from './celebrityslider/CelebSlider';
class HomePage extends Component {
render() {
return (
<div>
<SlidingBanner />
<anotherslider />
</div>
);
}
}
export default HomePage;
BannerList.js
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { itemsFetchData } from '../../../actions/items';
class BannerList extends Component {
componentDidMount() {
this.props.fetchData();
}
render() {
let bannerArray = [];
let banner = this.props.items.banner
for (let key in banner) {
bannerArray.push(banner[key]);
return (
<div>
<Slider {...slidersettings}>
{this.props.items.banner.map((item) => (
<div key={item.id}>
<img src={item.image_url} className="img-responsive"/>
</div>
))}
</Slider>
</div>
);
}
if (this.props.hasErrored) {
return <p>Sorry! There was an error loading the items</p>;
}
if (this.props.isLoading) {
return <p>Loading…</p>;
}
return (null);
}
}
BannerList.propTypes = {
fetchData: PropTypes.func.isRequired,
items: PropTypes.object.isRequired,
hasErrored: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired
};
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: (url) => dispatch(itemsFetchData(url))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(BannerList);
anotherslider.js
Now in this file, i want to fetch another array of objects or object from the same API.
I tried to mount the API in container component but did not worked, I hope i am doing some mistake. Please correct.
If you want to fetch data in anotherslider.js file you must connect reducer to class/function inside it as well as you are making it in BannerList.js file.
Now before render call componentWillReceiveProps(nextProps) function and you will get your data here.
If you want to call data in both of the sliders, you have 2 ways to handle it.
Make your redux requests in HomePage.js component and bind the data to the other components.
When you get the data on BannerList.js component, your state will be updated. Just add the redux connection to your anotherslider.js component and get data when updated.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
};
};
export default connect(mapStateToProps, mapDispatchToProps)(HomeList);
Apart from all these options, you can also use react's Context API as Provider/consumer to distribute your data among small components... this will save you passing props to all small components and directly access the value in component using Context.Consumer .. moreover if you do not want to store this state in global redux store, context API will save you from it...

props showing empty array - React Native

I'm learning React on my own. I have a couple of questions. I'm trying to fetch data to another component. I've tried to setState after mapping JSON data. But it was showing an error “setState is not a function”.
How can I setState after mapping JSON data?
// Fetching data and passing JSON values to TabBarMenu component
export default class App extends Component {
state = {
temp: [],
description: [],
time: []
};
_getFiveWeather = (lat, lng) => {
fetch(`http://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lng}&APPID=${API_KEY}`)
.then(response => response.json())
.then(json => json.list.map(function(item) {
return (
<TabBarMenu key={item.dt_txt} time={item.dt_txt} description={item.weather.description} />
);
})
}}
In TabBarMenu.js, I'm trying to pass the props (time, description) to the _FirstRoute. When I did console.log(this.props), it shows empty array.
How can I grab the props value from the constructor?
I really appreciate your help.
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { TabViewAnimated, TabBar, SceneMap } from 'react-native-tab-view';
export default class TabBarMenu extends Component {
constructor(props) {
super(props);
console.log(this.props) // showing empty array
this.state = {
index: 0,
routes: [
{ key: '1', title: 'Weather' },
{ key: '2', title: 'News' },
{ key: '3', title: 'Photos' },
],
};
}
_FirstRoute = this.props => (
<View style={styles.container}>
<Text style={styles.textStyle}>1</Text>
<Text style={styles.textStyle}>{this.props.description}</Text>
<Text style={styles.textStyle}>{this.props.time}</Text>
<Text style={styles.textStyle}>{this.props.temp}</Text>
</View>
)...
Your _getFiveWeather definition is essentially useless because after fetching data, you are not really doing anything with the mapped components. You just do the map operation and then that's it -- nothing else is happening.
That method that fetches data should be concerned only with that -- fetching data; it should have nothing to do with generating components. You should set the state with the new data after fetching it. Something like this.setState({ ... }) after successful fetching the info.
Then, in the render() method, you verify if there is anything to render. That is, if there is any weather information in your component state properties, you do some sort of mapping to dynamically create the components you need to render.
Nowhere did I see the render() method in your code, so I think you should focus on learning the basics. Start with render(). Don't try to do too many things at once.

React Native, Navigating a prop from one component to another

handleShowMatchFacts = id => {
// console.log('match', id)
return fetch(`http://api.football-api.com/2.0/matches/${id}?Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76`)
.then(res => {
// console.log('match facts', matchFacts)
this.props.navigator.push({
title: 'Match',
component: MatchPage,
passProps: {matchInfo: res}
})
// console.log(res)
})
}
I have this function above, that i want to send matchInfo to matchPage.
I take in that prop as follows below.
'use strict'
import React from 'react'
import { StyleSheet, View, Component, Text, TabBarIOS } from 'react-native'
import Welcome from './welcome.js'
import More from './more.js'
export default class MatchPage extends React.Component {
constructor(props) {
super(props);
}
componentWillMount(){
console.log('mathc facts ' + this.props.matchInfo._bodyInit)
}
render(){
return (
<View>
</View>
)
}
}
All the info I need is in that object - 'this.props.matchInfo._bodyInit'. My problem is that after '._bodyInt', I'm not sure what to put after that. I've tried .id, .venue, and .events, they all console logged as undefined...
You never change props directly in React. You must always change the state via setState and pass state to components as props. This allows React to manage state for you rather than calling things manually.
In the result of your api call, set the component state:
this.setState({
title: 'Match',
component: MatchPage,
matchInfo: res
}
Then pass the state as needed into child components.
render() {
return(
<FooComponent title={this.state.title} matchInfo={this.state.matchInfo} />
);
}
These can then be referenced in the child component as props:
class FooComponent extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
console.log(this.props.title);
console.log(this.props.matchInfo);
// Etc.
}
}
If you need to reference these values inside the component itself, reference state rather than props.
this.state.title;
this.state.matchInfo;
Remember components manage their own state and pass that state as props to children as needed.
assuming you are receiving json object as response , you would need to parse the response before fetching the values.
var resp = JSON.parse(matchInfo);
body = resp['_bodyInit'];

react-native redux accessing / manipulating an object

Hi I have this object and will have do some mathematical/statistical operations on it. Now I have to questions:
How do I access it? For Example I'd like to access numbers[0]['B1']
When I do {this.props.numbers[0]['B1']} I get: Cannot read property B1 of undefined.
If I want to do some calculations on those numbers, where would I put them? From my limited experience with react redux, I know I should not do anything like that in reducers, am I right? Would I create more actions (action creators) to eg. get the average B1 number, or any statistical operations on the numbers, etc. Would it be recommended to use 'reselect' for this kind of tasks?
numReducer
import { LIST_NUMBERS, PICK_NUMBER, GET_DATA } from '../actions/actionTypes';
export default (state = [], action = {}) => {
switch (action.type) {
case LIST_NUMBERS:
return action.payload || [];
case PICK_NUMBER:
return action.payload;
case GET_DATA:
return action.payload;
default:
return state;
}
};
actions:
import { LIST_NUMBERS, PICK_NUMBER, GET_DATA } from './actionTypes';
import dataSet from '../data.json';
export const listNumbers = () => {
const nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
return {
type: LIST_NUMBERS,
payload: nums
};
};
export const getData = () => {
return {
type: GET_DATA,
payload: dataSet
};
};
export const pickNumber = (num) => {
return {
type: PICK_NUMBER,
payload: num
};
};
data.json
[
{
"DrawDate": "22-Mar-17",
"B1": 12,
"B2": 6,
"B3": 11,
"B4": 31,
"B5": 27,
"B6": 19,
"BB": 42,
"BS": 1,
"DrawNumber": 2217
},
{
"DrawDate": "18-Mar-17",
"B1": 26,
"B2": 37,
"B3": 8,
"B4": 3,
"B5": 19,
"B6": 41,
"BB": 43,
"BS": 3,
"DrawNumber": 2216
},
....
Home Container
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { listNumbers, pickNumber, getData } from '../actions/numberActions';
import Home from '../components/Home';
const mapStateToProps = state => ({
numbers: state.numbers
});
const mapDispatchToProps = dispatch => (
bindActionCreators({
listNumbers,
pickNumber,
getData
}, dispatch)
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Home);
Home Component:
import React, { Component } from 'react';
import { View, Text, Button, TextInput } from 'react-native';
export default class Home extends Component {
static navigationOptions = {
title: 'Home Screen',
};
componentDidMount() {
this.props.getData();
}
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Text>####################</Text>
<Text>Intro Screen</Text>
<Text>Number: {this.props.numbers[0]['B1']}</Text>
</View>
);
}
}
EDIT/ADDITION:
As per suggestions below, I've changed the lifecycle method to ComponentWillMount and added a check to see if this.props.numbers is loaded.
import React, { Component } from 'react';
import { View, Text, Button, TextInput } from 'react-native';
export default class Home extends Component {
static navigationOptions = {
title: 'Home Screen',
};
componentWillMount() {
this.props.getData();
}
render() {
if (!this.props.numbers) {
console.log('not yet loaded'); // or a spinner?
}
const { navigate } = this.props.navigation;
return (
<View>
<Text>####################</Text>
<Text>Intro Screen</Text>
<Text>Number: {this.props.numbers[0]['B1']}</Text>
</View>
);
}
}
I still get the same error: Cannot read property 'B1' of undefined. Additionally, the console does not log 'not yet loaded', which would indicate that the numbers object is there - I'm just making an error accessing it.
EDIT2:
import React, { Component } from 'react';
import { View, Text, Button, TextInput } from 'react-native';
export default class Home extends Component {
static navigationOptions = {
title: 'Home Screen',
};
componentWillMount() {
this.props.getData();
}
listNums() {
return this.props.numbers.map((num) => num['B1']);
}
listSingleNum() {
return this.props.numbers[0]['B1'];
}
render() {
if (!this.props.numbers) {
console.log('not yet loaded'); // or a spinner?
} else {
console.log(this.listNums());
}
const { navigate } = this.props.navigation;
return (
<View>
<Text>####################</Text>
<Text>Intro Screen</Text>
<Text>Number: {this.listNums()}</Text>
</View>
);
}
}
So listNums() works fine displaying B1s of each element but if I try to access a single B1 element as in listSingleNum, it throws the error mentioned before: ExceptionsManager.js:63Cannot read property 'B1' of undefined.
How do I access it? For Example I'd like to access numbers[0]['B1'] When I do {this.props.numbers[0]['B1']} I get: Cannot read property B1 of undefined.
It looks like all your react/redux wiring is fine, its just that getData is getting called in componentDidMount so for the first render, the data is not not there yet (see the docs for lifecycle methods order). You can use componentWillMount instead, but I'm still not sure if the data will be available on the first render. To be safe, change the render function to do something different if numbers is undefined (you would have to do this anyway if you ever end up loading the data from a backend somewhere).
NOTE: The following is incorrect - see edit below
render() {
if (!this.props.numbers) {
return null; // or a spinner?
}
const { navigate } = this.props.navigation;
return (
<View>
<Text>####################</Text>
<Text>Intro Screen</Text>
<Text>Number: {this.props.numbers[0]['B1']}</Text>
</View>
);
}
If I want to do some calculations on those numbers, where would I put them? From my limited experience with react redux, I know I should not do anything like that in reducers, am I right? Would I create more actions (action creators) to eg. get the average B1 number, or any statistical operations on the numbers, etc. Would it be recommended to use 'reselect' for this kind of tasks?
This will depend on how intensive the calculations are. If the're pretty cheap, I'd just do them in the render function
import calculateAverage from './somewhere'
...
return (
<View>
<Text>####################</Text>
<Text>Intro Screen</Text>
<Text>Number: {this.props.numbers[0]['B1']}</Text>
<Text>Average: {calculateAverage(this.props.numbers.map((data) => data['B1'])}</Text>
</View>
);
Reselect is a good option if the calculation is expensive so that it doesn't unnecessarily recalculate the values every render. It's also nicer for testing than having the logic in the component itself.
EDIT: Wow... I'm feeling a bit silly at the moment...
this.props.numbers is not undefined (it's defined by the initial state of the reducer). If you check for length it will render (I've replicated all this code and run it myself to be sure this time).
render() {
if (this.props.numbers.length === 0) {
return null; // or a spinner?
}
const { navigate } = this.props.navigation;
return (
<View>
<Text>####################</Text>
<Text>Intro Screen</Text>
<Text>Number: {this.props.numbers[0]['B1']}</Text>
</View>
);
}
It is important to actually return something (or null) within the if statement so that it doesn't hit the undefined value (this.props.numbers[0]).
Explanation (requested in comments)
It all boils down to the component's lifecycle.
When the component mounts it has an empty array, set by the initialState of the reducer
export default (state = [], action = {}) => {
...
};
The mounting lifecycle methods will fire in order. When the componentDidMount (or componentWillMount depending on which update of the question we are at) the state is replaced in the redux store to have the full data set.
After the mounting lifecycle has completed the react-redux will change trigger the props to change, firing off the updating lifecycle methods.
During this stage render is called again, this time with the correct data.
So the component wont "keep re-rendering until the numbers object is not empty", it will re-render whenever the props change, and if the numbers array is not empty, will include desired components.
Returning null is valid in react and is commonly used to prevent components from trying to access props that are not available yet.

How to update another tab`s state

I have two tabs in side a TabBarIOS.
First tab is offer function to add new item into AsyncStorage
Second tab display all item from AsyncStorage
But when i run my app, after adding a new item from First Tab, navigate to second tab i don`t see page re-render, i will need to do a Command+R then i see my new data.
One way to resolve the issue is to read asyncStorage in shouldComponentUpdate, but notice that shouldComponentUpdate will be called constantly by react. While i want to only force update the UI on demand.
So in react native, what is the right way to update state of another UI component?
sample app:
https://drive.google.com/file/d/0B8kAIsj2xDnUMklIQmc0b3NiSVE/view?usp=sharing
Here's what I'm talking about. You will probably want to refactor this a bit once done, but in general here's how I think this would look in the end. I've still a lot to learn about using the lifecycle functions properly, and I'm not sure you'll need them now that I think about it.
appContainer.js
I've essentially removed your NavigatorIOS. I think those were a mistake. Instead, I'm replacing them with the components, passing down the data as props, including a callback function for the button press. I've moved the addData function up a level.
class AppContainer extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'data-list',
dataLen: 0
}
AsyncStorage.getItem("all-data")
.then((data)=>{
if(!data)
{
data = "[]";
}
var dataObj = JSON.parse(data);
this.setState({
dataLen : dataObj.length
});
});
this.addData.bind(this);
}
addData() {
AsyncStorage.getItem("all-data")
.then((data)=>{
if(!data)
{
data = "[]";
}
var dataObj = JSON.parse(data);
dataObj.push({
val: Date.now()
});
AsyncStorage.setItem("all-data", JSON.stringify(dataObj));
this.setState({
dataLen: dataObj.length
});
});
}
render() {
return (
<TabBarIOS style={styles.container}>
<TabBarIOS.Item
title="Add Data"
selected={this.state.selectedTab == 'add-data'}
onPress={()=> this.setState({selectedTab: 'add-data'})}
>
<AddData onButtonPress={this.addData} dataLen={this.state.dataLen} />
</TabBarIOS.Item>
<TabBarIOS.Item
title="Data List"
selected={this.state.selectedTab == 'data-list'}
onPress={()=> this.setState({selectedTab: 'data-list'})}
badge={this.state.dataLen}
>
<DataList dataLen={this.state.dataLen} />
</TabBarIOS.Item>
</TabBarIOS>
);
}
}
addData.js
This will simplify your sub-components significantly...
class AddData extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<Text>{this.props.dataLen}</Text>
<TouchableHighlight
onPress={this.props.onButtonPress}
style={styles.button}>
<Text style={styles.buttonText}>Plus One</Text>
</TouchableHighlight>
</View>
);
}
}
dataList.js
class PlayerList extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<Text>{this.props.dataLen}</Text>
</View>
);
}
}
Use Redux
Have you ever thought about using Redux or something equally?
I had the same problem. Eventually the use of Redux solved it for me.
Official documentation
I highly recommend you the official documentation of Redux.
Example
I will not try to teach you Redux, since the documentation is already really good. But I will try to outline some parts, to give you an idea how to use Redux. I really can't update your existing app, since it's not a trivial tweak. It wouldn't be really sufficient, if the most changes would be cryptic for you.
In your action creators you need to call AsyncStorage and use the values in your reducer to update your state. The important part is the connection of your components with the Redux store. You have to connect it with the component, which you load via NavigatorIOS, since NavigatorIOS doesn't update when passProps update.
<NavigatorIOS
initialRoute={{
component: MyView,
title: 'My View Title',
}}
/>
// MyView Component
...
class MyView extends Component {
...
}
// Prepare the information of the global state and only pass the relevant values as `props` to the component.
function mapStateToProps(state) {
const { isFetching, isError, lastUpdated } = state.posts;
const { entities: posts } = state.posts || { entities: {} };
return {
posts,
isFetching,
isError,
lastUpdated
}
}
export default connect(mapStateToProps)(MyView);