Passing props into state in React Native? - javascript

I have a button on the homescreen which toggles the text in the AlertBar.
So when I press a Button, the text in AlertBar should change according to the state isParked. Currently when I press the button, nothing happens... and I'm unsure why.
Here's my homescreen:
class Home extends Component {
constructor(props) {
super(props);
this.state = {
isParked: false
};
}
pressPark = () => this.setState({isParked:true})
render() {
console.ignoredYellowBox = ['Remote debugger'];
return (
<View>
<View>
<AlertBar isParked={this.state.isParked}/>
</View>
<View style={styles.parkButton}>
<Button title='PARK' onPress={this.pressPark} color='green'/>
</View>
</View>
);
}
}
Here's my AlertBar.js:
class AlertBar extends Component {
state = {
region: 'Singapore',
isParked: this.props.isParked,
alertText: null
}
... some unrelated code ...
componentDidMount() {
if (this.state.isParked === false) {
this.setState({alertText: "You're parking at"})} else if (this.state.isParked === true) {
this.setState({alertText: "You're parked at"})}
alert(this.state.alertText)
}
componentWillUnmount() {
// some unrelated code
}
render() {
... some unrelated code...
return(
<View style={styles.container}>
<Text style={styles.welcomeText}>
{this.state.alertText}
</Text>
<Text style={styles.locationText}>
{this.state.region}
</Text>
</View>
)
}
}
Am I doing this wrong? I can't tell what's wrong.... Please help! Thanks!

Use
if (this.props.isParked === false)
Instead of
if (this.state.isParked === false)
(and dont transfer props to state directly, this make no sense anyway :))

At this point, your AlertBar component is not handling any prop changes.
What you'll need to do, is map your props to the state whenever an update is received.
Add this line of code in your AlertBar.js and it will map isParked to state whenever it receives an update.
componentWillReceiveProps(props) {
this.setState({ isParked: props.isParked });
}

Related

react-native : update state then check with addListener

I am trying to update state then check the state with the 'addListener' in the 'componentDidMount'
When I update my state, I can see that my state is successfully updated but the addListener won't update.
I think I should see the console.log('state is the same') when I update my state but noting is happening.
Any Ideas?
This is my code:
export default class Update extends React.Component {
constructor(props) {
super(props)
this.state = {
number:1,
newNumber:undefined
}
console.log(this.state.number, 'number')
}
componentDidMount() {
this.props.navigation.addListener('willFocus', () => {
//when screen is loaded I get this console.
if(this.state.number != this.state.newNumber){
console.log('state is not the same')
}
//when I update state I should see this console log but nothing is happening.
if(this.state.number == this.state.newNumber){
console.log('state is the same')
}
})
}
render() {
return (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button title='update' onPress={()=> this.setState({newNumber:1}, ()=> console.log(this.state.newNumber, 'newNumber'))}/>
</View>
);
}
}
Basically what you are trying to achieve is to check that onClick of button , there is a newState which is set , and you want to check with existing one , did it update correctly or not.
So you are using this.props.navigation.addListener('willFocus') which gets called when the screen is focused while coming from other screens. but in your case you are in the same screen and you update the state by calling the button, so it wont get called.
Rather react has its own lifecylce method called componentDidUpdate , and if you check the condition there for any state updates you will achieve it.
Please find below code and also expo link,
export default class Update extends React.Component {
constructor(props) {
super(props)
this.state = {
number:1,
newNumber:undefined
}
console.log(this.state.number, 'number')
}
componentDidUpdate(){
if(this.state.number != this.state.newNumber){
alert('state is not the same')
}
//when I update state I should see this console log but nothing is happening.
if(this.state.number == this.state.newNumber){
alert('state is the same')
}
}
render() {
return (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button title='update' onPress={()=> this.setState({newNumber:1}, ()=> console.log(this.state.newNumber, 'newNumber'))}/>
</View>
);
}
}
expo link expo
hopeit helps, feel free for doubts
the reason why your code wont work is because you want to listen to state changes but your listener only listen to navigation changes which only triggered when the screen will focus.
to detect the changes to your state in class component, you can always use the lifecycle of react which is componentDidUpdate
what you can do perhaps something like this.
export default class Update extends React.Component {
constructor(props) {
super(props)
this.state = {
number:1,
newNumber: null
}
console.log(this.state.number, 'number')
}
componentDidMount() {
this.check()
}
componentDidUpdate(prevState) {
if(prevState.newNumber !== this.state.newNumber) {
this.check()
}
}
check = () => {
if(this.state.number != this.state.newNumber){
console.log('state is not the same')
} else {
console.log('state is the same')
}
}
render() {
return (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<Button title='update' onPress={()=> this.setState({newNumber:1}, ()=> console.log(this.state.newNumber, 'newNumber'))}/>
</View>
);
}
}

