I'm using https://github.com/gcanti/tcomb-form-native
& here's the snippet of code
_renderScene(route, navigator) {
var Cook_time = {
onFocus: () => {
console.log('cook time has focus');
console.log(this.refs);
},
};
options.fields['Cook_time'] = Cook_time;
return (
<View style={{ flex: 1 }}>
<ScrollView style={styles.container} ref="scrollView">
<Form
ref="form"
type={Recipe}
options={options}
onChange={this.onChange}
onFocus={this.onChange}
/>
console.log prints object{} when it should refer to scrollView, not sure what I might be missing. And here's the code that sets up the form itself
var Recipe = t.struct({
Recipe_name: t.String,
yield: t.maybe(t.String),
Prep_time: t.maybe(t.String),
Cook_time: t.maybe(t.String),
source: t.maybe(t.String),
})
var options = {
fields: {
yield: {
label: 'Yield',
},
Prep_time: {
label: 'Preparation time',
},
source: {
label: 'Source',
placeholder: 'family, friends, website ...',
onFocus: function () {
console.log('source has focus');
}
}
}
};
ref attribute is a callback and as per react-native documentation
all I need to do is save a reference as so
<ScrollView style={styles.container} ref={(ref) => this.myScrollView = ref}>
Related
I am building a food delivery application, and I would like to know how I can limit the number of checkboxes selected. An example is when entering the subsidiary, it displays a list of products. If I select a pizza, there is an extras section that limits the number of extras you can select, if you want to select more than two and your limit is two it should not allow you
all this with react hooks, I attach a fragment of my component
const ExtrasSelector = ({options = [{}], onPress = () => {}, limit = 0}) => {
const [showOptions, setShowOptions] = useState(true);
const [selectedAmount, setSelectedAmount] = useState(0);
const EXTRA = ' extra';
const EXTRAS = ' extras';
const updatedList = options.map(data => ({
id: data.id,
name: data.name,
price: data.price,
selected: false,
}));
const [itemsList, setItemsList] = useState(updatedList);
const toggleOptions = () => setShowOptions(!showOptions);
useEffect(() => {
}, [selectedAmount]);
// onPress for each check-box
const onPressHandler = index => {
setItemsList(state => {
state[index].selected = !state[index].selected;
onPress(state[index], getSelectedExtras(state));
// Increments or decreases the amount of selected extras
if (state[index].selected) {
setSelectedAmount(prevState => prevState + 1);
} else {
setSelectedAmount(prevState => prevState - 1);
}
return state;
});
};
const getSelectedExtras = extrasArr => {
const selectedExsArr = [];
extrasArr.map(item => {
if (item.selected) {
selectedExsArr.push(item);
}
});
return selectedExsArr;
};
return (
<View>
<View style={styles.container}>
<TouchableOpacity style={styles.row} onPress={toggleOptions}>
<Text style={styles.boldTitleSection}>
Extras {'\n'}
<Text style={titleSection}>
Selecciona hasta {limit}
{limit > 1 ? EXTRAS : EXTRA}
</Text>
</Text>
<View style={styles.contentAngle}>
<View style={styles.contentWrapperAngle}>
<Icon
style={styles.angle}
name={showOptions ? 'angle-up' : 'angle-down'}
/>
</View>
</View>
</TouchableOpacity>
{showOptions ? (
itemsList.map((item, index) => (
<View key={index}>
<CheckBox
label={item.name}
price={item.price}
selected={item.selected}
otherAction={item.otherAction}
onPress={() => {
onPressHandler(index, item);
}}
/>
<View style={styles.breakRule} />
</View>
))
) : (
<View style={styles.breakRule} />
)}
</View>
</View>
);
};
This is a simple react implementation of "checkboxes with limit" behaviour with useReducer. This way the business logic (here the limitation but can be any) is implemented outside of the component in a pure js function while the component itself is just a simple reusable checkbox group.
const { useReducer } = React; // --> for inline use
// import React, { useReducer } from 'react'; // --> for real project
const reducer = (state, action) => {
if (state.checkedIds.includes(action.id)) {
return {
...state,
checkedIds: state.checkedIds.filter(id => id !== action.id)
}
}
if (state.checkedIds.length >= 3) {
console.log('Max 3 extras allowed.')
return state;
}
return {
...state,
checkedIds: [
...state.checkedIds,
action.id
]
}
}
const CheckBoxGroup = ({ data }) => {
const initialState = { checkedIds: [] }
const [state, dispatch] = useReducer(reducer, initialState)
return (
<table border="1">
{data.map(({ id, label }) => (
<tr key={id}>
<td>
<input
onClick={() => dispatch({ id })}
checked={state.checkedIds.includes(id)}
type="checkbox"
/>
</td>
<td>
{label}
</td>
</tr>
))}
</table>
)
};
const data = [
{ id: "1", label: "Mashroom" },
{ id: "2", label: "Ham" },
{ id: "3", label: "Egg" },
{ id: "4", label: "Ananas" },
{ id: "5", label: "Parmesan" },
]
ReactDOM.render(<CheckBoxGroup data={data} />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.9.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I understand that this.props.isValidUser gets updated after action dispatches the axios promise. if the user is not valid is shows message. If the user is valid user, I want to navigate to another screen to enter pin. How do I navigate to another screen after I get axios result from action?
types.js
export const VALIDATE_USER = "VALIDATE_USER";
export const VALIDATE_PIN = "VALIDATE_PIN";
export const GET_ERRORS = "GET_ERRORS";
Reducer.js
import { VALIDATE_USER, VALIDATE_PIN, GET_ERRORS } from "../actions/types.js";
export default function (state = initialState, action) {
switch (action.type) {
case VALIDATE_USER:
return {
...state,
isValidUser: (action.payload == true) ? true : false,
Id: action.employeeId
};
case VALIDATE_PIN:
return {
...state,
isValidPin: action.payload,
action: "VALIDATE_PIN",
};
default:
return state;
}
}
action.js
import { GET_ERRORS, VALIDATE_USER, VALIDATE_PIN, } from "./types";
export const validateUser = (empId) => dispatch => {
axios.get(`${API}/api/Account/ValidateMobileAppUser?employeeId=${empId}`)
.then(res => {
dispatch({
type: VALIDATE_USER,
payload: res.data,
Id: empId,
});
})
.catch(err => {
dispatch({
type: VALIDATE_USER,
payload: false,
Id: empId
});
});
};
Login.js
import PropTypes from "prop-types";
import { validateUser } from "../actions/authActions";
class Login extends PureComponent {
constructor() {
super();
this.state = {
employeeId: "",
pin: "",
isValidUser: false,
};
this.onValidateUser = this.onValidateUser.bind(this);
this.onEmployeeId = this.onEmployeeId.bind(this);
}
onEmployeeId(employeeId) {
this.setState({ employeeId });
}
onValidateUser() {
this.props.validateUser(this.state.employeeId);
}
render() {
const { loading } = this.props.loading;
return (
<KeyboardAvoidingView style={styles.login} >
<ScrollView showsVerticalScrollIndicator={false}>
<Block padding={[10, theme.sizes.base * 2]} onPress={Keyboard.dismiss}>
<Block middle>
<Input
placeholder={this.state.placeholder}
keyboardType={this.state.keyboardType}
style={[styles.input]}
value={this.state.employeeId}
onChangeText={this.onEmployeeId}
/>
{(this.props.isValidUser == false) ? (
<Text center style={{ color: "#C00000", marginTop: 15, fontSize: 14 }}>
Employee Id not registered. Please contact HR.
</Text>
) : ""}
<Button
gradient
style={styles.loginButton}
onPress={this.onValidateUser}
>
<Text white center>
Login
</Text>
</Button>
</Block>
<Button
onPress={() => this.onGoToStep(1)}
style={{
borderWidth: 1,
borderRadius: 30,
borderColor: "#E46932"
}}
>
<Text gray caption center style={{ color: "#E46932" }}>
Don't have an account? Sign Up
</Text>
</Button>
</Block>
</ScrollView>
</KeyboardAvoidingView>
);
}
}
Login.propTypes = {
validateUser: PropTypes.func.isRequired,
errors: PropTypes.object.isRequired
};
function reducerCallback(state, ownProps) {
if (state.auth.isValidUser == true) {
ownProps.navigation.navigate("mPin", { Id: state.auth.employeeId, type: "LOGIN" });
}
}
const mapStateToProps = (state, ownProps) => ({
auth: reducerCallback(state, ownProps),
isValidUser: state.auth.isValidUser,
errors: state.errors
});
export default connect(
mapStateToProps,
{
validateUser,
}
)(Login);
this.props.isValidUser == false tells me if the user is valid or not. But if the user is valid I'm navigating to another screen using reducerCallback() function. I'm not aware if this is the correct way to do so. My question is how to I navigate to another screen after I get return result from async axios action and How to I set local state using setState when I get callback from axios dispatch. Please guide
Try to below code:
login.js:
onValidateUser() {
this.props.validateUser({
empId: this.state.employeeId,
onSuccess: () => {
//Navigate to other screen
},
onFailure: () => {
//Alert error message
},
});
}
Action.js:
export const validateUser = ({empId, onSuccess, onFailure}) => dispatch => {
axios
.get(
`${API}/api/Account/ValidateMobileAppUser?employeeId=${empId}`
)
.then(res => {
dispatch({
type: VALIDATE_USER,
payload: res.data,
Id: empId,
});
onSuccess();
})
.catch(err => {
dispatch({
type: VALIDATE_USER,
payload: false,
Id: empId
});
onFailure()
});
};
I want to select only one checkbox, not multiple.
If i select two checkboxes one by one the previously selected checkbox should be unselected.
In my below code i can select multiple checkboxes.
import React ,{Component} from "react";
import CircleCheckBox, {LABEL_POSITION} from "react-native-circle-checkbox";
class Select_Delivery_Option extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
});
this.state = {
check_data:[],
dataSource: ds.cloneWithRows([]),
checked:false,
isLoading:false,
};
}
//I had call The componentDidMount for json Data here and bind it in Data source;
render() {
return ();
}
_renderRow(rowData: string, sectionID: number, rowID: number) {
return (
<View style={{ flex:1,flexDirection:'column',backgroundColor:'#FFF'}}>
<View style={{ flex:1,flexDirection:'row',backgroundColor:'#FFF'}}>
<View style={{flexDirection:'column',margin:10}}>
{rowData.adbHomeAddress}
<CircleCheckBox
checked={rowData.checked}
onToggle={()=>this._onPressRow(rowID, rowData,rowData.checked)}
labelPosition={LABEL_POSITION.LEFT}
label={rowData.Address1 +" ,\n "+ rowData.Address2 +",\n"+rowData.ctiName+", "+rowData.staName+", "+rowData.ctrName+","+rowData.adbZip+"."}
innerColor="#C72128"
outerColor="#C72128"
styleLabel={{color:'#000',marginLeft:10}}
/>
</View>
</View>
</View>
);
}
_onPressRow = (rowID,rowData,checked) => {
const {check_data,filter} = this.state;
console.log('rowdata',rowData);
console.log('rowid',rowID);
console.log('checked',checked);
rowData.checked = !rowData.checked;
var dataClone = this.state.check_data;
dataClone[rowID] = rowData;
this.setState({check_data: dataClone });
}
}
Link to the CircleCheckBox component used: https://github.com/paramoshkinandrew/ReactNativeCircleCheckbox
I had the same requirement and wasted hours looking for solution. Eventually, I was able to resolve the problem on my own.
Posting my answer below, l have used hooks in the example, let me know if someone wants a class-based solution.
const checkboxComponent = () => {
const [checkboxValue, setCheckboxValue] = React.useState([
{ label: 'Customer', value: 'customer', checked: false },
{ label: 'Merchant', value: 'merchant', checked: false },
{ label: 'None', value: 'none', checked: false },
])
const checkboxHandler = (value, index) => {
const newValue = checkboxValue.map((checkbox, i) => {
if (i !== index)
return {
...checkbox,
checked: false,
}
if (i === index) {
const item = {
...checkbox,
checked: !checkbox.checked,
}
return item
}
return checkbox
})
setCheckboxValue(newValue)
}
return (
<View>
{checkboxValue.map((checkbox, i) => (
<View style={styles.checkboxContainer} key={i}>
<CheckBox
value={checkbox.checked}
onValueChange={(value) => checkboxHandler(value, i)}
/>
<Text style={styles.label}>{checkbox.label}</Text>
</View>
))}
</View>
)
}
export default checkboxComponent
I suggest you to use FlatList instead of ListView it's more advance and easy to use component.
For your issue please create a state checkedItem: -1 and directly assign id of your item you check last then just add a check to your CircleCheckBox item. something like below code.
<CircleCheckBox
checked={rowData.id === this.state.checkedItem}
onToggle={(rowID)=> this.setState({ checkedItem: rowID})}
labelPosition={LABEL_POSITION.LEFT}
label={rowData.Address1 +" ,\n "+ rowData.Address2 +",\n"+rowData.ctiName+", "+rowData.staName+", "+rowData.ctrName+","+rowData.adbZip+"."}
innerColor="#C72128"
outerColor="#C72128"
styleLabel={{color:'#000',marginLeft:10}}
/>
Let me know if any query.
I currently developing a react native app ( version 0.55.2) and mapbox/react-native (version 6.1.2-beta2)
I have a situation where some annotations are shown initially on map render, then further annotations are loaded when the user's zooms.
The first annotations are displayed at the right place.
However, when new annotations are added, there are all stuck at the top left corner.
Following their documentation, https://github.com/mapbox/react-native-mapbox-gl/blob/master/docs/MapView.md, I tried to call the function when the map is loaded or rendered. I even tried a setTimeout. The annotations always appears at the topleft map.
Any ideas how should I approach this?
THanks!
class map extends React.Component {
constructor(props) {
super(props);
this.getMapVisibleBounds = getMapVisibleBounds.bind(this);
this.state = {
...INIT_MAP_STATE
};
}
//compo lifecyle
componentDidUpdate(prevProps, prevState) {
if (this.state.userPosition.longitude !== prevState.userPosition.longitude) {
this.setBounds();//first annotations. works fine
}
if (this.state.zoomLevel !== prevState.zoomLevel) {
this.setBounds(); //update annotations. doesn't work
}
}
render()=>{
const { quest, checkpoint } = this.props;
const { selectedIndex } = this.state;
return (
<View style={styles.container}>
<Mapbox.MapView
styleURL={MAP_STYLE}
zoomLevel={this.state.zoomLevel}
centerCoordinate={[this.state.userPosition.longitude,
this.state.userPosition.latitude]}
style={styles.mapWrap}
>
{this.renderMap(checkpoint, "checkpoint")}
</Mapbox.MapView>
</View>
);
}
setBounds = () => {
this.getMapVisibleBounds(this.map)
.catch(err => {
console.error(err);
})
.then(bounds => {
this._setMapBounds(bounds);// set state bounds
return this.props.onLoadQuest(bounds); //api call
});
}
}
// annotations rendering
class checkPoint extends Component {
constructor(props) {
super(props);
}
renderAnnotations = (data, id) => {
const uniqKey = `checkpoint_${id}`;
return (
<Mapbox.PointAnnotation key={uniqKey} id={uniqKey} coordinate={[data[0], data[1]]}>
<TouchableWithoutFeedback onPress={idx => this.onSelect(id)}>
<Image source={checkPointImg} style={styles.selfAvatar} resizeMode="contain" />
</TouchableWithoutFeedback>
</Mapbox.PointAnnotation>
);
};
render() {
if (!this.props.checkpoint || isEmpty(this.props.checkpoint)) {
return null;
}
const { hits } = this.props.checkpoint;
if (!Array.isArray(hits)) {
return [];
}
return hits.map((c, idx) =>
this.renderAnnotations(c._source.location.coordinates, c._source.id)
);
}
}
"PointAnnotation" is legacy, try passing your points to as an object. You're map render will be so much faster once you make the swap. Something like this.
<MapboxGL.MapView
centerCoordinate={[ userLocation.longitude, userLocation.latitude ]}
pitchEnabled={false}
rotateEnabled={false}
style={{ flex: 1 }}
showUserLocation={true}
styleURL={'your_style_url'}
userTrackingMode={MapboxGL.UserTrackingModes.MGLUserTrackingModeFollow}
zoomLevel={10}
>
<MapboxGL.ShapeSource
key='icon'
id='icon'
onPress={this._onMarkerPress}
shape={{type: "FeatureCollection", features: featuresObject }}
type='geojson'
images={images}
>
<MapboxGL.SymbolLayer
id='icon'
style={layerStyles.icon}
/>
</MapboxGL.ShapeSource>
</MapboxGL.MapView>
Where "featuresObject" looks something like this...
let featuresObject = []
annotation.forEach((annot, index) => {
let lat = annot.latitude
let lng = annot.longitude
featuresObject[index] = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [lng, lat]
},
properties: {
exampleProperty: propertyValue,
}
}
})
Example for polygon layer
Example with custom icon
You can add markers dynamically by using this code:
Create marker component:
const Marker = ({ coordinate, image, id }) => {
return (
<MapboxGL.MarkerView coordinate={coordinate} id={id}>
// Add any image or icon or view for marker
<Image
source={{ uri: image }}
style={{width: '100%', height: '100%'}}
resizeMode="contain"
/>
</MapboxGL.MarkerView>
);
};
Consume it inside MapBoxGL:
<MapboxGL.MapView
style={{
// it will help you keep markers inside mapview
overflow: 'hidden'
}}>
{markers &&
markers?.length > 0 &&
markers.map((marker, index) => (
<Marker
coordinate={[marker.longitude, marker.latitude]}
// id must be a string
id={`index + 1`}
image={getIconUrl(index)}
/>
))
}
</MapboxGL.MapView>
const layerStyles = Mapbox.StyleSheet.create({
icon: {
iconImage: "{icon}",
iconAllowOverlap: true,
iconSize: 0.5,
iconIgnorePlacement: true
}
});
const mapboxIcon = props => {
return (
<Mapbox.ShapeSource
shape={makeMapBoxGeoJson(props.datum, props.mapKey, props.name)}
key={`${props.name}_key_${props.mapKey}`}
id={`${props.name}_${props.mapKey}`}
images={getIcon(props.name)}
onPress={idx => (props.isActive ? props.onSelectId(props.mapKey) : null)}
>
<Mapbox.SymbolLayer
id={`${props.mapKey}_pointlayer`}
style={[layerStyles.icon, { iconSize: props.iconSize ? props.iconSize : 0.5 }]}
/>
</Mapbox.ShapeSource>
);
};
I am trying to access one of my actions from within the my row onPress function but the issue is in the onPress it is out of scope from the rest of the state.
My question is how do I access the state functions to call from within my onPress method.
import {planLocalesFetch, localeDelete} from '../actions';
import LocaleListItem from './LocaleListItem';
import Swipeout from 'react-native-swipeout';
class PlanLocalesList extends Component {
// all the state code is here
renderRow(planLocale) {
let swipeBtns = [{
text: 'Delete',
fontWeight: 'bold',
backgroundColor: 'red',
onPress: () => {
axios.delete(`http://localhost:3000/locales/${planLocale.id}`, { params: {
locale_id: planLocale.id }});
this.props.planLocalesFetch(plan); // error is that this does not exist in this scope;
}
}];
return (
<Swipeout right={swipeBtns}
backgroundColor= 'transparent'>
<View>
<LocaleListItem planLocale={planLocale} />
</View>
</Swipeout>
)
}
render () {
return (
<View style={{flex: 1}}>
<ListView
dataSource={this.dataSource}
renderRow={this.renderRow.bind(this)}
/>
</View>
);
}
}
const mapStateToProps = state => {
const plan = state.planLocales.plan
const planLocales = _.map(state.planLocales.locales, (val, uid) => {
return { ...val, uid };
});
return { planLocales, plan};
};
export default connect(mapStateToProps, {planLocalesFetch, localeDelete})(PlanLocalesList);
Here is the action to fetch the data for the list.
export const planLocalesFetch = (plan) => {
return (dispatch, state) => {
if (plan) {
state().planForm.currentPlan = plan;
}
var plan_id = state().planForm.currentPlan.id;
axios.get(`http://localhost:3000/plans/${plan_id}`).then((response) => {
dispatch({type: PLAN_LOCALES_FETCH, payload: response.data})
});
};
};
The problem I am having is the this.props.planLocalesFetch(plan); does not exist in the nested onPress scope.
fix using the .then statement
let swipeBtns = [{
text: 'Delete',
fontWeight: 'bold',
backgroundColor: 'red',
onPress: () => {
axios.delete(`http://localhost:3000/locales/${planLocale.id}`, { params: {
locale_id: planLocale.id }}).then(() => {
this.props.planLocalesFetch();
})