React Native - Cascade state updates from parent to child - javascript

I'm trying to cascade a state change from a parent component down to a child.
export default class App extends React.Component {
constructor(props) {
super(props);
this.listUpdater = new_list_updater();
this.state = {
schoollist: this.listUpdater.update_list(),
}
}
listUpdateCallback = () => {
console.log("Callback triggered!");
console.log(this.state.schoollist.slice(1,3));
this.setState({schoollist: this.listUpdater.update_list()});
console.log(this.state.schoollist.slice(1,3));
}
render() {
return (
<SafeAreaView style={styles.container}>
<Header updateCallback={this.listUpdateCallback}/>
<SchoolList school_list={this.state.schoollist}/>
</SafeAreaView>
);
}
}
listUpdater.update_list() is a method in a class that implements getISchools and ShuffleArray and stores the list of schools that are being shuffled. It returns the shuffled list of schools.
import { shuffleArray } from './Shuffle'
import { getISchools } from './iSchoolData'
class ListUpdater{
constructor() {
console.log("Initiating class!");
this.currentSchoolList = [];
}
update_list() {
console.log("Updating list!");
if (this.currentSchoolList.length == 0){
this.currentSchoolList = getISchools();
}
else{
shuffleArray(this.currentSchoolList);
}
return(this.currentSchoolList);
}
}
export function new_list_updater(){
return new ListUpdater();
}
As far as I can tell everything works. When I press a refresh button in the Header component, it triggers the updateCallback, which updates the list stored in the state variable (verified by logging to console and ComponentDidUpdate()
This is the Component not refreshing:
import React from 'react';
import { StyleSheet, Text, View, SafeAreaView, FlatList } from 'react-native';
export default class SchoolList extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<SafeAreaView style={styles.listArea}>
<FlatList
data = {this.props.school_list}
renderItem = {({item}) =>
<View style={styles.row}>
<View style={styles.num_area}>
<Text style={styles.num_text}>{item.key}</Text>
</View>
<View style={styles.text_area}>
<Text style={styles.univ_text}>{item.univ}</Text>
<Text style={styles.school_text}>{item.school}</Text>
</View>
</View>
}
/>
</SafeAreaView>
);
}
componentDidUpdate(){
console.log("SchooList Updated!");
}
}
The flow I'm expecting is:
Parent passes updateCallback reference to Header (child)
Refresh button in Header triggers updateCallback in Parent
updateCallback in Parent updates state with setState
Parent and relevant children that use state variable re-render, displaying new list
1-3 appear to be working, 4 is not!

Maybe your componenet is not re-rendering when you use setState for some reason. Try adding a warn in the render method to check this. I also noticed you are mutating the array this.currentSchoolList, winch is passade as reference for your state (all objects are passed as refence). Try replaceing this making a copy of the array beforing calling shuffleArray(this.currentSchoolList).
You can copy the array this way (this is ES6 sintax): newArray = [...oldArrray];
Or using other methods.

Related

React Native - Using Props for Modal

