I have navigation screens in my App.js with one screen rendering custom header as:
const DailyStack = createStackNavigator({
Dashboard,
SalesDashboard: {
screen : SalesDashboard,
navigationOptions:{
header: null,
}
},
StackNavSecond : {
screen: StackNavSecond,
navigationOptions : {
header : <CustomHeader />,
}
},....
Then in my CustomHeader.js file, I have some state data:
class CustomHeader extends Component {
constructor(props) {
super(props)
this.state = {
title:'Regions',
subtitle:'',
oem: ''
}
}
async componentDidMount(){
let car_brand = await AsyncStorage.getItem('brand_name');
let main_oem = await AsyncStorage.getItem('oem_name');
await this.setState({
oem: main_oem,
subtitle: car_brand,
});
console.log(this.state.oem)
}
render() {
console.log(this.state.title)
const {title, subtitle, oem} = this.state;
return (
<View>
<CustomDropdown title={title} subtitle={subtitle} oem={oem} />
</View>
)
}
}
export default withNavigation(CustomHeader);
The prop title is not getting passed to its child component which is getting rendered further in two more screens.
The code for CustomDropdown.js is:
class CustomDropdown extends Component {
constructor(props) {
super(props)
this.state = {
title: '',
oem: '',
subtitle:''
};
}
componentDidMount(){
this.setState({
title:this.props.title,
subtitle: this.props.subtitle,
oem: this.props.oem,
});
console.log(this.state.title, this.state.oem, this.state.subtitle)
}
render() {
return (
<View style={{flexDirection:'row', justifyContent: 'space-between'}}>
.........
</View>
)
}
}
export default withNavigation(CustomDropdown);
When I console this.state.title, it prints but no value for subtitle and oem. I even tried putting console statement inside the callback() of this.setState() but still, no props gets print for oem and subtitle.
Please help to resolve this.
You can use withNavigation when you do not want to deliver components. But your code is deeply nested.
If you want to use it like this, you can change the code like this.
CustomHeader.js
class CustomHeader extends React.Component {
constructor(props) {
super(props)
this.state = {
title:'Regions',
subtitle:'',
oem: ''
}
}
async componentDidMount(){
let car_brand = await AsyncStorage.getItem('brand_name');
let main_oem = await AsyncStorage.getItem('oem_name');
this.setState({
oem: main_oem,
subtitle: car_brand,
});
console.log(this.state.oem)
}
render() {
console.log(this.state.title)
const {title, subtitle, oem} = this.state;
return (
<View>
<CustomDropdown title={title} subtitle={subtitle} oem={oem} />
</View>
)
}
}
export default withNavigation(CustomHeader);
CustomDropdown.js
class CustomDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
oem: props.oem,
subtitle: props.subtitle
};
}
componentDidMount(){
console.log(this.state.title, this.state.oem, this.state.subtitle)
}
render() {
return (
<View style={{flexDirection:'row', justifyContent: 'space-between'}}>
.........
</View>
)
}
}
export default CustomDropdown;
Related
I am trying to update the state in a method start() and then passing the state's value to MyComponent.
My component works ok, and the state updates ok when I'm setting it within the class, but when I'm trying to pass it from start method - it doesn't work. getting "TypeError: this.setState is not a function"
Edited - fixed binding but something still doesn't work.
What can be the problem?
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
this.start= this.start.bind(this)
}
start() {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
this is MyComponent:
class MyComponent extends Component {
constructor(props){
super(props)
this.state={}
this.state.custum={
backgroundColor: 'red'
}
let intervalid;
if (this.props.value){
setTimeout(() => {
this.setState( {
custum:{
backgroundColor: 'green'
}
})
}, 1000);
setTimeout(() => {
this.setState( {
custum:{
backgroundColor: 'red'
}
})
}, 2000);
}
}
render() {
return (
<View style={[styles.old, this.state.custum]}>
</View>
);
}
}
var styles = StyleSheet.create({
old:{
padding: 5,
height: 80,
width: 80,
borderRadius:160,
},
})
export default MyComponent;
You have to bind the context to your function.
constructor (props){
super(props);
this.state = {
val: false
}
this.start = this.start.bind(this)
}
or just you can an arrow function, without binding
start = () => {
this.setState({ val: true })
}
bind start method to the component other 'this' will be undefined for the start function
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
this.start = this.start.bind(this)
}
start() {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
You need to make start function to be binded through constructor or ES6 arrow function.
export default class App extends Component{
constructor (props){
super(props);
this.state = {
val: false
}
}
start = () => {
this.setState({ val: true })
}
render(){
return (
<View>
<Button
title='start'
onPress={this.start}>
</Button>
<MyComponent value={this.state.val}> </MyComponent>
</View>
);
}
}
You have to bind the method with this. Just add
this.start = this.start.bind(this)
after this.state in the constructor.
EDIT
And also try to move custom inside state in MyComponent like this:
this.state={
custum: {
backgroundColor: 'red'
}
}
and remove
this.state.custum={
backgroundColor: 'red'
}
As you can't just set state like this.
I am getting buffered image/jpeg from backend. Parent component does setState every time a new image is received and is passed down to Child component.
Child Component receives every update and can be seen in console logs, but it is only displaying single image.
Parent component:
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: ''
}
}
getFileStatusFromNode = (data) => {
this.setState({nodeResponse: data})
}
render() {
let CardView = nodeResponse !== '' &&
<Card key={nodeResponse.fileName} name={nodeResponse.fileName} image={nodeResponse.buffer} />
return (
<div className="App tc">
{ CardView }
</div>
)
}
}
Child component:
class Card extends PureComponent {
constructor({props}) {
super(props);
this.state = {
src: '',
};
}
componentDidMount() {
console.log("Card mounted")
this.setState(prevState => ({
src: [this.props.image, ...prevState.src]
}), () => console.log(this.state.src, this.props.name));
}
render() {
const { name } = this.props;
const { src } = this.state;
return (
<a style={{width: 200, height: 250}} key={name} className={'tc'} >
<div id='images'>
<img style={{width: 175, height: 175}} className='tc' alt='missing' src={`data:image/jpeg;base64, ${src}`}/>
</div>
</a>
)
}
}
NOTE: These images are coming from socket io. I want to display them in real time rather than creating a list first and then display together.
You are only rendering 1 <Card> when defining the <CardView> component.
Assuming nodeResponse.imageFiles is an array of files, you should have something like the following:
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: ''
}
}
getFileStatusFromNode = (data) => {
this.setState({nodeResponse: data})
}
render() {
let CardView
if(this.state.nodeResponse !== '') {
CardView = this.state.nodeResponse.imageFiles.map(file => (
<Card
key={image.fileName}
name={image.fileName}
image={image.buffer} />
)
)}
return (
<div className="App tc">
{ CardView }
</div>
)
}
}
Try with
class App extends Component {
constructor(props) {
super(props);
this.state = {
nodeResponse: []
}
}
getFileStatusFromNode = (data) => {
// here you merge the previous data with the new
const nodeResponse = [...this.state.nodeResponse, data];
this.setState({ nodeResponse: nodeResponse });
}
render() {
return (<div className="App tc">
{this.state.nodeResponse.map(n => <Card key={n.fileName} name={n.fileName} image={n.buffer} />)}
</div>);
}
}
and in your child component
class Card extends PureComponent {
render() {
const { src } = this.props;
return (<a style={{width: 200, height: 250}} className="tc">
<div id='images'>
<img style={{width: 175, height: 175}} className='tc' alt='missing' src={`data:image/jpeg;base64, ${src}`}/>
</div>
</a>);
}
}
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 problem - i need to change parent component's state from child component. I tried standard variant with props, but it's not helping in my case.
Here is code of Parent component:
class ThemesBlock extends React.Component {
constructor(props){
super(props);
this.state = {
currentThemeId: 0
}
}
changeState(n){
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
data.map(function (item) {
return <Theme key={item.id} data={item} changeState=
{item.changeState}/>
})
)
}
}
And here is my code for Child Component:
class Theme extends React.Component {
constructor(props){
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const { id, themename } = this.props.data;
const link = '#themespeakers' + id;
return (
<li><a href={link} onClick={() => this.changeState(id)}
className="theme">{themename}</a></li>
)
}
}
The primary issue is that changeState should be bound to the ThemesBlock instance, not to the Theme instance (by ThemesBlock).
Here's an example where I've bound it in the ThemesBlock constructor (I've also updated it to show what theme ID is selected):
class ThemesBlock extends React.Component {
constructor(props) {
super(props);
this.state = {
currentThemeId: 0
}
this.changeState = this.changeState.bind(this);
}
changeState(n) {
this.setState({currentThemeId: n})
}
render() {
let { data } = this.props;
return (
<div>
<div>
Current theme ID: {this.state.currentThemeId}
</div>
{data.map(item => {
return <Theme key={item.id} data={item} changeState={this.changeState} />
})}
</div>
)
}
}
class Theme extends React.Component {
constructor(props) {
super(props);
this.changeState = this.props.changeState.bind(this);
}
render() {
const {
data: {
id,
themename
},
changeState
} = this.props;
const link = '#themespeakers' + id;
return (
<li>{themename}</li>
)
}
}
const data = [
{id: 1, themename: "One"},
{id: 2, themename: "Two"},
{id: 3, themename: "Three"}
];
ReactDOM.render(
<ThemesBlock data={data} />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Did you try to move map() before return? Like this:
render() {
let { data } = this.props;
const outputTheme = data.map(function (item) {
return <Theme key={item.id} data={item} changeState= {item.changeState}/>
})
return (
{outputTheme}
)
}
I know there's many similar threads but since I have a hard time understanding the answers I figured I might try with my own code and see if I can make any sense of the answers.
I just set this up really simple to test it out. I have an Index file that is opened when I start the app. In the index I have testValue in this.state:
Update:
In SignIn:
constructor(props){
super(props);
this.state={
credentials: {
username: "",
password:"",
}
}
this.navigate = this.props.navigation.navigate;
{...}
this.navigate("main", {
credentials: this.state.username,
});
In main:
constructor(props) {
super(props);
this.params = this.props.navigation.state.params;
this.navigate = this.props.navigation.navigate;
{...}
render() {
console.log(this.params.username);
This would actually log the testValue. All you have to do is to pass the testValue state as a prop in TestIndex component. Like this:
Index.jsx
export default class Index {
constructor(props) {
super(props);
this.state = {
testValue: 'hello'
}
}
render() {
return <TestIndex testValue={this.state.testValue} />
}
}
TestIndex.jsx
export default class TestIndex extends React.Component {
index() {
this.props.navigation.navigate("index")
}
handleClickMain = () => {
this.props.navigation.navigate("index");
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.btn} onPress={() => {
console.log(this.props.testValue) //here's where I want it to log testValue from the index file
this.handleClickIndex()
}
}>
</TouchableOpacity>
</View>
);
}
}