react-native redux accessing / manipulating an object - json

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.

Related

React-Native: Parsing Simple JSON GET/POST and Refresh every X Seconds

Get data from JSON Response and populate a field and refresh every X Seconds. It doesn't have to be a background task, just while the screen is active.
The responses would be: res.name and res.host. It would also load the current image: res.imgurl
I have tried the code below, but the examples on the react site are quite complex and use lists, I just have simple JSON data, no array.
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
fetch("https://broadcast.truthnetwork.com/play-currentshow.lasso?station=WTRU")
.then(res => res.json())
.then(res => {
console.log("Server response :- \n", res);
})
.catch(error => {
console.log("Error from server :- \n", error);
});
return (
<View style={styles.container}>
<Text style={styles.sometext}>Show Name:</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#375963',
alignItems: 'center',
justifyContent: 'center',
},
sometext: {
color: '#ffffff'
}
});
Here is a sample JSON Response:
{
"name": "Encouraging Word",
"imgurl": "https://broadcast.truthnetwork.com/_img/shows300/4B0FA4EA116331D9A1WH3AE062F0.jpg",
"description": "This is a simple description",
"slug": "encouraging-word-don-wilton",
"weburl": "http://www.theencouragingword.org/",
"feedurl": "http://feeds.feedburner.com/tewfbs",
"host": "Don Wilton",
"showid": "69"
}
I expect app to pull JSON and show the current show, show image and show host and refresh the data every 15-30 seconds (no background task is necessary, just while the screen is active).
Note: This API will work with GET OR POST on station=WTRU
The following approach might come in handy. The following example fetches data from an API every 5 seconds using JavaScript's setInterval. We are making the request once the component first renders, in the componentDidMount method and removing the interval once the component unmounts inside componentWillUnmount.
You need to use state manipulation to update your screen after each api request after every x seconds. Since each state update causes a re-render.
Sandbox Link: https://codesandbox.io/s/falling-pine-81hx4?fontsize=14
Read more about setInterval here: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
data: {}
}
componentDidMount() {
this.getData();
this.interval = setInterval(() => {
this.getData();
}, 5000);
}
getData = () => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => {
console.log(json);
this.setState({ data: json })
})
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return (
<div className="App">
<p>{this.state.data.title}</p>
</div>
);
}
}

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.

Calling a function when opening a react-native screen

I'm trying to load a JSON from AsyncStorage every time a user opens one of my react-native screens (I'm using StackNavigator). This JSON contains information on what my states should be set to.
How can I call a function that runs every time this screen is opened?
Further info:
I've written a function that updates my states according to a JSON loaded from AsyncStorage. The function works perfectly when called from a button, but when the function is called from render(), part of my screen freezes and some buttons are not touchable anymore. Strangely only TextInput still works.
use componentWillMount() method. This will execute automatically before render() method gets triggered.
class Sample extends Component{
state = {data : []};
componentWillMount(){
this.setState({data : inputObject});
}
render(){
return(
<View>
//you can render the data here
</View>
);
}
}
import { useEffect, useState } from 'react';
const Sample = () => {
const [state, setState] = useState([]);
useEffect(() => {
setState(inputObject);
}, [])
return(
<View>
//you can render the data here
</View>
);
}
Reference: https://facebook.github.io/react/docs/react-component.html#componentwillmount
If you want to handle back button page navigation then you need to listen to the
navigation event once when the component has mounted, use the code below for the same.
componentDidMount = () => {
this.focusListener = this.props.navigation.addListener('focus',
() => {
console.log('focus is called');
//your logic here.
}
);
}
This can be easily accomplished using 'withNavigationFocus' , found in the react native documentation here
import React, { Component } from 'react';
import { View } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
class TabScreen extends Component {
componentDidUpdate(prevProps) {
if (prevProps.isFocused !== this.props.isFocused) {
// Use the `this.props.isFocused` boolean
// Call any action
}
}
render() {
return <View />;
}
}
// withNavigationFocus returns a component that wraps TabScreen and passes
// in the navigation prop
export default withNavigationFocus(TabScreen);
You could use a hook approach:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
I literally just copied the first example of the documentation, but it's a very good one.
If you want continue reading: https://reactjs.org/docs/hooks-effect.html
I used "onLayout" method inside the view.
read the doc
onLayout: Invoked on mount and on layout changes.
export default function Login({ navigation }) {
const executeOnLoad = () => {
console.log("view has loaded!");
};
return (
<View style={styles.container} onLayout={executeOnLoad}>
--- layout code here
</View>
);
}
Since you are dealing with the screen, I will suggest you use useFocusEffect hooks.
example:
const ExampleScreen = () => {
// your code here
useFocusEffect(useCallback(() => {
// your logic goes here
}, []))
return (
<View>
{/* render your content here */}
</View>
)
}

React-Native - Dynamic State from JSON for Switch

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);