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);
Related
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...
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>
)
}
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'];
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);
I have a JSON object from a script tag like so:
<script type="text/json" id="json-data">
{'someData': 'Lorem ipsum...'}
</script>
I would like to be able to pull this information and use it within a React component in my render method.
The issue seems to be that I need to set this to a variable within componentWillMount:
export default MyReactComponent extends Component {
componentWillMount() {
const test = document.getElementById('json-data').innerHTML;
}
render() {
return (
<div>
// This is where I would like to use this data.
</div>
);
}
}
Is this the best way to handle passing this data? If so, how can I access this data within the render method of my component?
Thanks!
Store it in the component's state. The render method should only depend this.state and this.props
At the risk of oversimplifying:
this.props are passed from parent components
this.state is state that is internal to the component
Example
export default MyReactComponent extends Component {
componentDidMount() {
this.setState({
test: JSON.parse(document.getElementById('json-data').innerHTML)
});
}
render() {
return <div>{this.state.test}</div>;
},
getInitialState: function() {
return {test: {}}
}
}