I have images associated with a counter and based on this increment or decrement in counter, a calculation is done and displayed in a text at the bottom.
The problem is that when I render, the images get rendered again and are loaded again and again and again. which I dont want.
If I dont render, the text will not update with the calculated amount.
For the counter I am using react-native-counter.
I have already tried with shouldcomponentupdate, but I want to stop only image rendering, the rest should work.
Please advise.
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Header
backgroundColor="#25D366"
leftComponent={
<Icon
name="menu"
size={40}
color={"#fff000"}
onPress={() => this.props.navigation.openDrawer()}
/>
}
centerComponent={{
text: "Veg & Fruits",
style: {
color: "#ffffff",
fontSize: 25,
fontWeight: "bold",
},
}}
rightComponent={<Icon name="home" color={"#ff0000"} />}
></Header>
/// this is searchbar component,
<SearchBar
fontColor="#ffffff"
fontWeight="bold"
fontSize={20}
iconColor="#c6c6c6"
shadowColor="#ffffff"
cancelIconColor="#c6c6c6"
backgroundColor="#25D366"
placeholder="Search here"
onChangeText={(text) => {
this.setState({ photos: [] });
this.state.search = text;
this.filterList(this.state.search);
console.log("text changed");
}}
onPressCancel={(text) => {
text = "";
//this.filterList(text);
}}
onPress={(text) => {
console.log("rendering");
console.log("now text is: ", this.state.search);
}}
/>
/// in this view images are displayed using functions
<View>
<ScrollView
style={{
height: Dimensions.get("window").height - 200,
}}
>
<View
key={Date.now()}
style={{
flex: 1,
flexDirection: "column",
flexWrap: "wrap",
}}
>
{this.filterList(this.state.search)}
{this._renderImages()}
</View>
</ScrollView>
<CalcText tt={total_num} />
</View>
</div>
);
}
}
class CalcText extends Component {
constructor(props) {
super(props);
this.state = {
ta: 0,
};
}
shouldComponentUpdate(nextProps) {
console.log(nextProps.tt);
if (this.props.tt !== nextProps.tt) {
console.log("changed");
return true;
} else {
console.log("Not changed");
return false;
}
}
render() {
return (
<View style={{ height: 40, backgroundcolor: "ff0000" }}>
<Text style={{ fontSize: 26, fontWeight: "bold" }}>
Total : {this.props.tt}
</Text>
</View>
);
}
}
You can create a TextBox component and split it from ImageComponent. In this way the images will not be bound to the state of the component rendering text, and you can safely change TextBox state and text preventing ImageComponent to re-render.
Edit:
Okei, now i get it. I think you have no possibility to do it like this.
Let's formalize the problem:
<Parent>
<Images calc={functionToCalc} />
<CalcText totalAmount={this.state.totalAmount}/>
</Parent>
functionToCalc is defined in in <Parent> and update parent state, something like:
const funcToCalc = () => {
// when condition occurs
this.setState({
totalAmount : computedAmount
})
}
The state of <Parent> has:
this.state : {
totalAmount: none
}
Whenever condition (buttonClick?) occurs in <Images/> you run functionToCalc update <Parent> state and rerender <CalcText>. Problem here is that also <Images> will be rerender again as all the parent component will be rerender.
this is one of the way to pass info from siblings in React.
You only have a possibility if you want to prevent <Images> rerendering.
Redux or similar libraries for centralize state
You will move the computed calculation in a Store and <CalcText/> will read that from there. <Images/> component will trigger an action modifying that state but won't listen to that so not being rerender.
Related
class ContentPage extends Component {
constructor(){
super()
this.state = {
data: '',
count: 0 ,
}
this.handleClick = this.handleClick.bind(this)
this.deletehandle = this.deletehandle.bind(this)
}
handleClick(){
this.setState(prevState => {
return {
count: prevState.count + 1
}
})
}
deletehandle(){
if (this.state.count === 0) {
this.setState({
counter: 0
});
} else {
this.setState(prevState => ({
count: this.state.count - 1
}));
}
}
this is my increment and decrement code on first page and after on press handleclicked it increases and the state value i want to show is on next page on icon
i have used
{this.state.count}
</View>
<View style={{alignItems:'flex-end',alignContent:'flex-end',marginLeft:260}}>
<View style={{flexDirection:'row'}}>
<Text style={{fontSize: 18}}> Quantity : </Text><Text style={{fontSize:18}}>{this.state.count}</Text>
</View>
</View>
<View style={{height: 4, width: 30}} />
<TouchableOpacity
// onPress={this._AddCart}
onPress={this.handleClick}
style={{
height: 40,
width: 130,
backgroundColor: 'orange',
justifyContent: 'center',
alignItems: 'center',
borderRadius:8
}}>
<Text style={{color:"#FFFFFF",fontSize:15}} >Add to Cart{this.state.count}</Text>
</TouchableOpacity>
edited my code now showing my count state in the same page but i want to show my count numbers on next screen ?
This example is just focusing on how to pass the data as props from the parent to the child component. So please ignores the syntax and other issues.
// Parent Component
class ParentComponent extends Component {
// Pass count to Child Component as props
<ChildComponent count={this.state.count}/>
}
// Child Component
class ChildComponent extends Component {
// call count in child component using props
this.props.count
}
You need to pass your count property down to the component where you want to use it (exactly like #Sourav Singh has shown in his answer). I composed an increment/decrement example for you, hope you find it helpful:
https://codesandbox.io/s/react-typescript-429re
Also, you might find some interesting insights in Components and Props article from official React documentation.
I have a jobs app. In my JobsComponent, where I display the jobs, I have added a sort option that allows users to sort the list of jobs by different criteria. The flow is this: 1) I get the jobs from the server -> 2) the user sorts the jobs -> 3) the sorted list of jobs is re-rendered on the screen.
The problem is that step 3) is not working. The actual list of jobs is being sorted (I can see that in the logs), but my FlatList is not being re-rendered.
What I have tried
I have a flag, sortOrderChanged, set in my state. Whenever the user selects a sorting option, I change this flag in my componentDidMount() method:
this.setState({
sortOrderChanged: !this.state.sortOrderChanged,
selectedSortOrder: dataFromChild
});
and pass it to FlatList as extraData:
<FlatList
data={sort_array}
extraData={props.sortOrderChanged}
renderItem={renderJobItem}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
This does not help though. I have also tried sending the whole state to the FlatList and passing it to extraData, but it also didn't work. I assume the problem is that my data is not actually being changed, but sorted. However, I do not know how to force it to re-render. Can someone help me out, please?
Below is my JobsComponent.js:
function RenderJobs(props) {
var json = JSON.parse(props.jobsData);
var sort_array = [];
for (var _id in json) {
sort_array.push({
_id:_id,
jobtitle: json[_id].jobtitle,
company: json[_id].company,
duration_driving_value:json[_id].duration_driving.value,
duration_transit_value: json[_id].duration_transit.value,
duration_walking_value: json[_id].duration_walking.value,
duration_driving:json[_id].duration_driving.text,
duration_transit:json[_id].duration_transit.text,
duration_walking:json[_id].duration_walking.text,
date: json[_id].date,
formatedDescription: json[_id].formatedDescription,
applyUrl: json[_id].applyUrl
});
}
//sort the list based on user selection
if (props.sortOrder === props.sortArray[0]) {
sort_array.sort(function(x,y){return new Date(y.date) - new Date(x.date)});
}
else if (props.sortOrder === props.sortArray[1]) {
sort_array.sort(function(x,y){return x.duration_driving_value - y.duration_driving_value});
}
else if (props.sortOrder === props.sortArray[2]) {
sort_array.sort(function(x,y){return x.duration_transit_value - y.duration_transit_value});
}
else {
sort_array.sort(function(x,y){return x.duration_walking_value - y.duration_walking_value});
}
const renderJobItem = ({item}) => {
var durationCarApi, durationPublicTransportApi, durationWalkApi, formattedApiDate, formattedJobDescription;
//format data
return (
<Panel //custom component used to display each job
jobTitle={item.jobtitle}
company={item.company}
durationCar={durationCarApi}
durationTram={durationPublicTransportApi}
durationWalking={durationWalkApi}
dateAdded={formattedApiDate}
onPress={() =>
{
props.navigation.navigate('JobDetails', {
jobTitle: item.jobtitle,
company: item.company,
durationCar: durationCarApi,
durationTram: durationPublicTransportApi,
durationWalking: durationWalkApi,
jobDescription: formattedJobDescription,
applyUrl: item.applyUrl
})
}
}/>
);
}
//handle loading/error scenarios
return (
<FlatList
data={sort_array}
extraData={props.sortOrderChanged}
renderItem={renderJobItem}
keyExtractor={(item, index) => index.toString()}
style={{marginTop: 10}}
/>
);
}
class Jobs extends Component {
constructor(props) {
super(props);
this.state = {
jobTitle: this.props.navigation.getParam('jobTitle', ''),
address: this.props.navigation.getParam('address', 'error'),
sortOrderChanged: false,
sortArray: [0,1,2,3],
selectedSortOrder: 1 //default is sort_driving
};
}
componentDidMount() {
handleSorting = (dataFromChild) => {
console.log('Sort order clicked: ' + dataFromChild);
this.RBSheet.close();
this.setState({
sortOrderChanged: !this.state.sortOrderChanged,
selectedSortOrder: dataFromChild
});
}
render() {
return(
<ScrollView contentContainerStyle={styles.bkg}>
<RenderJobs
jobsData={JSON.stringify(this.props.jobs.jobs)}
isLoading={this.props.jobs.isLoading}
errMess={this.props.jobs.errMess}
navigation={this.props.navigation}
sortOrder={this.state.selectedSortOrder}
sortArray={this.state.sortArray}
sortOrderChanged={this.state.sortOrderChanged}
/>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<RBSheet //custom component used to render the sorting options
ref={ref => {this.RBSheet = ref;}}
height={200}
duration={250}
customStyles={{
container: {
justifyContent: "center",
alignItems: "center"
}
}}>
<SortSheet //this is the child component used to render the sorting options
sortOrder={this.handleSorting}
sortArray={this.state.sortArray}/>
</RBSheet>
</View>
</ScrollView>
)
}
}
Move your data from local variable to state.
Or add forceUpdate after else.
The solution was to change the keyExtractor to item._id:
<FlatList
data={props.jobsData}
extraData={props.sortOrderProps}
renderItem={renderJobItem}
keyExtractor={(item, index) => item._id}
style={{marginTop: 10}}
/>
I am presenting a component using Modal in react-native. So in the Child component(the component that is presented on top of parent as Modal) I am trying to update the state variable of parent, but it is throwing error like "'changeModalVisibility' is missing in props validation".
Kindly help me to solve this.
Related Code is below:
// Parent Class in render
<SafeAreaView style={styles.container}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.isModalVisible}
onRequestClose={() => { this.changeModalVisibility(false) }}
>
<ChildClass
changeModalVisibility={this.changeModalVisibility}
/>
</Modal>
</SafeAreaView>
// Outside Render function
changeModalVisibility = (bool) => {
this.setState({ isModalVisible: visible });
}
// In Child Class
<TouchableHighlight
underlayColor="none"
onPress={this.props.closeButtonTapped}
style={{ alignItems: 'center', justifyContent: 'center', }}
>
<Text style={{
color: 'white',
marginTop: 10,
marginLeft: 20,
fontWeight: '600',
fontSize: 18,
}}
>Close
</Text>
</TouchableHighlight>
closeButtonTapped() {
this.props.changeModalVisibility //"**'changeModalVisibility' is missing in props validation**"
}
Your Child component in parent component has changeModalVisibility prop.
You should call
this.props.changeModalVisibility(true) or this.props.changeModalVisibility(false)
inside child component. Make sure to use arrow function when you want to execute function from prop :
changeModalVisibility={this.changeModalVisibility}
changeModalVisibility = (visible) => {
this.setState({ isModalVisible: visible });
}
In child component onPress props should be like this:
onPress={() => this.closeButtonTapped()}
I would like to toggle only one switch in the render item I am creating. So far when I toggle, all buttons toggle and I understand it's because I'm passing the same state to the value of all my switches but I don't know how to target just one
I have tried using other native components but none seem to be able to work like an input where I can pass an event and target it
state = {
containers: dataYay,
selectedContainers: [],
checked: false
}
containerSelected(value, item, index) {
console.log(item.Nbr, item.CtrId)
this.setState({ checked: value })
}
renderItem={({ item, index, section }) => {
return (
<Block padding={[10, 15, 20, 15]} color='#EFF8FF' margin={[0, 0, 1, 0]} row center middle key={index}>
<Block styles={styles.ContainerAssign} flex={0.3}>
<Switch
style={{ transform: [{ scaleX: .8 }, { scaleY: .8 }] }}
value={this.state.checked} // i would like to check just one of these
testID={index}
onValueChange={(value) => this.containerSelected(value, item, index)}
/>
</Block>
<Block styles={styles.ContainerInfo} padding={[0, 5, 0, 0]}>
<Text style={styles.ContainerInfoText}>
<Text style={styles.ContainerInfoTitle}> Container ID: </Text> {item.CtrId}
</Text>
... // code would be too long to displa
</Block>
<Button
... // code would be too long to display
</Button>
</Block>
)
}}
What I need help with is to find a way to pass the switch toggle to the current item on click?
Solution
Make your item to state component through React.Component class.
For example,
class Item extend React.Component {
state = {
checked: false,
}
render() {
return() {
<Block padding={[10, 15, 20, 15]} color='#EFF8FF' margin={[0, 0, 1, 0]} row center middle key={index}>
<Block styles={styles.ContainerAssign} flex={0.3}>
<Switch
style={{ transform: [{ scaleX: .8 }, { scaleY: .8 }] }}
value={this.state.checked} // i would like to check just one of these
testID={index}
onValueChange={(value) => this.containerSelected(value, item, index)}
/>
</Block>
<Block styles={styles.ContainerInfo} padding={[0, 5, 0, 0]}>
<Text style={styles.ContainerInfoText}>
<Text style={styles.ContainerInfoTitle}> Container ID: </Text> {item.CtrId}
</Text>
... // code would be too long to displa
</Block>
<Button
... // code would be too long to display
</Button>
</Block>
}
}
}
And use this item in your renderItem of parent.
renderItem = {({item}) => <Item item={item}/>}
Why?
this.state.checked was applied to renderItem's component. Thus, every renderItem component was using the same state called from this.state.checked. That is why it affects to all items. And I resolve this to use independent states in Item component.
In React Native, only TextInput has onFocus and onBlur event listener. However, we would like to simulate this effect on a View.
Here is what we try to achieve. There is a View on the screen. It gains "focus" when user taps on it and is hightlighted. We want to detect that user taps outside the View, so that we can "blur" the View, that is, remove the highlight.
I know we can use focus method (https://facebook.github.io/react-native/docs/nativemethodsmixin.html#focus) to request focus for the View. The problem is that I don't know how to detect that user presses outside the View.
I think the easiest is to set a state variable instead so you can play with that one such as:
class MyView extends Component {
constructor(props) {
super(props);
this.state = {
activeView: null,
}
this.views = [
(<View style={{ padding: 20 }}><Text>View 1</Text></View>),
(<View style={{ padding: 20 }}><Text>View 2</Text></View>),
(<View style={{ padding: 20 }}><Text>View 3</Text></View>),
(<View style={{ padding: 20 }}><Text>View 4</Text></View>),
(<View style={{ padding: 20 }}><Text>View 5</Text></View>),
];
}
_setActive( index ) {
return () => {
this.setState({ activeView: index })
}
}
render() {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
{this.views.map( (view, index) => {
let containerStyle = [];
if ( index === this.state.activeView ) {
containerStyle.push({ borderWidth: 10 })
}
return (
<TouchableOpacity onPress={this._setActive(index)} style={containerStyle}>
{view}
</TouchableOpacity>
);
})}
</View>
);
}
}
You can use MyView where requried as <MyView /> also every item on the array views can have any style required and configure each if is active just by access to this.state.activeView.
Let me know if you have any question.
In situations like this, I add onPress() to the the element that is outside of the View in question.
<View onPress(removeHighlight)></View>
<View onPress(addHighlight)></View>