Not sure what I am doing wrong here??
In a simple react-redux web app I can't get the new date to set.
The onChange should be setting the selection to the new state for the 'purchase_date'??
Here is my code...
import React, { Component } from 'react';
import { connect } from 'react-redux';
import DatePicker from 'react-datepicker';
import moment from 'moment';
import 'react-datepicker/dist/react-datepicker.css';
// CSS Modules, react-datepicker-cssmodules.css
// import 'react-datepicker/dist/react-datepicker-cssmodules.css';
class DateSelect extends Component {
constructor (props) {
super(props)
this.state = {
startDate: moment()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(date) {
this.setState({
selected: date
});
}
render() {
return <DatePicker
selected={this.state.startDate}
onChange={this.purchase_date}
/>;
}
}
export default DateSelect;
///////
<TableRow selectable={false} key={id}>
<TableRowColumn>{item_description}</TableRowColumn>
<TableRowColumn>{purchase_date}</TableRowColumn>
<TableRowColumn id="count-me">${item_price}
**<DatePicker
selected={this.state.startDate}
onChange={this.purchase_date}
/>**
Firstly, you are binding your handler but aren't actually using it. Additionally, you may wish to pass in a date via your props so it can be set to an initial value that is not the current date (defaulting to current date if not specified:
class DateSelect extends Component {
constructor (props) {
super(props);
this.state = {
startDate: moment()
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(date) {
this.setState({
selected: date
});
}
render() {
return <DatePicker
selected={this.props.selected || this.state.startDate}
onChange={this.handleChange} // <- attaches handleChange to onChange
/>;
}
}
Now, down in your JSX for your table, you only need to call for your component. you should call your custom component instead of DatePicker so that it loads with your behaviors and allows you to do things like pass default or stored values to the components props:
<TableRow selectable={false} key={id}>
<TableRowColumn>{item_description}</TableRowColumn>
<TableRowColumn>{purchase_date}</TableRowColumn>
<TableRowColumn id="count-me">${item_price}
<!-- DateSelect component, passing the 'selected' prop as
an value in someStartDate -->
<DateSelect selected={someStartDate} />
Related
Is there a way to read the options before using the mergeOptions function.
I'm trying to add a sideMenu that opens and closes with the same button. But to handle that logic, Instead of making use of redux, I want to read the options before the merge, so I can simply do something like visible: !pastVisible.
navigationButtonPressed({ buttonId }) {
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
'left': {
visible: false
}
}
});
console.log(`Se presiono ${buttonId}`);
}
So basically I want to read the value of the visible option before changed it.
By now, I can only achieve this using redux.
import React, {Component} from 'react';
import {View, Text} from 'react-native';
import { Navigation } from 'react-native-navigation';
import { connect } from 'react-redux';
import { toggleSideMenu } from './../../store/actions/index';
class SideDrawer extends Component {
constructor(props) {
super(props);
Navigation.events().registerComponentDidDisappearListener(({ componentId }) => {
this.props.toggleSideMenu(false);
});
}
render() {
return (
<View>
<Text>This is the sidedrawer</Text>
</View>
);
}
}
const mapDispatchToProps = dispatch => {
return {
toggleSideMenu: (visible) => dispatch(toggleSideMenu(visible))
};
};
export default connect(null, mapDispatchToProps)(SideDrawer);
Then I just add the listeners to the sidemenu component. Depending on the case, I update the current state of the component (visible or not).
Finally on the components where I want to use the side drawer button I just implement the navigationButtenPressed method. Then I just call the reducer to know the current visible state and toggled it.
navigationButtonPressed({ buttonId }) {
const visible = !this.props.sideMenu;
Navigation.mergeOptions(this.props.componentId, {
sideMenu: {
'left': {
visible: visible
}
}
});
this.props.toggleSideMenu(visible);
}
If there is a more easy way to achieve this I'll be glad to know about it.
I've been struggling passing a value from one component to another. It's a continuation of the issue from a previous question which was partially resolved: react-native tab navigator search box
I'm using tab navigator and here's my app setup:
index.js (renders tab setup)
router.js
searchHeader.js
tab1.js
tab2.js
etc
In index.js when a tab is changed I'm getting the name of the tab. I want to pass that to searchHeader.js to update the placeholder text.
As searchHeader.js isn't imported into index.js and not a direct child how do I pass it that value?
index.js
import React, { Component } from 'react';
import { Root, Tabs } from './config/router';
import { Alert,View } from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
}
}
_getCurrentRouteName(navState) {
if (navState.hasOwnProperty('index')) {
this._getCurrentRouteName(navState.routes[navState.index])
} else {
if (navState.routeName==='One') {
this.setState({searchText:'Search One'})
}
if (navState.routeName==='Two') {
this.setState({searchText:'Search Two'})
}
if (navState.routeName==='Three') {
this.setState({searchText:'Search Three'})
}
if (navState.routeName==='Four') {
this.setState({searchText:'Search Four'})
}
}
}
render() {
return (
<Root onNavigationStateChange={(prevState, newState) => {
this._getCurrentRouteName(newState)
}} />
)
}
}
export default App;
router.js
...
export const Root = StackNavigator({
Tabs: {
screen: Tabs,
navigationOptions: {
header: <SearchHeader data={'Test'} />
}
},
}, {
mode: 'modal',
});
searchHeader.js
import React, { Component } from 'react';
import { View,Text,Dimensions,Alert } from 'react-native';
import { SearchBar } from 'react-native-elements';
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
placeholder: "Search One"
}
}
render() {
return (
<SearchBar
noIcon
containerStyle={{backgroundColor:'#fff'}}
inputStyle={{backgroundColor:'#e3e3e3',}}
lightTheme = {true}
round = {true}
placeholder={data}
placeholderTextColor = '#000'
/>
);
}
};
export default SearchHeader;
You could perhaps pass it as a navigation prop using the setParams method.
An alternative, depending on the scope of your app, would be to look at a state library such as Redux or MobX - but if it's a small app, it's overkill
For that you can use Redux, you will have a store where you can put shared properties and values,
Then your components can connect to that store and bind its props with the chosen reducer(s) and dispatch actions..
this structure may work:
class Home extends Component {
func(val) {
this.setState({value: val});
}
render() {
return (
<View>
<Two func={(val) => this.func(val)} />
</View>
)
}
}
class Two extends Component {
render() {
return (
<View>
<Button title="set" onPress={() => this.props.func('data')} />
</View>
)
}
}
How should I access the properties of an element without using the 'this' keyword in React Native? I have a function with which the parent class itself is bound as 'this' but I want to access the properties of the element that is being clicked. Here's the code-
import {Circle} from 'react-native-svg';
export default App extends Component {
constructor(props) {
super(props);
this.state = {activeX: null}
}
handleTouch(event) {
const x = event.target.cx; //How to access "cx" property here?
this.setState({ activeX: x });
}
render() {
return (
<Circle cx='10' cy='10' r='5' onPress={this.handleTouch.bind(this)}/>
<Circle cx='20' cy='20' r='5' onPress={this.handleTouch.bind(this)}/>
);
}
}
Try this
import {Circle} from 'react-native-svg';
export default App extends Component {
constructor(props) {
super(props);
this.state = {
activeX: null,
cx: 10
}
}
handleTouch = () => {
const x = this.state.cx
this.setState({ activeX: x });
}
render() {
return (
<Circle cx={this.state.cx} cy='10' r='5' onPress={this.handleTouch}/>
);
}
}
import ReactNativeComponentTree from'react-native/Libraries/Renderer/src/renderers/native/ReactNativeComponentTree';
And access the properties as-
const x = ReactNativeComponentTree.getInstanceFromNode(event.currentTarget)._currentElement.props.cx;
Sorry for leaving an answer but I cannot leave a comment since <50 rep.
You should edit the improve part of your answer, with the following bit:
import ReactNativeComponentTree from 'react-native';
instead of what you have right now,
import ReactNativeComponentTree from'react-native/Libraries/Renderer/src/renderers/native/ReactNativeComponentTree';
since is throwing an error (trying to import unknown module).
A better way of accessing the component properties in an event is actually by creating a component and passing it the needed data:
import { Circle } from 'react-native-svg';
class TouchableCircle extends React.PureComponent {
constructor(props) {
super(props);
this.circlePressed = this.circlePressed.bind(this);
}
circlePressed(){
this.props.onPress(this.props.cx);
}
render() {
return (
<Circle cx={this.props.cx} cy={this.props.cy} r={this.props.r} onPress={this.circlePressed}/>
);
}
}
export default App extends Component {
constructor(props) {
super(props);
this.state = {activeX: null}
this.handleTouch = this.handleTouch.bind(this);
}
handleTouch(cx) {
this.setState({ activeX: cx });
}
render() {
return (
<TouchableCircle cx='10' cy='10' r='5' onPress={this.handleTouch}/>
<TouchableCircle cx='20' cy='20' r='5' onPress={this.handleTouch}/>
);
}
}
NB: Performance tip from Facebook for event handlers:
We generally recommend binding in the constructor or using the property initializer syntax, to avoid this sort of performance problem. (i.e. to avoid the creation of the callback everytime a component renders)
ref: React Handling Events
(credits to https://stackoverflow.com/a/42125039/1152843)
You can change your event handler to a curried function like so:
import {Circle} from 'react-native-svg';
export default App extends Component {
constructor(props) {
super(props);
this.state = {activeX: null}
}
//Use ES6 arrow and avoid this.bind
//Curried function handleTouch accepts cx, cy as extra parameters
handleTouch = (cx, cy) => event => {
console.log(cx, cy) // This is how you access props passed to Circle here
console.log(event)
this.setState({ activeX: cx });
}
render() {
//You are actually invoking the handleTouch function here, whose return value is
//a function, which is set as the onPress event handler for the <Circle> component
return (
<Circle cx='10' cy='10' r='5' onPress={this.handleTouch(10, 10)}/>
<Circle cx='20' cy='20' r='5' onPress={this.handleTouch.(20, 20)}/>
);
}
}
Checkout the working snack below:
https://snack.expo.io/#prashand/accessing-props-from-react-native-touch-event
I have a list of items (jobs) and when an item (job) is being selected, a new scene is being opened. I want the ID of the selected item to be passed from the scene with the list to the other scene with the details about the selected item (job) without using Redux.
Router
import React from 'react';
import { Scene, Router } from 'react-native-router-flux';
import JobsList from './components/JobsList';
import Job from './components/Job';
const RouterComponent = () => {
return (
<Router>
<Scene key="jobs" component={JobsList} initial />
<Scene key="Job" component={Job} title="Test" />
</Router>
);
};
export default RouterComponent;
Jobs list
import React, { Component } from 'react';
export default class JobsList extends Component {
render() {
return (
<TouchableOpacity onPress={() => { Actions.Job({ jobId: jobId }) }}>
...
</TouchableOpacity>
);
}
}
Job
import React, { Component } from 'react';
export default class Job extends Component {
constructor() {
super();
this.state = {
job: {}
};
axios.get(
// PROBLEM: this.props.jobId is empty
`http://api.tidyme.dev:5000/${this.props.jobId}.json`,
{
headers: { Authorization: 'Token token=123' }
}
).then(response => this.setState({
job: response.data
}));
}
render() {
return (
<Text>{this.state.job.customer.firstName}</Text>
);
}
}
You should call super(props) if you want to access this.props inside the constructor.
constructor(props) {
super(props);
console.log(this.props);
}
The best practice is defining Components as pure functions:
const Job = ({ job, JobId}) => {
return (
<Text>{job.customer.firstName}</Text>
);
}
otherFunctions() {
...
}
In my react native app I'm using redux to handle state transition of a Post object -- the state is changed by couple of child components. The Post object has properties like title, name, description which the user can edit and Save.
In the reducer Im using React.addons.update return new state object.
The main container view has 2 custom child components (wrapped in TabBarNavigator).
One of the child component has few TextInputs which is updating a state.
Using the logger middleware and console.log() I see the new state value in the parent view's render() (via this.props.name) but not in the child view.
I'm trying to figure out why the updated state is not propagated to the child container. Any suggestion is much appreciated.
Im at a point where Im thinking of subscribeing to the redux store manually in the child container but it feels wrong
my code looks like this:
MainView
Reducer
configure store etc
The MainView
const React = require('react-native');
const {
Component,
} = React;
const styles = require('./../Styles');
const MenuView = require('./MenuView');
import Drawer from 'react-native-drawer';
import TabBarNavigator from 'react-native-tabbar-navigator';
import BackButton from '../components/BackButton';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as PostActions from '../actions/Actions';
import {Details} from './Article/Details';
import {ArticleSecondary} from './Article/Secondary';
var update = require('react-addons-update');
import configureStore from '../store/configureStore';
class ArticleMainView extends Component {
constructor(props){
super(props);
//var store = configureStore(props.route.post);
this.state = {
};
}
componentDidMount(){
}
savePost() {
console.log(this.props.post.data);
this.props.navigator.pop();
}
render(){
console.log("ArticleMainView: render(): " + this.props.name);
return(
<TabBarNavigator
ref="navComponent"
navTintColor='#346293'
navBarTintColor='#94c1e8'
tabTintColor='#101820'
tabBarTintColor='#4090db'
onChange={(index)=>console.log(`selected index ${index}`)}>
<TabBarNavigator.Item title='ARTICLE' defaultTab>
<Details ref="articleDetail"
backButtonEvent={ () => {
this.props.navigator.pop();
}}
saveButtonEvent={ () => {
this.savePost();
}}
{...this.props}
/>
</TabBarNavigator.Item>
<TabBarNavigator.Item title='Secondary'>
<ArticleSecondary ref="articleSecondary"
{...this.props}
backButtonEvent={ () => {
this.props.navigator.pop();
}}
saveButtonEvent={ () => {
this.savePost();
}}
/>
</TabBarNavigator.Item>
</TabBarNavigator>
);
}
}
function mapStateToProps(state) {
return {
post: state,
text: state.data.text,
name: state.data.name,
description: state.data.description
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(PostActions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ArticleMainView);
The Reducer:
import {Constants} from '../api/Constants';
var update = require('react-addons-update');
export default function postReducer(state, action) {
switch(action.type) {
case Constants.SET_POST_TEXT:
if( state.data.text){
return update(state, {
data: { $merge: {text: action.text }}
});
}else{
return update(state, {
data: { $merge: {text: action.text }}
});
}
break;
case Constants.SET_POST_NAME:
return update(state, {
data: { name: { $set: action.text }}
});
return newO;
break;
case Constants.SET_POST_DESCRIPTION:
return update(state, {
data: { description: { $set: action.text }}
});
break;
default:
return state;
}
}
render scene of the app:
renderScene(route, navigator) {
switch (route.id) {
case "ArticleMainView":
let store = configureStore(route.post);
delete route.post; // TODO: not sure if I should remove this
return (
<Provider store={store}>
<ArticleMainView navigator={navigator} {...route}/>
</Provider>
);
default:
return <LandingView navigator={navigator} route={route}/>
}
}
configureStore:
import { createStore,applyMiddleware,compose } from 'redux'
import postReducer from '../reducers/SocialPostReducer';
import createLogger from 'redux-logger';
const logger = createLogger();
export default function configureStore(initialState){
return createStore(
postReducer,
initialState,
compose(applyMiddleware(logger))
);
}
If anyone stumbles on this question this is how I solved it. In each of the child components I declared a contextTypes object like so
ChildComponentView.contextTypes = {
store: React.PropTypes.object
}
to access the current state in the child component
let {store} = this.context;
store.getState();
I don’t know React Native well but something that threw me off is that you’re effectively creating a store on every render:
case "ArticleMainView":
let store = configureStore(route.post);
delete route.post; // TODO: not sure if I should remove this
return (
<Provider store={store}>
<ArticleMainView navigator={navigator} {...route}/>
</Provider>
);
Store should only be created once per application lifetime. It never makes sense to create it inside render() or renderScene() or similar methods. Please check the official Redux examples to see how the store is typically created.
Another problem is that you don’t show how you update the data, which child component doesn’t get updated, when you expect it to get updated, and so on. This is a lot of code, and it is very hard to help because it is incomplete, and most of it is not relevant to the problem. I would suggest you to remove all the irrelevant code until you can reproduce the problem with a minimal possible complete example. Then you can amend your question to include that example.