React component state changes after following function call - javascript

I am attempting to show some info based on what item was pressed.
To do so I am modifying the state's selectedSchedule value to different values according to which button was pressed.
The info(for now, the selectedSchedule value itself) displayed is always the one that should've been before. When you press 'Segmented' the displayed text is '', the value selectedSchedule was initialized with. Then, when you go back, and press, 'Uberman' 'Segmented appears'.
I don't know if this is an issue with the component lifecycle or if javascript/React handle functions asynchronously; the function written after setState() seems to get called first.
//SleepSchedules.js
import React, { Component} from 'react';
import { Container, Content, Card, CardItem, Text, Icon, Button } from 'native-base';
import ScheduleItem from './ScheduleItem';
export default class SleepSchedules extends Component {
constructor(props) {
super(props);
this.state = {selectedSchedule: ''};
}
_handlePress(schedule){
this.setState({
selectedSchedule: schedule
});
this._navScheduleItem()
}
_navScheduleItem(){
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: {scheduleName: this.state.selectedSchedule}
})
}
render() {
return (
<Container style={{paddingTop:64}}>
<Content>
<Card>
<CardItem button
onPress={()=> this._handlePress('Segmented')} >
<Text>Segmented</Text>
</CardItem>
<CardItem button
onPress={()=> this._handlePress('Everyman')}>
<Text>Everyman</Text>
</CardItem>
<CardItem button
onPress={()=> this._handlePress('Uberman')}>
<Text>Uberman</Text>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
Here is the component it's passing it's props to:
import React, { Component} from 'react';
import { Container, Content, Card, CardItem, Text, Icon, Button } from 'native-base';
import ComingSoon from './ComingSoon';
export default class ScheduleItem extends Component {
constructor(props){
super(props);
}
render(){
return(
{this.props.scheduleName}
);
}
}

Simple, setState is async in this case (it is sync, in some special cases, lol, right?). That means, never rely on setState being sync...
There are 2 possible solutions.
First: Don't wait until state is changed. Pass received value manually, immediately.
_handlePress(selectedSchedule) {
this.setState({ selectedSchedule });
this._navScheduleItem(selectedSchedule)
}
_navScheduleItem(scheduleName) {
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: { scheduleName },
})
}
Second: Wait until state change propagate. Read updated state value.
_handlePress(selectedSchedule){
this.setState({ selectedSchedule }, () => {
this._navScheduleItem()
});
}
_navScheduleItem(){
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: { scheduleName: this.state.selectedSchedule }
})
}

Related

React.Native: Flat List not showing up

I am trying to return two things, a separate Mission component and a flat list. When I run my emulator, I can get the return on the Mission component but not the flat list. There are no errors, so I'm a bit lost with how to approach this issue. I even double-checked my imports. Here is the code:
import React, { Component } from "react";
import { ScrollView, Text, FlatList } from 'react-native';
import Mission from './Mission';
import PARTNERS from '../shared/partners';
import { ListItem } from 'react-native-elements';
class About extends Component {
constructor(props) {
super(props);
this.state = {
partners: PARTNERS,
}
};
static navigationOptions = {
title: 'About Us'
}
render() {
const { navigate } = this.props.navigation;
const renderPartner = ({ item }) => {
return (
<ListItem
title={item.name}
subtitle={item.description}
leftAvatar={{ source: require('./images/bootstrap-logo.png') }}
/>
);
};
return (
<ScrollView>
<Mission />
<FlatList
data={this.state.partners}
renderItem={renderPartner}
keyExtractor={item => item.id.toString()}
/>
</ScrollView >
);
}
}
export default About;
import React, { Component } from 'react';
import { FlatList, View, Text } from 'react-native';
import { ListItem, Card } from 'react-native-elements';
import { FILMS } from '../shared/films';
/*
When working correctly, when you hit "Tap to play" in the simulated mobile device to the right, you will see a list of films from shared/films.js.
Before updating this code:
- You must be logged into your Expo account (create an account if you do not have one already)
- Use the Save button on the upper right corner to fork this Snack to your account. Name it as you please, or accept the default random name.
Share the link to your version in the forum for this code challenge.
Your challenges: 1. Fix the component by adding a constructor and adding the data imported as FILMS to the component's state
2. In the FlatList, use that state data for films in the 'data' attribute.
3. In the ListItem, add the film title as the title, and the director as the subtitle.
4. Update films.js to add a third film of your choice.
Bonus Challenge: Write a custom view in the ListItem subtitle to show more details about each film, such as the release year, genre, language.
*/
class FilmCatalogue extends Component {
constructor(props) {
super(props);
this.state = {
films: FILMS
}
}
render() {
const renderFilm = ({item}) => {
return (
<ListItem
title={item.title}
titleStyle={{fontWeight: 700, color: 'dark-grey'}}
subtitle={
<View >
<Text style={{fontStyle: "italic", fontWeight: 500}}>{item.director}</Text>
<Text>{item.category}</Text>
<Text>{item.language}</Text>
</View>
}
rightSubtitle={item.year}
bottomDivider
/>
);
};
return (
<Card title="Film Catalogue">
<FlatList
data={this.state.films}
renderItem={renderFilm}
keyExtractor={item=>item.id.toString()}
/>
</Card>
);
}
}
export default FilmCatalogue;
i used this on an expo snack to display a list of the files i had in this file (the Film Catalogue Component.js)
hope this kind of helps!