I am trying to use modal on my application but I want to separated into different classes as App.js and /components/modal. The problem I encountered is I couldn't pass the props properly. Here is my codes.
I imported modal.
import InfoModal from '../components/InfoModal';
I created state.
state = {modalVisible: false}
The visible function on press.
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
Calling component.
render() {
const { modalVisible } = this.state;
return (
<InfoModal visible= {modalVisible} />
<TouchableOpacity onPress={() => this.setModalVisible(true)} ><Text style={styles.infoButton}>Info</Text></TouchableOpacity>
)}
I didn't understand what prop should I set and how, to work it properly.
Since you have a render method, I assume your App.js is a Class component.
You can create a state on the constructor like that
class App extends React.Component {
constructor(props){
super(props);
this.state = {
modalVisible: false,
}
}
// rest of the class
}
Your function for changing the modal's visibility should be like that
setModalVisible = (visible) => {
this.setState({modalVisible: visible});
}
That's how you control the state on the App class.
And for your modal, you pass App.state.modalVisible as a prop.
<InfoModal visible={this.state.modalVisible} />
When you use setState, all the components receiving that new value as a prop will properly update
use state and method inside Modal component and toggle it by using modal reference.
Put
const { modalVisible } = this.state;
and
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
in InfoModal.js
then use it like this
render() {
return (
<InfoModal ref={(c)= this.infoModal=c }visible= {modalVisible} />
<TouchableOpacity onPress={() => this.infoModal.setModalVisible(true)} ><Text style={styles.infoButton}>Info</Text></TouchableOpacity>
)

React-Native Switch in ListItem is alwaays unchecked

I have a list of objects which I want to show in a FlatList. The list items should also have a switch to select or deselect the entry. But everytime I click the switch, the state stays unchecked. I don't know if this is a bug of the ListItem component or in my code. The onValueChange event is triggered correctly but then it seems that the FlatList is not rerendered after changing the state or the dataset is not updated...
Here are my classes:
class Person {
constructor() {
this._name = '';
this._selected = false;
}
getName() {
return this._name;
}
setName(value) {
this._name = value;
}
isSelected() {
return this._selected;
}
setSelected(value) {
this._selected = value;
}
}
class App extends Component {
constructor(props) {
super(props);
let p1 = new Person();
p1.setName('Superman');
let p2 = new Person();
p2.setName('Batman');
let list = [ p1, p2 ];
this.state = { persons: list };
}
renderItem = ({item, index}) => (
<ListItem
title={item.getName()}
switch={{
value: item.isSelected(),
onValueChange: (value) => {
let list = this.state.persons;
list[index].setSelected(value);
this.setState({ persons: list });
},
}}
/>
);
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.persons}
renderItem={this.renderItem}
/>
</View>
);
}
}
Your code works fine, only the extraData property of the FlatList component is missing.
Checkout the documentation:
By passing extraData={this.state} to FlatList we make sure FlatList
itself will re-render when the state.selected changes. Without setting
this prop, FlatList would not know it needs to re-render any items
because it is also a PureComponent and the prop comparison will not
show any changes.
In your case, it has to be:
<FlatList
data={this.state.persons}
renderItem={this.renderItem}
extraData={this.state}
/>
Here is a working demo.

How to use the variable 'document' in react native?

I am trying to capture all click events outside of my SearchBar component so that I can then tell the dropdown menu to close when one clicks out of it. I looked up examples of how to do this online and I need to use the global variable 'document' in javascript. However, it seems react native does not support this. Does anyone know a work around to use the 'document' variable or a react native equivalent?
class Products extends Component {
constructor(props) {
super(props);
this.setWrapperRef = this.setWrapperRef.bind(this);
this.handleClickOutside = this.handleClickOutside.bind(this);
}
setWrapperRef(node) {
this.wrapperRef = node;
}
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
alert('You clicked outside of me!');
}
}
componentWillMount() {
this.props.dispatch(getProductList());
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside);
}
render() {
const {isLoading, products} = this.props.products;
if (isLoading) {
return <Loader isVisible={true}/>;
}
return (
<View ref={this.setWrapperRef} style={styles.wrapper}>
<Header/>
<View style={styles.bodyWrapper}>
<ScrollView style={styles.scrollView}>
<ProductsContainer data={{productsList: { results: products }}}/>
</ScrollView>
<SearchBar style={styles.searchBar}/>
</View>
<Footer/>
</View>
);
}
}
function mapStateToProps(state) {
const {products} = state;
return {
products
};
}
export default connect(mapStateToProps)(Products);
You can't use document, it's an object on the window. The above answer is incorrect and hasn't taken into account this platform is React Native (answer has since been removed).
To handle click events, you you need to wrap everything in a TouchableWithoutFeedback.
<TouchableWithoutFeedback
onPress={this.hideSearchBar}
/>
I would add a zIndex style to the TouchableWithoutFeedback and one in styles.scrollView. Make sure the zIndex inside of styles.scrollView is more than the one you added to the TouchableWithoutFeedback.

React Native - Trouble passing parent's state to child

