I'm new to react native and want to make one function change state for the clicked button only not others that have the same function
as I explained in the title here is an example code
please any help & I know it might be a selly question but any answer will help
thanks a lot
export default class App extends Component {
constructor(){
super();
this.state = {
opened: true,
}
}
componentHideAndShow = () =>{
this.setState(previousState => ({opened: !previousState.opened}))
}
render() {
return (
{
this.state.opened ? <Text> hello</Text> : <Text> hello sdfsdfsdf</Text>
}
<Text onPress={this.componentHideAndShow}>test</Text>
{
this.state.opened ? <Text> hello</Text> : <Text> hello sdfsdfsdf</Text>
}
<Text onPress={this.componentHideAndShow}>test</Text>
);
}
}
This should work.
import React, { Component } from 'react';
import { View, Text, Button } from 'react-native';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
opened: [true, true]
};
}
componentHideAndShow = index => {
const opened = this.state.opened;
opened[index] = !opened[index];
this.setState({ opened: opened });
};
render() {
return (
<View>
{this.state.opened[0] ? (
<Text> hello</Text>
) : (
<Text> hello sdfsdfsdf</Text>
)}
<Button onPress={() => this.componentHideAndShow(0)}>test</Button>
{this.state.opened[1] ? (
<Text> hello</Text>
) : (
<Text> hello sdfsdfsdf</Text>
)}
<Button onPress={() => this.componentHideAndShow(1)}>test</Button>
</View>
);
}
}
Edit: you can do like this if you don't know the number of items:
import React, { Component } from 'react';
import { View, Text, Button } from 'react-native';
const myArrayOfStrings = ['hello1', 'hello2', 'hello3', 'hello4', 'hello5'];
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
opened: undefined
};
}
componentDidMount() {
let opened = [];
myArrayOfStrings.map(item => {
opened.push(true);
});
this.setState({ opened: opened });
}
componentHideAndShow = index => {
const opened = this.state.opened;
opened[index] = !opened[index];
this.setState({ opened: opened });
};
render() {
const output = myArrayOfStrings.map((item, index) => {
return (
<View>
<Text>
{this.state.opened[index]
? `${item} is opened`
: `${item} is opened`}
</Text>
<Button onPress={() => this.componentHideAndShow(0)}>test</Button>
</View>
);
});
return <View>{output}</View>;
}
}
Related
I have a component where when I click on an icon, I execute a function that modify a state and then i can check the state and modify the icon. In that comonent, I am mapping datas and it renders several items.
But when I click on one icon all the icons of the components change too.
Here is the code for the component
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
starSelected: false
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar() {
this.setState({ starSelected: !this.state.starSelected })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.starSelected == true)?'star':'star-outline'} onPress={this.onPressStar.bind(this)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}
And here is the code for my screen that uses the component
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
loading: true
};
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
Your initiation is correct but you are missing INDEX of each item. Inside this.onPressStar() method check if item's index = currentItem. Also don't forget to set item id = index onpress.
I hope this has given you idea how to handle it.
You have to turn your stars into an Array and index them:
change your constructor:
constructor(props) {
super(props)
this.state = {
starSelected: []
};
}
change your onPressStar function to :
onPressStar(index) {
this.setState({ starSelected[index]: !this.state.starSelected })
}
and your icon to
<Icon style={[styles.iconStar]} name={(this.state.starSelected[index] == true)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
Well, the problem is that you have a single 'starSelected' value that all of your rendered items in your map function are listening to. So when it becomes true for one, it becomes true for all.
You should probably maintain selected state in the top level component, and pass down the discovery, whether its selected, and how to toggle being selected as props to a render function for each discovery.
export default class DiscoveryItem extends Component {
constructor(props) {
super(props);
this.state = {
discoveries: [],
selectedDiscoveries: [] // NEW
loading: true
};
}
toggleDiscovery = (discoveryId) => {
this.setState(prevState => {
const {selectedDiscoveries} = prevstate
const discoveryIndex = selectedDiscoveries.findIndex(id => id === discoveryId)
if (discoveryIndex === -1) { //not found
selectedDiscoveries.push(discoveryId) // add id to selected list
} else {
selectedDiscoveries.splice(discoveryIndex, 1) // remove from selected list
}
return {selectedDiscoveries}
}
}
componentDidMount() {
firebase.database().ref("discoveries/").on('value', (snapshot) => {
let data = snapshot.val();
let discoveries = Object.values(data);
this.setState({discoveries: discoveries, loading: false});
});
}
render() {
return (
<Container>
<Content>
{
this.state.discoveries.map(d => {
return <DiscoveryComponent key={d.id} discovery={d} selected={selectedDiscoveries.includes(d.id)} toggleSelected={this.toggleDiscovery} />
//<DiscoveryComponent discoveries={this.state.discoveries} />
</Content>
</Container>
)
}
}
You can then use your DiscoveryComponent to render for each one, and you're now maintaining state at the top level, and passing down the discovery, if it is selected, and the toggle function as props.
Also, I think you may be able to get snapshot.docs() from firebase (I'm not sure as I use firestore) which then makes sure that the document Id is included in the value. If snapshot.val() doesn't include the id, then you should figure out how to include that to make sure that you use the id as both key in the map function as well as for the selectedDiscoveries array.
Hope that helps
It works now, thanks.
I've made a mix between Malik and Rodrigo's answer.
Here is the code of my component now
export default class DiscoveryComponent extends Component {
constructor(props) {
super(props)
this.state = {
tabStarSelected: []
};
}
static propTypes = {
discoveries: PropTypes.array.isRequired
};
onPressStar(index) {
let tab = this.state.tabStarSelected;
if (tabStar.includes(index)) {
tabStar.splice( tabStar.indexOf(index), 1 );
}
else {
tabStar.push(index);
}
this.setState({ tabStarSelected: tab })
}
render() {
return (
this.props.discoveries.map((discovery, index) => {
return (
<Card key={index} style={{flex: 0}}>
<CardItem>
<Left>
<Body>
<Text note>{discovery.category}</Text>
<Text style={[styles.title]}>{discovery.title}</Text>
</Body>
</Left>
<TouchableOpacity style={[styles.star]}>
<Icon style={[styles.iconStar]} name={(this.state.tabStarSelected[index] == index)?'star':'star-outline'} onPress={()=>this.onPressStar(index)}/>
</TouchableOpacity>
</CardItem>
</Card>
)
})
);
}
}
I have a button and when I toggle the button It changes the color.
This is the code:
state={
status:[
{toggle:false}
]
}
_onPress(){
const newState = !this.state.toggle
this.setState({toggle:newState})
}
render(){
const {toggle} = this.state
const textValue = toggle?"ON":"OFF"
const buttonBG = toggle?"#6AAAC6":"white"
const textColor = toggle?"white":"gray"
return(
<TouchableOpacity
onPress={()=>this._onPress()}
<Text>button</Text>
</TouchableOpacity>
)
}
}
But what if I have multiple buttons and they basically do the same function. Is there a way I could reuse this code without copy and pasting multiple times?
you can create a component call CustomButton
class CustomButton extends React.Component {
static defaultProps = {
onToggle: () => {},
}
state = {
status: [{ toggle: false }]
}
_onPress() {
const newState = !this.state.toggle
this.setState({ toggle: newState })
this.props.onToggle(newState)
}
render() {
const { toggle } = this.state
const textValue = toggle ? 'ON' : 'OFF'
const buttonBG = toggle ? '#6AAAC6' : 'white'
const textColor = toggle ? 'white' : 'gray'
return (
<TouchableOpacity onPress={() => this._onPress()}>
<Text>button</Text>
</TouchableOpacity>
)
}
}
and use anywhere you want
class App extends React.Component {
onButtonToggle = (isToggle) => {
console.log(isToggle)
}
render() {
return (
<View>
<CustomButton onToggle={this.onButtonToggle} />
</View>
)
}
}
I'm working on an app that keeps track of salespeople's availability based on being either "Available" or "With Client".
Here's the bug I'm having. I'll use an example:
2 salespeople have been added to the app. The order in which they have been added to the app seems to matter in a way I don't expect to. For example, if the first salesperson I've added is James and the second is Rick, If I click on the button next to Rick that reads "Helped A Customer", James will now populate both the "Available" and the "With Client" tables, and Rick will have disappeared.
However if I click on them in a certain order, it works fine. For example, in the same situation as the example above, if I click on James' "Helped A Customer" first, then Rick's "Helped A Customer", then James' "No Longer With Customer", then Rick's "No Longer With Customer", it behaves as expected.
Here's the github project, you can clone it and try it out yourselves:
https://github.com/jackson-lenhart/salesperson-queue
I'll post what I think is the most relevant code here as well:
main.js:
import React from "react";
import { render } from "react-dom";
import shortid from "shortid";
import deepCopy from "deep-copy";
import AddForm from "./add-form";
import Available from "./available";
import WithClient from "./with-client";
class Main extends React.Component {
constructor() {
super();
this.state = {
queue: {
available: [],
withClient: [],
unavailable: []
},
currName: ""
};
this.addToQueue = this.addToQueue.bind(this);
this.removeFromQueue = this.removeFromQueue.bind(this);
this.handleInput = this.handleInput.bind(this);
this.move = this.move.bind(this);
}
addToQueue(name) {
let newQueue = deepCopy(this.state.queue);
newQueue.available = this.state.queue.available.concat({
name,
id: shortid.generate()
});
this.setState({
queue: newQueue
});
}
removeFromQueue(id) {
let newQueue = deepCopy(this.state.queue);
for (let k in this.state.queue) {
newQueue[k] = this.state.queue[k].filter(x =>
x.id !== id
);
}
this.setState({
queue: newQueue
});
}
move(id, from, to) {
this.setState(prevState => {
let newQueue = deepCopy(prevState.queue);
let temp = newQueue[from].find(x => x.id === id);
newQueue[from] = prevState.queue[from].filter(x =>
x.id !== id
);
newQueue[to] = prevState.queue[to].concat(temp);
return {
queue: newQueue
};
});
}
handleInput(event) {
this.setState({
currName: event.target.value
});
}
render() {
return (
<div>
<AddForm
addToQueue={this.addToQueue}
handleInput={this.handleInput}
currName={this.state.currName}
/>
<Available
available={this.state.queue.available}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
<WithClient
withClient={this.state.queue.withClient}
move={this.move}
removeFromQueue={this.removeFromQueue}
/>
</div>
);
}
}
render(
<Main />,
document.body
);
add-form.js:
import React from "react";
class AddForm extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.addToQueue(this.props.currName);
}
render() {
return (
<div>
<input
type="text"
onChange={this.props.handleInput}
/>
<button onClick={this.clickWrapper}>
<strong>Add To Queue</strong>
</button>
</div>
);
}
}
export default AddForm;
available.js:
import React from "react";
import Salesperson from "./salesperson";
class Available extends React.Component {
render() {
const style = {
item: {
padding: "10px"
},
available: {
padding: "20px"
}
};
let available;
this.props.available.length === 0 ?
available = (
<p>None available.</p>
) : available = this.props.available.map(x =>
<div key={x.id} style={style.item}>
<Salesperson
key={x.id}
id={x.id}
name={x.name}
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
parent={"available"}
/>
</div>
);
return (
<div style={style.available}>
<h1>Available</h1>
{available}
</div>
);
}
}
export default Available;
salesperson.js:
import React from "react";
import DeleteButton from "./delete-button";
import HelpedButton from "./helped-button";
import NlwcButton from "./nlwc-button";
class Salesperson extends React.Component {
render() {
const style = {
name: {
padding: "10px"
},
button: {
padding: "5px"
}
};
let moveButton;
switch(this.props.parent) {
case "available":
moveButton = (
<HelpedButton
move={this.props.move}
id={this.props.id}
style={style.button}
/>
);
break;
case "withClient":
moveButton = (
<NlwcButton
move={this.props.move}
removeFromQueue={this.props.removeFromQueue}
id={this.props.id}
style={style.button}
/>
);
break;
default:
console.error("Invalid parent:", this.props.parent);
}
return (
<div>
<span style={style.name}>{this.props.name}</span>
{moveButton}
<DeleteButton
removeFromQueue={this.props.removeFromQueue}
name={this.props.name}
id={this.props.id}
style={style.button}
/>
</div>
);
}
}
export default Salesperson;
helped-button.js:
import React from "react";
class HelpedButton extends React.Component {
constructor() {
super();
this.clickWrapper = this.clickWrapper.bind(this);
}
clickWrapper() {
this.props.move(this.props.id, "available", "withClient");
}
render() {
return (
<span style={this.props.style}>
<button onClick={this.clickWrapper}>
<strong>Helped A Customer</strong>
</button>
</span>
);
}
}
export default HelpedButton;
This was just a typo on my part. No longer an issue. Here's the commit that fixed the typo:
https://github.com/jackson-lenhart/salesperson-queue/commit/b86271a20ac8b700bec1e15e001b0c6ef57adb8b
Trying to get FlatList to display data from Firebase.
Setup is correct and I can see the date in my console, but don't know how to visualise it.
I'd like to see 'recipeOne' 'recipeTwo' 'recipeThree' in the list.
I am sure I am missing something basic.
Here is the code
...
import {DataConfig} from '../data/DataConfig';
const firebaseApp = firebase.initializeApp(DataConfig);
globalTexts = require('../styles/Texts.js');
globalColors = require('../styles/Colors.js');
export default class ListSort extends Component {
constructor(props) {
super(props);
this.dataItem = firebaseApp.database().ref('recipes');
this.state = {
item: []
}
};
componentWillMount() {
this._createItemList();
};
_createItemList = (dataItem) => {
this.dataItem.on('value', (snapshot) => {
var itemList = [];
snapshot.forEach((doc) => {
itemList.push({
key:doc.key,
itemType: doc.toJSON()
});
this.setState({item: itemList});
console.log(this.state.item);
})
})
};
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.item}
renderItem={({item, index}) => (
<View style={styles.cell}>
<Text style={globalText.btnFlatPrimary}>{item.recipes}</Text>
</View>
)}
/>
</View>
)
}
}
and here is the data. The rules in Firebase are setup as read:true only.
{
"recipes": {
"recipeOne": {...
"recipeTwo": {...
"recipeThree": {...
}
}
I have a project in react-native (0.23) with Meteor 1.3 as back-end and want to display a list of contact items. When the user clicks a contact item, I would like to display a checkmark in front of the item.
For the connection to Meteor DDP I use the awesome library inProgress-team/react-native-meteor.
import Meteor, { connectMeteor, MeteorListView, MeteorComplexListView } from 'react-native-meteor';
class ContactsPicker extends React.Component {
constructor(props) {
super(props);
this.state = {
subscriptionIsReady: false
};
}
componentWillMount() {
const handle = db.subscribe("contacts");
this.setState({
subscriptionIsReady: handle.ready()
});
}
render() {
const {subscriptionIsReady} = this.state;
return (
<View style={gs.standardView}>
{!subscriptionIsReady && <Text>Not ready</Text>}
<MeteorComplexListView
elements={()=>{return Meteor.collection('contacts').find()}}
renderRow={this.renderItem.bind(this)}
/>
</View>
);
}
The first problem is, that subscriptionIsReady does not trigger a re-render once it returns true. How can I wait for the subscription to be ready and update the template then?
My second problem is that a click on a list item updates the state and should display a checkmark, but the MeteorListView only re-renders if the dataSource has changed. Is there any way to force a re-render without changing/ updating the dataSource?
EDIT 1 (SOLUTION 1):
Thank you #user1078150 for providing a working solution. Here the complete solution:
'use strict';
import Meteor, { connectMeteor, MeteorListView, MeteorComplexListView } from 'react-native-meteor';
class ContactsPicker extends React.Component {
getMeteorData() {
const handle = Meteor.subscribe("contacts");
return {
subscriptionIsReady: handle.ready()
};
}
constructor(props) {
super(props);
this.state = {
subscriptionIsReady: false
};
}
componentWillMount() {
// NO SUBSCRIPTION HERE
}
renderItem(contact) {
return (
<View key={contact._id}>
<TouchableHighlight onPress={() => this.toggleSelection(contact._id)}>
<View>
{this.state.selectedContacts.indexOf(contact._id) > -1 && <Icon />}
<Text>{contact.displayName}</Text>
</View>
</TouchableHighlight>
</View>
)
}
render() {
const {subscriptionIsReady} = this.data;
return (
<View>
{!subscriptionIsReady && <Text>Not ready</Text>}
<MeteorComplexListView
elements={()=>{return Meteor.collection('contacts').find()}}
renderRow={this.renderItem.bind(this)}
/>
</View>
);
}
}
connectMeteor(ContactsPicker);
export default ContactsPicker;
You have to do something like this :
getMeteorData() {
const handle = Meteor.subscribe("contacts");
return {
ready: handle.ready()
};
}
render() {
const { ready } = this.data;
}