When to switch from storing nested component variables in this.props vs this.state

I'm getting back into React and trying to replicate an instagram post. Variables that don't change such as the name of the poster, location, etc I think I have a solid understanding of how to handle. I keep in them in the chain of this.props as they will be immutable from the user perspective. But when it comes to something like "reactions", I am unsure of when to begin storing them in this.state vs. this.props due to the multiple levels of nesting.
Should I keep the data relating to my reactions array / object in this.state (as I attempt to do below) for all nested components all the way down? Or keep them in this.props until I get to the lowest child component?
I don't have any code for lifting state up since I want to make sure my architecture is correct first.
Home.js
const POST_DATA = [
{
name: 'Outdoors Guy',
location: 'Yosemite, CA',
id: '123',
body: 'Hello world!',
image: 'https://someurl',
reactions: [
{
reaction: 'Like',
count: 2,
selected: true
},
{
reaction: 'Love',
count: 1,
selected: false
}
]
}
];
export default class HomeScreen extends React.Component {
constructor(props){
super(props)
this.state = {
posts: POST_DATA
}
}
render(){
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView>
<FlatList styles={styles.list}
data={this.state.posts}
renderItem={({ item }) =>
<Post
name={item.name}
location={item.location}
body={item.body}
image={item.image}
reactions={item.reactions}/>}
keyExtractor={item => item.id}/>
</ScrollView>
</View>
);
}
}
Post.js
export default class Post extends Component {
constructor(props){
super(props)
state = {
// should I be doing this?
reactions: this.props.reactions
}
}
render() {
return (
<View>
<View style={styles.postHeader}>
// Example of these two variables ending their chaining here
<Text style={styles.name}>{this.props.name}</Text>
<Text style={styles.location}>{this.props.location}</Text>
</View>
<Text style={styles.body}>{this.props.body}</Text>
<Image style={styles.image} source={{uri: this.props.image}}/>
// Heres where the confusion begins
<ReactionList list={this.props.reactions}/>
</View>
);
}
}
ReactionList.js
export default class ReactionList extends Component {
constructor(props){
super(props)
state = {
reactions: this.props.reactions
}
}
render() {
if (this.state.reactions === null)
{
return (
<View>
<AddReaction/>
<FlatList styles={styles.reactionList}
data={this.state.reactions}
renderItem={({ item }) =>
<Reaction
reaction={item.reaction}
count={item.count}
selected={item.selected}/>}
keyExtractor={item => item.id}/>
</View>
)
}
else{
return (
<View>
<AddReaction/>
</View>
)
}
}
}
Reaction.js
export default class Reaction extends Component {
constructor(props){
super(props)
state = {
// I understand these are not visible in the component
// but still want to track the data
count: this.props.count,
selected: this.props.selected
}
}
_onSelectPress () {
this.setState({
selected: !this.state.selected,
count: ++this.state.count
})
}
render() {
return (
<TouchableOpacity style={styles.reaction}
onPress={() => this._onSelectPress()}>
<Text style={styles.reactionText}>{this.props.reaction}</Text>
</TouchableOpacity>
);
}
If it makes sense to you, you could store the POST_DATA completely inside the state, as this will greatly simplify how the Parent component who holds the post information and the Child Post component behave.
For example, what if, instead of just letting the user edit reactions, you wanted the post owner to be able to edit the body or location of POST_DATA? Or what if you wanted the reactions count to be accesible to say, a separate modal (when you click on the post)?
A way to future-proof this design, while keeping it simple, is to send the whole POST_DATA to your <Post> component and if you don't want to use a library such as Redux to handle the state, then lift the callback for edits up:
Here's a simplified Codesandbox I did of this implementation.
<InstaPost
post={this.state.post} <-- this.state.post holds POST_DATA
handleReactionClick={this.handleReactionClick}
handleBodyEdit={this.handleBodyEdit} <-- if we decide to implement this later on
/>
Why hold network data as an Immutable object in the app/component state?
The benefits of holding the post data in the state, as an Immutable object (in the example I used immer.js), is that if, for example, the way Instagram or Facebook work is that, when you click on the "Love" reaction, an API request is sent for your reaction, and it returns the value of the reactions updated real-time:
So for example, if I pressed on "Like (2)", and 10 more people pressed like before that, the API wouldn't return "3" (2+1), but rather, "13" (2 + 1 + 10). Handling this with your current design would require much more work than with the design I propose.
Personally, I would prefer using a library such as Redux for application state, but for a small prototype it's not necessary. I do, however, recommend always using immutable objects whatever the size of the project if network fetching/updating is involved.
Hope this helps!

Sending custom props to custom component is failing

I have created a custom component which takes a color name from the parent component and updates that color in the state in the parent component. Currently, after I have done all the code, it does not save the new color, and therefore, does not update the the state.
This is for a react-native android app that I am building. I have looked at the ReactNative documentation for flatlist and textinput. I have looked at Stack overflow for solutions too
Set up a react native project. this is my parent component
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "blue",
availableColors: [
{name: 'red'}
]
}
this.changeColor = this.changeColor.bind(this)
this.newColor = this.newColor.bind(this)
}
changeColor(backgroundColor){
this.setState({
backgroundColor,
})
}
newColor(color){
const availableColors = [
...this.state.availableColors,
color
]
this.setState({
availableColors
})
}
renderHeader = ()=>{
return(
<ColorForm onNewColor={this.newColor} />
)
}
render() {
const { container, row, sample, text, button } = style
const { backgroundColor, availableColors } = this.state
return (
<View style={[container,{backgroundColor}, {flex: 1}]} >
<FlatList
data={availableColors}
renderItem={
({item}) =>
<ColorButton
backgroundColor={item.name}
onSelect={(color)=>{this.changeColor(color)}}>
{item.name}
</ColorButton>}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={this.renderHeader}
>
</FlatList>
</View>
);
}
}
this is the code for ColorForm component
class ColorForm extends Component {
constructor(props) {
super(props);
this.state = {
txtColor:'',
}
this.submit = this.submit.bind(this)
}
submit() {
this.props.onNewColor(this.state.txtColor.toLowerCase())
this.setState({
txtColor: 'yellow',
})
}
render() {
const {container, txtInput, button} = style
return (
<View style={container}>
<TextInput style={txtInput}
placeholder="Enter a color"
onChangeText={(txtColor)=>this.setState({txtColor})}
value={this.state.txtColor}></TextInput>
<Text
style={button}
onPress={this.submit}>Add</Text>
</View> );
}
}
and below is the code for ColorButton component
export default ({backgroundColor, onSelect=f=>f}) => {
const {button, row, sample, text} = style
return (
<TouchableHighlight onPress={()=>{onSelect(backgroundColor)}} underlayColor="orange" style={button}>
<View style={row}>
<View style={[sample,{backgroundColor}]}></View>
<Text style={text}>{backgroundColor}</Text>
</View>
</TouchableHighlight>
)
}
The imports and stylesheets are setup as standard and do not effect the code so I have chosen to not show them.
EDIT: Adding the expo snack here.
Expected Behavior:
When I press "ADD" on the ColorForm component, it should take that color and add that to the this.state.availableColor array and therefore visible in the ColorButton component. And when I touch the button, it should make that change
Current behaviour:
When I enter a color and press on add, it makes an empty button in the ColorButton component - NOT the color i entered in the color I entered in the ColorForm component.
EDIT: Adding the expo snack here.
Your state is updating but the FlatList is not updating. Because your data={availableColors} in flatlist is not changing but your state is changing .
Try to add extraData
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
Try this
<FlatList
extraData={this.state.backgroundColor}
Updated Answer
the problem is in this function newColor(color)
const availableColors = [
...this.state.availableColors,
color
]
you just receive a string of color but you have defined object like this {name: 'red'}
please use this code
newColor(color){
const availableColors = [
...this.state.availableColors,
{name: color}
]
this.setState({
availableColors
})
}
Snack link with example : https://snack.expo.io/#mehran.khan/healthy-cake-state-sample
Also add export default to main component to remove error of launch
export default class HomePage extends Component {
App Preview
I had many problems using setState() in the way you are using now. I recommend you to use in this way setState(), with a callback:
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});
This is one of the article that talks about it.
setState() does not always immediately update the component. It may
batch or defer the update until later.
From react website setState().