a React newbie having some issues passing down states and functions from Parent Component (App in this case) and accessing from Child Components (Main in this case). I'm sure it's one or two really simple mistakes, where am I getting tripped up?
Here is the project structure:
App
|__ Rootstack
|
|__Favorites
|__Main
And here is the stripped down code:
class Main extends React.Component {
render() {
return (
<View>
<Text>{this.props.favoritesList.length}</Text>
<Card>
onSwiped={() => {this.props.updateArray}} //the idea being that on a swipe, updateArray would add 1 to the 'favoriteList' in the parents state, and the favoritesList.length would update by +1.
</Card>
</View>
);
}
}
class Favorites extends React.Component {
....
}
const RootStack = StackNavigator(
{
Main: {
screen: Main},
Favorites: {
screen: Favorites}
},
{
initialRouteName: 'Main'
}
);
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
favoritesList: []
};
this.updateArr = this.updateArr.bind(this); //don't know if this is necessary?
}
updateArr=()=>{this.setState({ favoritesList:
[...this.state.favoritesList, 'new value']})};
render() {
return <RootStack {...this.state} updateArray={this.updateArr}/>;
}
}
Error I'm getting is -- any ideas? Thanks in advance!
1-
This is not correct
<Card ... props should be here!!--- >
onSwiped={() => {this.props.updateArray}} //the idea being that on a swipe, updateArray would add 1 to the 'favoriteList' in the parents state, and the favoritesList.length would update by +1.
</Card>
The correct answer is:
<Card
onSwiped={() => {this.props.updateArray}} //the idea being that on a swipe, updateArray would add 1 to the 'favoriteList' in the parents state, and the favoritesList.length would update by +1.
>
</Card>
The meaning of the error is that the child of Card is expected to be an object and not .....
2-
this.updateArr = this.updateArr.bind(this); is not necessary since updateArr = () => ... is written in es6

How to call navigate from a component rendered at top level

According to the docs on react-navigation you can call navigate from the top level component using the following:
import { NavigationActions } from 'react-navigation';
const AppNavigator = StackNavigator(SomeAppRouteConfigs);
class App extends React.Component {
someEvent() {
// call navigate for AppNavigator here:
this.navigator && this.navigator.dispatch(
NavigationActions.navigate({ routeName: someRouteName })
);
}
render() {
return (
<AppNavigator ref={nav => { this.navigator = nav; }} />
);
}
}
However I'm trying to figure out how can this be done if the logic that does the dispatch is in another component that is rendered on the same level as the navigator? In my case I create my navigators (A drawer with a stack navigator and other nested navigators) and then I render them using the <Drawer>. On the same level I'm loading my <PushController> component to handle push notifications. The pushcontroller actually gets the event that I want to dispatch on.
I can't figure out how to pass(?) the ref to the pushcontroller component so I can use it, currently the following isn't working. I get the console log telling me that the fcm.ACTION.OPEN_NOTIFICATION triggered but no dispatch occurs. I suppose it could be because the ref is created during a render and it isn't available to pass yet when the render occurs? But I'm also not sure you would do things this way in order to give another component access to a ref declared at the same level. Thanks for your help in advance!
Drawer + PushController rendering
render(){
return(
<View style={{flex: 1}}>
<Drawer ref={nav => { this.navigator = nav; }}/>
<PushController user={this.props.user} navigator={this.navigator}/>
</View>
)
}
PushController snippet:
import { NavigationActions } from 'react-navigation';
async doFCM() {
FCM.getInitialNotification().then(notif => {
console.log('Initial Notification', notif);
if(notif.fcm.action === "fcm.ACTION.OPEN_NOTIFICATION"){
console.log('fcm.ACTION.OPEN_NOTIFICATION triggered', notif);
this.props.navigator && this.props.navigator.dispatch(NavigationActions.navigate({routename: 'Chat'}))
}
});
}
Answer was to move the navigate call to a function defined at render and pass it to the component.
import { NavigationActions } from 'react-navigation';
...
//class definition omitted for brevity
render(){
const callNavigate = (routeName, params) => {
console.log('Params', params);
this.navigator.dispatch({
type: NavigationActions.NAVIGATE,
routeName: routeName,
params: params
})
}
return(
<View style={{flex: 1}}>
<Drawer ref={nav => this.navigator = nav }/>
<PushController callNavigate={callNavigate}/>
</View>
)
}
The function is called within PushController like this:
this.props.callNavigate('RouteName', params);

Categories

Resources