React native updating multiple child component states - react-native

I am making a minesweeper app in react native as a personal project to try and learn the concepts. The problem I am having is trying to update multiple children objects at the same time. I'm not sure if this is the best practice.
I have 3 components Game > Grid > cell. The Game component takes care of the logic of the game such as timer/win condition/lose condition. The grid is a collection of cells and determines what each cell should be.
Game component:
render() {
return (
<Grid
action={this.props.action}
firstClick={this.state.firstClick}
/>
);
}
I pass the state firstClick to the grid component which is equal to true. meaning when I tap the first cell I want it to reveal all surrounding neighbors while first click is equal to true.
Grid Component:
_renderItem = ({ item }) => (
<Cell
item={item}
neighbours={this.state.neighbours[item.key.substring(0,1)][item.key.substring(1,2)]}
firstClick={this.props.firstClick}
/>
);
render() {
return (
<FlatList
data={this.state.flatGrid}
renderItem={this._renderItem}
keyExtractor={this._keyExtractor}
numColumns={10}
/>
);
}
The grid is a flatlist of cells and the grid knows the neighboring cells of each cell when its tapped. The problem I am having is I'm not sure where I should update the state of the tapped cell and its neighboring cells. I would like the isHidden state to be set to false for the cell plus all of its neighbors.
Should it be handled in the grid component or cells component? I'm not sure how to tackle this without breaking encapsulation.
These are the states in my cell Component
constructor(props) {
super(props);
this.state = {
isFlagged: false,
isHidden: true,
value: '',
id: ''
};
Thanks in advance!

Here is something which can make life easier for you.
First of all, your cell should be a PureComponent and you should never store the state in your cell. The sole reason being the cell will be reused at some point and your entire screen might start behaving randomly. You can store the state of each cell model in the data source.
Finally, you should pass a callback function as props to your cell. When the cell is tapped, then calculate the neighboring cells in your Grid component. When this is calculated, then change the appropriate cell models accordingly.
Example
class Grid extends React.Component {
state = {
dataSource: [{
isFlagged: false,
isHidden: true,
value: '1',
id: '1',
}, {
isFlagged: false,
isHidden: true,
value: '2',
id: '2',
}],
};
onCellClicked = (item) => {
// Find neighbouring items and replace the modified items in the state in this function
}
_renderItem = (item) => {
return <Cell onClick={() => this.onCellClicked(item)} item={item}>;
}
render() {
return (
<FlatList data={this.state.dataSource} renderItem={this._renderItem}/>
);
}
}

Related

How to allow text input in list item using react native?

This question relates to React Native specifically the text input component. I wanted to create an app which allows users to select a number of people from their contacts and based on their selection, create a list with text input each beside them. From there, they are able to do text input and with the onChangeText function, changes the state of a particular key.
Below is the code that I have attempted. I tried to change the state of percent inside the prop which I believe is incorrect since all elements will share the same text input.
By doing so, whenever I made a text input to one field, the text input will erase subsequently.
constructor(props) {
super(props);
this.state = {
selected2: undefined,
description: "",
amount: "",
notes: "",
percent: {},
selectedContacts:
this.props.navigation.state.params.selectedContacts,
};
}
const SelectedList = (props) => {
const list = ({ allContacts }) => {
if (allContacts) {
return allContacts.map((item) => {
return (
<ListItem key={item.id}>
<Body>
<Text>{`${item.first_name}`}</Text>
<Text note>{`${item.phone_number}`}</Text>
</Body>
<Right>
<Item>
<Input
placeholder="0"
maxLength={3}
onChangeText={(percent) => {
this.setState({percent});
}}
/>
<Text>%</Text>
</Item>
</Right>
</ListItem>
)
})
}
}
<SelectedList
allContacts={this.state.selectedContacts}
/>
I hope to achieve is that after selecting the number of contacts and transferring the array data, I want the array to be printed out and the text input of each list item to be independent of each other.
Any feedback and advice are welcome and I really appreciate for you to spending time reading my query and helping me with my problem. Thank you!
I think its about your logic.. something like this should work. make 'percent' type to array and in onChangeText change specific item
this.state = {
...
percent: [],
...
}
...
return allContacts.map((item,index) => {
...
onChangeText={(someText) => {
var {percent} = this.state;
percent[index] = someText;
this.setState({percent});
}}

How to build long scrollable content views in React Native?

For the case of this question let's say I'm building Medium as a React Native app, specifically the screen where you read a story.
This is a long screen, with different types of content appearing as you scroll down. The title, the author information, the content (paragraphs, images, videos, etc), some meta information at the end, social features like comments, etc.
What's the recommended way to build a view like this? ScrollView doesn't seem performant enough especially if there were videos or other media types that needed to be loaded within the content. ListViews seem like they are the more performant option but don't seem like they are designed for this use-case.
Has anyone else faced this challenge? What's the best way to approach it?
<ScrollView> renders all its children first. So, let say you have thousands of elements(medium articles), <ScrollView> will first render all of them, which is pretty much slow and is visible.
For these cases, you can use <FlatList>, which renders the child when it enters the viewport. So, it is performant.
You can refer <FlatList> documentation here: https://facebook.github.io/react-native/docs/flatlist
I had a similar problem for one screen in my app where there were various categories of components involved, for my case <FlatList> worked very well, performance was also up-to the mark.
Solution
Example.js
import React, { Component } from "react";
import { FlatList } from "react-native";
export default class Example extends Component {
constructor(props) {
super(props);
this.state = {
showProgress: false,
content: [{}, {}, {}] // list of objects
};
}
onRefresh() {
this.setState({ showProgress: true });
// call methods here to load new data, once loaded make sure to reset the loading state to false
this.setState({ showProgress: false });
}
showParagraph() {
return <React.Fragment>{/* Your paragraph */}</React.Fragment>;
}
showVideo() {
return <React.Fragment>{/* Your Videos */}</React.Fragment>;
}
showAudio() {
return <React.Fragment>{/* Your Audio */}</React.Fragment>;
}
renderItem(item) {
return (
<React.Fragment>
{item.isVideo ? showVideo() : showParagraph()}
</React.Fragment>
);
}
NoDataMessage() {
return <React.Fragment>{/* Empty screen message goes here */}</React.Fragment>;
}
render() {
return (
<FlatList
data={this.state.content}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => this.renderItem(item)}
onRefresh={() => this.onRefresh()}
refreshing={this.state.showProgress}
ListEmptyComponent={this.NoDataMessage()}
/>
);
}
}
read more about props from here, also you can make an infinite news feed like facebook home page check this React native infinite scroll with flatlist
hope this helps..!

React Native ios Switch in FlatList not toggling after value changed

I am trying to toggle ios Switch in react native. But the switch comes back to initial position as soon as I change it.
What I have:
class ABC extends Component {
constructor(props) {
super(props)
this.state = {
obj: []
}
}
fetch(){
// fetch something from remote server, set it to state object array
}
setStatus(id, value){
var temp = [...this.state.obj]
temp.map((t) => {
if (t.id == id) {
t.flag = value
}
})
this.setState({ obj: temp })
}
render() {
return (
<View>
<FlatList
data={this.state.obj}
renderItem={({ item }) =>
<View>
<Text>{item.name}</Text>
<Switch
onValueChange={(val) => this.setStatus(item.id, val)}
value={item.flag}
/>
</View>
}
keyExtractor={({ id }, index) => id.toString()}
/>
</View>
);
}
}
I logged the before and after value of obj state and they seem to update. Should the FlatList be rendered again (like a web page refresh) ? Or is there something I am missing ? Searched SO for answers, couldn't find my mistake.
Flatlist has a prop called extraData.
This prop tells Flatlist whether to re-render or not.
If data in extraData changes then flatlist re-renders based on new data provided in data prop.
So whenever you need to re-render flatlist just change something in extraData.
Best way is to pass state toextraData which is passed to Data.
So, just pass extraData={this.state.obj}.
there also other way called forceUpdate.
you can call this.forceUpdate().
but this is not recommended because this will render not only flatlist but entire component in which you are calling this.

Pass data through screens in React Native

I am new one in React Native. I have to make an app with google map and make possible to change map type from other screen. I am using TabNavigator to switch between screens.
Qestion is how to change map type by switching a radio button in other screen.
This is my HomeScreen map component:
<View style={styles.container}>
<MapView
mapType={'standard'}
provider={this.props.provider}
style={styles.map}
initialRegion={this.state.region}
onPress={e => this.onPress(e)}
>
I want to pass a mapType from another screen with radio buttons
const MAP_TYPES = {
STANDARD: 'standard',
SATELLITE: 'satellite',
HYBRID: 'hybrid',
TERRAIN: 'terrain',
NONE: 'none',};
var radio_props = [
{label: MAP_TYPES.STANDARD, value: MAP_TYPES.STANDARD },
{label: MAP_TYPES.SATELLITE, value: MAP_TYPES.SATELLITE }];
export default class LinksScreen extends React.Component {
static navigationOptions = {
title: 'Options',
};
onPress(value) {
this.setState({value:value});
/*
Here I want to send value to HomeScreen but I don't know how =(
*/
console.log(value);
}
render() {
return (
<ScrollView style={styles.container}>
<RadioForm
radio_props={radio_props}
initial={0}
onPress={(value) => {this.onPress(value)}}
/>
</ScrollView>
);
}
}
App Images
This is my home screen with map
This is my switch
There are two common techniques for this.
Use a higher order component to maintain the state of the map type, and pass it down to child components as a prop. Also pass down a function which can be used to update the state in the higher order component.
Use a system such as redux to create a single state which you can read and update from anywhere in your application.
It’s a matter of personal preference which method you choose, though if the app is going to get more complex over time, some global state management such as redux may be a good idea to use early on.

Reacting to a component being scrolled off screen in a react-native ScrollView

Currently I am using a <ScrollView /> component to handle the scrolling for my items, as I will only ever have a maximum of three items I feel this is appropriate instead of introducing a <FlatList />. This component receives a prop called collapsed and onCollapseToggle which is used to modify the collapse prop that is passed to the child. I have also experimented with the child having it's collapsed variable in state, but it seems that modifying the child's state from the parent would be near-impossible.
When scrolling through the <ScrollView /> when a component is passed up (The user scrolls down far enough that the component is no longer displayed on the screen) I want to execute a function that could potentially change the collapsed value that's passed to the item being rendered. This way if a user as expanded an item to view more information about it, and then continues scrolling down the <ScrollView /> the item would be self-collapsing, without the user having the manually close it through some form of input.
I'm not currently sure about any way to go about this, and any help would be greatly appreciated. I will provide an example of the structure that I am working with, which may help someone come up with a solution. I do not mind restructuring my components.
class ContentInformation extends React.Component {
state = { content: [ ... ] };
onCollapseToggle = (index, displayed=true) => {
const { content } = this.state;
const arr = content.slice();
const item = arr[index];
if(!item) return;
if(!displayed) {
if(!item.collapsed) {
item.collapsed = true;
}
} else {
item.collapsed = !item.collapsed;
}
arr[index] = item;
this.setState({ content: arr });
}
render() {
return (
<ScrollView>
{ this.state.content.map((content, index) => (
<Item
key={content._id}
index={index}
onCollapseToggle={this.onCollapseToggle}
{...content} />
); }
</ScrollView>
);
}
}
So basically, as you can see, I only need to figure out when the <Item /> goes off-screen so I can call onCollapseToggle(index, false) to automatically re-collapse the component if it's open.
This is an example of how you can detect when an item is offscreen when scrolling.
state = { toggleDistance: 0 }
_handleScroll({nativeEvent: {contentOffset: {y}}}) {
const {toggleDistance} = this.state;
if (y >= toggleDistance) {
// The item is offscreen
}
}
render() {
<ScrollView
onScroll={this._handleScroll.bind(this)}>
<Item
onLayout={({nativeEvent: {layout: {y, height}}}) => this.setState({toggleDistance: (y + height)})}/>
...
</ScrollView>
}
Seems to me you need a FlatList or a ListView and ScrollView simply doesn't support this use case.
The hint is in the ScrollView description:
FlatList is also handy if you want ... any number of other features it supports out of the box.
I solve the problem improving the answer from ivillamil with some adjusts:
const [toggleDistance, setToggleDistance] = useState(0);
const [showBottom, setShowBottom] = useState(false);
const onScrollMoveFooter = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
const currentOffset = event.nativeEvent.contentOffset.y;
if (event.nativeEvent.layoutMeasurement.height - currentOffset <
toggleDistance) {
setShowBottom(true);
} else {
setShowBottom(false);
}
};
And in the another component:
<Component
onLayout={({
nativeEvent: {
layout: { y, height },
},
}) => setToggleDistance(y + height)}
/>
and so I can conditionally hide what I wanted:
{showBottom && (<AnotherComponent/>)}