react native state changes after double tap.

I have a button and when I click on it, button changes its state. But I have to double tap to change the state. I console logged it and on first click, it shows blank and when I click on it again, it will change its state. Below is my code:
class CustomButtonOne extends React.Component {
constructor(props) {
super(props);
this.state = {
buttonOne:'',
};
this.selectionOnPressOne = this.selectionOnPressOne.bind(this),
}
selectionOnPressOne = () => {
this.setState({
buttonOne:'ONE'
})
console.log(this.state.buttonOne, 'this.state.buttonOne')
}
render() {
return (
<View>
<TouchableOpacity
onPress={()=> this.selectionOnPressOne()}>
<Text>Button</Text>
</TouchableOpacity>
</View>
)
}
}
export default CustomButtonOne
Why is this happening? and How can I change the state with one tap?
Any advice or comments would be appreciated thanks!
The setState action is async, therefore the changes that you log may not be available immediately.
Try this
this.setState({
buttonOne:'ONE'
}, () => console.log(this.state.buttonOne, 'this.state.buttonOne'))

AWS Amplify: onStatusChange then render main page

I am trying to render the main entry point of my application when an auth status change occurs but when I do try to go to the main entry point of my application I get a blank screen. I'm assuming I can't go to a main page if it isn't being called within the render function itself? If so, how do I render the main Page of my app when I established signIn?
index.js
class App extends Component {
/*
constructor(props) {
super(props);
this.state = {
authState: null,
authData: null
}
}
*/
handleAuthStateChange(state) {
alert(state)
if (state === 'signedIn') {
alert('here');
return ( // go To Entry point of app
<ApolloProvider store={store} client={client}>
<AppWithNavigationState/>
</ApolloProvider>
);
}
}
render() {
//const { authState, authData } = this.state;
// const signedIn = (authState === 'signedIn');
return (
<Authenticator hideDefault={true} onStateChange={this.handleAuthStateChange}>
<Login/>
</Authenticator>
);
}
}
export default App = codePush(App);
Login.js
export default class Login extends Component {
render() {
const { authState, onStateChange } = this.props;
if (!['signIn', 'signedOut', 'signedUp'].includes(authState)) {
alert('login')
return null;
}
return (<View style={styles.container}>
<View style={styles.backgroundContainer}>
<Image source={images.icons.LoginImage} style={styles.backgroundImage} />
</View>
<View style={styles.overlay}>
<Button iconLeft light rounded style={styles.facebookLoginButton}>
<Icon style={styles.facebookIcon} name='logo-facebook' />
<Text style={styles.facebookButtonText}>Login with Facebook</Text>
</Button>
<Button rounded bordered light style={styles.loginLaterButton}
onPress={() => onStateChange('signedIn')}>
<Text style={styles.buttonText}>Sign In Test</Text>
</Button>
</View>
</View>
);
}
}
If you resolved it I hope it will help someone else.
I think the following is a better option than using 'onAuthStateChange':
from Amplify dox :
import { Auth } from 'aws-amplify';
Auth.currentAuthenticatedUser({
bypassCache: false // Optional, By default is false. If set to true, this call will send a request to Cognito to get the latest user data
}).then(user => console.log(user))
.catch(err => console.log(err));
You can add your logic within '.then(user => ...' to add route to your protected pages. Also you can redirect to Login page from '.catch(err => '.
If you include above code within a function and call it from 'componentWillReceiveProps' it should be called every time auth status changes.
This is really about rendering and state (and not anything to do with AWS Amplify). First, set up state in your constructor:
constructor(props) {
super(props);
this.state = { authState: '' };
}
Then, your onAuthStateChange() becomes:
onAuthStateChange(newState) {
if (newState === 'signedIn') {
this.setState({ authState: newState });
}
}
Finally, in your render() method, you adjust your rendering so that it does "the right thing" based on your auth state.
render() {
if (this.state.authState === 'signedIn') {
return (<ApolloProvider ...><MyApp/></ApolloProvider>);
} else {
return (<Authenticator .../>);
}
}
You can abstract this away with a HOC as well (the same way the withAuthenticator() HOC from AWS Amplify does it). Basically, the Authenticator gets displayed initially. Once the signedIn state is received, the internal component state is updated and that causes a re-render of the component with the rest of your app.

Categories