Display a new component with button click

Im making my first react ptoject. Im new in JS, HTML, CSS and even web app programing.
What i try to do, is to display some infomration on button click.
I have an API, that looks like this:
endpoint: https://localhost:44344/api/Projects
My Data from it:
[{"id":1,"name":"Mini Jira","description":"Description for first project in list","tasks":null},{"id":2,"name":"Farm","description":"Description for second one","tasks":null}]
And im fine with that, i can get it easily by axios in my react app.
Now i will show you my Project.js Component:
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={Here i want to display new component with details }bsStyle="primary">Details</Button>
</ButtonToolbar>
);
}
}
export default Project;
I have all data from api in project type.
My question is, how to display component that i named ProjectDetails.js on button click? I want to show all data stored in project from my api in separate view (new page or somethig like that).
View looks like this:
Thanks for any advices!
EDIT:
based on #Axnyff answer, i edited Project.js. it works ok. But when i want to (for testing) displat project.name, i get error map of undefined. My ProjectDetails.js:
import React, { Component } from "react";
class ProjectDetails extends Component {
state = {};
render() {
return <li>{this.props.project.name}</li>;
}
}
export default ProjectDetails;
EDIT2:
In Project.js in #Axnyff answet i just edited that line:
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
i passed project by props, now it works like i want too. After click it displays project.name that i clicked on.
You should use state in your React component.
Let's create a field called showDetails in your state.
You can initialize it in your constructor with
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
Then you need to modify the onClick to set that state to true
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
And then use that state to show or not the ProjectDetails:
{ showDetails && <ProjectDetails /> }
The full component should look like
import React, { Component } from "react";
import { ListGroupItem, Button, ButtonToolbar } from "react-bootstrap";
import ProjectDetails from "./ProjectDetails";
class Project extends Component {
constructor(props) {
super(props); // needed in javascript constructors
this.state = {
showDetails: false,
};
}
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name}</ListGroupItem>
<Button onClick={() => this.setState({ showDetails : true })} bsStyle="primary">Details</Button>
{ this.state.showDetails && <ProjectDetails /> }
</ButtonToolbar>
);
}
}
export default Project;
You can then modify the logic to add a toggling effect etc.
If you haven't done it, you should probably follow the official tutorial
function Bar() {
return <h1>I will be shown on click!</h1>;
}
class Foo extends React.Component {
constructor() {
super();
this.state = { showComponent: false };
}
handleClick = () => {
this.setState({ showComponent: !this.state.showComponent });
};
render() {
return (
<div>
{this.state.showComponent && <Bar />}
<button onClick={this.handleClick}>click</button>
</div>
);
}
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React.js setState does not update state even if render method was called

I cannot believe that I cannot update state by setState.
I want to update cardModalOpen state to close the Modal.
I add bind(this) but it still does not work.
(Modal is opened by click Card Component)
However, I did setState({cardModalOpen: false}) by closeModal() function but it is still true even after render method was called.
Can someone please explain what I am doing wrong.
This is my code.
index.js
import React, { Component }from 'react';
import { Button, Card, Image, Header, Modal, Form, Input } from 'semantic-ui-react'
class App extends React.Component {
state = { cardModalOpen:false }
showCardModal() {
this.setState({cardModalOpen:true})
}
closeModal(){
this.setState({cardModalOpen:false})
}
render() {
const messagesDataNew = [];
for (var i = 0; i < 3; i++) {
messagesDataNew.push(
<Card
onClick={() => {
this.showCardModal();
}}
>
<DetailModal
cardModalOpen={this.state.cardModalOpen}
closeModal={this.closeModal}
/>
</Card>
);
}
return <div>{messagesDataNew}</div>;
}
}
DetailModal.js
import React, { Component }from 'react';
import { Button, Card, Image, Header, Modal, Form, Input } from 'semantic-ui-react'
class DetailModal extends Component{
render(){
return(
<Modal open={this.props.cardModalOpen} onClose={()=>{this.props.closeModal()}} >
<Modal.Header>Select a Photo</Modal.Header>
<Modal.Content image>
<Image wrapped size='medium' src='https://react.semantic-ui.com/images/avatar/large/rachel.png' />
<Modal.Description>
<Header>Default Profile Image</Header>
<p>We've found the following gravatar image associated with your e-mail address.</p>
<p>Is it okay to use this photo?</p>
</Modal.Description>
</Modal.Content>
<Button onClick={()=>{this.props.closeModal()}}>Close</Button>
</Modal>
)
}
}
export default DetailModal;
Here is a codesandbox with issue reproduced https://codesandbox.io/s/jjk7nw647y
In codesandbox you shared there is no clickable trigger-element for any of the modals. That's because of you are rendering an empty content of Card.
Here is my changes to your example https://codesandbox.io/s/l97n95n2om
The only difference is line #20 - I added a text Some text so your Card component has valid visible (and clickable) DOM element.
Do not forget bind your state handler functions:
constructor(props){
super(props)
this.showCardModal=this.showCardModal.bind(this)
this.closeModal =this.closeModal.bind(this)
}

ReactJS: Separating Components Best Practices

So, I have a react-bootstrap nav and I want to have one of the nav items open and close a bootstrap modal component.
I have this working with this:
import React, { Component, render } from 'react';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem, Modal, Button } from 'react-bootstrap';
export default class NavigationBar extends Component {
constructor() {
super();
this.state = {
showModal: false
}
}
close() { this.setState({ showModal: false }); }
open() { this.setState({ showModal: true }); }
render() {
return (
<div>
<Navbar>
...entire navbar...
</Navbar>
<Modal show={this.state.showModal} onHide={() => this.close()}>
...entire modal... which would be nice to put if a different file
</Modal>
</div>
); } }
Ideally, I would like to put the modal in a different component file and import it in, but when I do, I'm lost on how to translate the navbar open and close.
What is the best practice for combining components while maintaining their state across files?
Thanks!
A good way to think about it is containers vs presentational components. Containers hold your state and most of your logic. Presentational components take in inputs (props) and render html (jsx) [and do little else].
So, you could make your own Modal component that takes in the methods to call on close and one on whether or not it's shown. It could even be a stateless component - if it's just props + jsx, no need for a full class structure:
import React, { PropTypes } from 'react';
const MyModal = ({ show, onHide}) => (
<Modal show={show} onHide={onHide}>
// ...entire modal...
</Modal>
);
// displayName and propTypes are always good to have
MyModal.displayName = 'MyModal';
MyModal.propTypes = {
show: PropTypes.bool.isRequired,
onHide: PropTypes.func.isRequired,
};
export default MyModal;
then to use it, you will need to make sure to bind your methods so they're called in the right context:
class NavigationBar extends Component {
constructor() {
super();
this.state = {
showModal: false
};
// this is the important binding
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
close() { this.setState({ showModal: false }); }
open() { this.setState({ showModal: true }); }
render() {
return (
<div>
<Navbar>
// ...entire navbar...
</Navbar>
<MyModal
show={this.state.showModal}
onHide={this.close}
>
// child content if needed (unless it's all defined in MyModal)
</Modal>
</div>
);
}
}
You can wrap your react-bootstrap Modal with your content into your own custom component like so:
import React from 'react';
import { Modal } from 'react-bootstrap';
const NewModal = ({show, onHide}) => {
<Modal show={show} onHide={onHide}>
Modal content in here
</Modal>
};
export default NewModal;
And then import that modal from your component file
import Modal from 'components/modal' // Import your new modal's default export

Passing props / state in React-Native Navigator

React Native newbie here.
I'm trying to build a simple React Native app for practice that is essentially just a multi-page contact form.
I'm having trouble figuring out the best way to pass props / state to my child components when I'm rendering them via the navigator (navigatorRenderScene function)
Whenever I try to assign props to my component here, it allows me to pass a string but not a function or an object. For example in the component First, I'm trying to pass a string and a function. Only the string will hold its value once the page is rendered.
Am I doing this completely the wrong way? Do I need to look into some kind of state management system like Flux or Redux? Maybe even the redux router package? (Haven't even touched these yet to be honest)
The routing is all working great, it's just the props / state that I can't figure out.
Here's my index
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';
import { Container, Header, Title, Content, List, ListItem, InputGroup, Input, Button, Icon, Picker } from 'native-base'
// a bunch of crap here being imported that I don't need, I'll trim it down later.
import First from './components/First'
import Second from './components/Second'
class App extends Component {
constructor(props) {
super(props);
this.state = {
text : 'initial state',
sentBy : '',
company : '',
phoneNumber : ''
};
this.testFunc = this.testFunc.bind(this)
}
// end constructor
mostRecentPush() {
// firebase stuff, not really important to the problem
console.log('pushing input data to DB')
firebase.database().ref('mostRecent/').set({
'body' : this.state.text,
'sentBy' : this.state.sentBy,
'location' : this.state.pickerValue,
'company' : this.state.company
});
firebase.database().ref('emails/').push({
'body' : this.state.text,
'sentBy' : this.state.sentBy,
'location' : this.state.pickerValue,
'company' : this.state.company
})
}
// end mostRecentPush() / firebase stuff
onButtonPress() {
this.props.navigator.push({
id: 'Second'
})
} // end onButtonPress
testFunc() {
console.log('calling this from the index')
}
render() {
console.log('rendering home')
return (
<Navigator
initialRoute = {{
id: 'First'
}}
renderScene={
this.navigatorRenderScene
}
/>
)
} // end render
navigatorRenderScene(route, navigator) {
_navigator = navigator;
switch (route.id){
case 'First':
return(<First testString="cat123" testFunc={this.testFunc} navigator={navigator} title="First" />)
// passing our navigator value as props so that the component has access to it
case 'Second':
return(<Second navigator={navigator} title="Second" />)
case 'Third':
return(<Third navigator={navigator} title="Third" />)
case 'Fourth':
return(<Fourth navigator={navigator} title="Fourth" />)
case 'Fifth':
return(<Fifth navigator={navigator} title="Fifth" />)
} //end switch
} // end navigatorRenderScene
} // end component
AppRegistry.registerComponent('App', () => App);
And here's an example component, First
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';
import { Container, Header, Title, Content, List, ListItem, InputGroup, Input, Button, Icon, Picker } from 'native-base'
// too much stuff being imported, will clean this up later
class First extends Component {
constructor(props) {
super(props)
this.onButtonPress = this.onButtonPress.bind(this)
}
onButtonPress() {
this.setState({
text: 'changed state from first component'
})
console.log(this.state)
this.props.navigator.push({
id: 'Second'
})
}
render() {
return(
<Container>
{console.log('rendering first')}
<Content>
<List>
<ListItem>
<InputGroup>
<Input
placeholder='Name'
/>
</InputGroup>
</ListItem>
</List>
<TouchableHighlight onPress={this.onButtonPress}>
<Text>Go to Page 2</Text>
</TouchableHighlight>
</Content>
</Container>
)
}
}
export default First
I think the the problem it's with the scope. When setting the renderScene prop to the navigator, you are not binding that function to the actual class, therefore when the navigatorRenderScene gets executed, the scope it's not the same as testFunc.
Try changing this line of code:
navigatorRenderScene(route, navigator) {
//...
}
For this:
navigatorRenderScene = (route, navigator) => {
// ...
}
The arrow function will bind the navigatorRenderScene to the class, therefore this.testFunc will exist there.
Other options to do the binding
If you don't want to use an arrow function, you could try something like this in the constructor.
// First options
this.navigatorRenderScene = this.navigatorRenderScene.bind(this)
Or you can also use the bind method directly on the callback
// Second option
renderScene={
this.navigatorRenderScene.bind(this)
}
Or an arrow function as well
// Third option
renderScene={
(route, navigator) => this.navigatorRenderScene(route, navigator)
}
Let me know if this works. Good luck!

Categories

Resources