I have a simple form that submits an update request to a server. I would like to display a success/error message for a few seconds after the form is submitted and then reset the form. However, if the user happens to submit subsequent requests in between the last submit and the end of the delay to perform the reset, then I don't want to perform the reset.
To accomplish this I was hoping to use the submitCount found in the FormState. However, I am finding that the submitCount is resetting automatically - even when I don't call reset at all. Also when I call reset and pass to it keepSubmitCount: true
Here's a skeleton of what I'm doing:
import { IToolPanelParams, SelectionChangedEvent } from "ag-grid-community";
import React, { useEffect, useMemo, useState } from "react";
import styled from "styled-components";
import { FieldValues, SubmitErrorHandler, SubmitHandler, useForm, useFormState} from "react-hook-form";
interface MyFieldValues extends FieldValues {
note: string;
}
export const MyToolPanel = (props: IToolPanelParams) => {
const [selected, setSelected] = useState<Record[]>();
const { register, handleSubmit, reset, control } = useForm<MyFieldValues>();
const { errors, submitCount, isSubmitting, isSubmitted, isSubmitSuccessful } = useFormState({ control });
...
const onSaveSubmit: SubmitHandler<MyFieldValues> = async (data, e) => {
return peform_update();
}
useEffect(() => {
if(isSubmitted) {
console.log(`successful? (${isSubmitSuccessful}) submit on #${submitCount}`);
const currSubmitCount = submitCount;
setTimeout(() => {
if(!isSubmitting && submitCount == currSubmitCount) {
console.log(`reset after submit #${currSubmitCount}`);
/*reset({
keepValues: true,
keepDefaultValues: true,
keepSubmitCount: true
});
*/
}
}, 3000);
}
}, [isSubmitted, submitCount, reset])
const onError: SubmitErrorHandler<BulkEditNotesFieldValues> = (errors) => {
console.error(`BulkEditNotesToolPanel::onError - errors.note. type: ${errors.note?.type}. types: ${JSON.stringify(errors.note?.types)}. message: ${errors.note?.message}`);
}
return (
<Container>
<Spin spinning={isSubmitting}>
<h2>Bulk Edit Records</h2>
{ !!selected?.length && (<>
<form onSubmit={handleSubmit(onSaveSubmit, onError)}>
<label htmlFor="Note">
<textarea
style={textAreaStyle}
{...register("note", {
required: { value: true, message: 'Note required'},
pattern: { value: /[^\s]/, message: 'Note cannot be empty'}
})}
/>
</label>
{ errors?.note && (
<Error>{errors.note.message}</Error>
)}
<button
type="submit"
style={submitStyle}
disabled={!selected?.length}
>
Save { selected?.length > 1? 'Records' : 'Record' }
</button>
</form>
</>)}
{ isSubmitted && isSubmitSuccessful && (
<SuccessMessage>Successfully Updated The Selected Record(s)</SuccessMessage>
)}
{ updateError && (<>
<Error>An error occurred while attempting to update the selected records...</Error>
</>)}
</Spin>
</Container>
);
};
While I can of course create my own submit counter via useState, I was hoping to figure out why react-hook-form's submitCount is resetting on me and how to prevent that? Thanks!
Related
I've been using React hook form and have a compare validation written on the input itself.
I need to move over to Yup validation lib as others on the project are using it so for consistency. Here is what I currently have that is working but having problems when I use a yup.schema.
const {
register,
setValue,
getValues,
formState: { errors, isValid },
clearErrors,
trigger,
} = useForm({
mode: 'onBlur',
defaultValues: {
password: '',
compare: '',
},
})
return(
<div>
<Input
name='password'
{...register('password', { required: true })}
<Input
name='compare'
{...register('compare', {
validate: (value: string) => {
const { password} = getValues()
return password === value || ''
},
})}
</div>
)
As I said this above works but now I thought I could add a schema but that breaks all what I have so I'm trying to figure out how I can achieve this using yup schema.
const schema = yup.object().shape({
password: yup.string().required(),
compare: yup.string().test(
'compare',
(field) => {
... not sure how to compare against another filed here ?
}
),
})
const {
register,
setValue,
getValues,
formState: { errors, isValid },
clearErrors,
trigger,
} = useForm({
mode: 'onBlur',
resolver: yupResolver(schema),
defaultValues: {
password: '',
compare: '',
},
})
Update 1: ============
After remove "window.ShadyDOM={force:true};" then it worked. But this is causing other problem ;):
https://vaadin.com/forum/thread/17399734/leverage-browser-save-password-feature
#Override
public void configurePage(InitialPageSettings settings) {
// TODO Auto-generated method stub
settings.addMetaTag("mobile-web-app-capable", "yes");
settings.addMetaTag("apple-mobile-web-app-capable", "yes");
settings.addMetaTag("apple-mobile-web-app-status-bar-style", "black");
settings.addInlineWithContents(
InitialPageSettings.Position.PREPEND, "window.customElements=window.customElements||{};"
+ "window.customElements.forcePolyfill=true;" + "window.ShadyDOM={force:true};",
InitialPageSettings.WrapMode.JAVASCRIPT);
}
End Update. ============
I am trying to integrate Payal checkout to Vaadin 14.7 (Spring core, not spring boot).
Here is paypal-view.js
import { LitElement, html, css } from "lit-element";
let buttons;
let hasRendered = false;
class PaypalElement extends LitElement {
static get properties() {
return {
mood: {
type: String,
noAccessor: false,
hasChanged(newVal, oldVal) {
console.log("newVal " + newVal + " oldVal " + oldVal);
},
},
};
}
static get styles() {
return [
css`
mood_color {
color: green;
}
#paypal-button {
size: "responsive";
}
`,
];
}
firstUpdated(_changedProperties) {
let testFname = this.shadowRoot.getElementById("fname");
super.firstUpdated(_changedProperties);
if (buttons && buttons.close && hasRendered) {
buttons.close();
hasRendered = false;
}
buttons = window.paypal.Buttons({
// Set up the transaction
createOrder: function (data, actions) {
return actions.order.create({
application_context: {
brand_name: "Brand name",
user_action: "PAY_NOW",
//No shipping for in-tangible merchant
shipping_preference: "NO_SHIPPING",
payment_method: {
payee_preferred: "IMMEDIATE_PAYMENT_REQUIRED", // Pending status transactions will not be allowed if you pass this parameter.
payer_selected: "PAYPAL",
},
},
purchase_units: [
{
soft_descriptor: "CC_STATEMENT_NAME",
amount: {
value: "5.00",
},
},
],
});
},
// Finalize the transaction
onApprove: function (data, actions) {
const elementText = document.getElementById("fname");
return actions.order.capture().then(function (orderData) {
// Successful capture! For demo purposes:
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
let transaction = orderData.purchase_units[0].payments.captures[0];
// Replace the above to show a success message within this page, e.g.
// const element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
document.getElementById("update-paypal-trans").innerHTML =
"update-paypal-trans = " + transaction.id;
// trigger lit event
testFname.value = transaction.id;
testFname.click();
document.getElementById("paypalelement").remove();
//console.log(elementText);
});
},
onError: function (error) {
console.log("onError", error, JSON.stringify(error, null, 2));
},
onCancel: function (data, actions) {
console.log("onCancel", data, JSON.stringify(data, null, 2));
document.getElementById("update-paypal-trans").innerHTML =
"testing 123";
// update shadow element
testFname.value = "12345 " + actions;
// trigger lit event
testFname.click();
},
});
// load paypal buttons and put them to element id="paypal-button-to-display" which is shadow dom
buttons
.render(this.shadowRoot.getElementById("paypal-button-to-display"))
.then(() => {
hasRendered = true;
})
.catch((err) => {
console.log(err)
// not mounted anymore, we can safely ignore the error
return;
});
}
// outside updates shadow element
updateShadow() {
this.shadowRoot.getElementById("test-update-shadow").innerHTML =
"test update shadow trans";
this.mood = "nice";
}
updateTask(e) {
console.log("updateTask: " + e);
}
updateTaskClick(e) {
console.log("updateTaskClick: " + e);
// call back-end
}
render() {
return html`
Web Components are
<span class="mood_color"> ${this.mood} and ${this.innerHTML}</span>!
<input
type="text"
id="fname"
name="fname"
value="${this.mood}"
#change=${(e) => this.updateTask(e.target.value)}
#click="${(e) => this.updateTaskClick(e.target.value)}"
/>
<div id="paypal-button-to-display"></div>
<br />
<div id="test-update-shadow">test-update-shadow-default</div>
<br />
<input
#click="${() => this.updateShadow()}"
id="myinput"
type="button"
value="update shadow button"
/>
`;
}
attributeChangedCallback(name, oldval, newval) {
super.attributeChangedCallback(name, oldval, newval);
console.log("attribute change: ", name, newval);
}
changeProperties() {
let randomString = Math.floor(Math.random() * 100).toString();
this.mood = "myProp " + randomString;
console.log("randomString change: ", randomString);
}
}
customElements.define("paypal-element", PaypalElement);
hello-world-view.ts
import { html, LitElement, customElement } from 'lit-element';
import './paypal-view';
import '#vaadin/vaadin-button';
import '#vaadin/vaadin-text-field';
#customElement('hello-world-view')
export class HelloWorldView extends LitElement {
createRenderRoot() {
// Do not use a shadow root
return this;
}
constructor() {
super();
}
render() {
return html`
<vaadin-text-field id="name" label="Your name"></vaadin-text-field>
<vaadin-button >Say hello</vaadin-button>
<paypal-elementt mood="great" id="paypalBut">hello customer</paypal-elementt>
`;
}
}
hello-world-view.ts
package com.lecompany.iad.view;
import com.lecompany.iad.app.MainView;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.littemplate.LitTemplate;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.template.Id;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
/**
* A Designer generated component for the stub-tag template.
*
* Designer will add and remove fields with #Id mappings but does not overwrite
* or otherwise change this file.
*/
#PageTitle("Hello World")
#Route(value = "hello", layout = MainView.class)
#Tag("hello-world-view")
#JsModule("./src/view/hello-world-view.ts")
public class HelloWorldView extends LitTemplate {
#Id
private TextField name;
// #Id
// private Button sayHello;
public HelloWorldView() {
UI.getCurrent().getPage().addJavaScript("https://www.paypal.com/sdk/js?client-id=AejZZjQsvxS299I2_LSnRkJStp0AsBzScCqbGK1_W6RNJssNR5NVnKYd97dM2kOJnRMF1u1ldLGjOlZ5¤cy=USD");
// sayHello.addClickListener(e -> {
// Notification.show("Hello " + name.getValue());
// });
}
}
And here is error:
I tested above codes in Spring boot, then it works fine.
but it got error in the normal spring code. Any advice on this ?
There is reported issue for Paypal.
Paypal reported issue
It is a Vaadin bug and reported here:
Vaadin bug
I am trying to create a delete functionality in my application which seems to be deleting the object from the backend just fine, but not in the frontend.
Here is how I structured my project:
in actions/deleteJournal.js
export const deleteJournal = (journal) => {
return dispatch => {
fetch(`http://localhost:3001/journals/${journal.id}` , {
method: "DELETE" })
.then(resp => resp.json())
.then(journal => { dispatch({ type: "DELETE_JOURNAL", journal })
})
}
}
in reducers/journalsReducer.js
const initialState = {
journals: [],
loading: true
}
const journalsReducer = (state=initialState, action) => {
switch(action.type) {
case "LOADING":
return {
...state,
loading: true
}
case "SET_JOURNALS":
return {
...state,
loading: false,
journals: action.journals
}
case "ADD_JOURNAL":
return {
...state,
journals: [...state.journals, action.journal],
}
case 'DELETE_JOURNAL':
return {
journals: state.journals.filter(todo => todo.id !== action.id),
...state
}
default:
return state;
}
}
export default journalsReducer
in components/List.js
import React, { Component } from 'react'
import { connect } from 'react-redux'
import Journal from './Journal'
import { deleteJournal } from '../actions/getJournals'
class ListFiltered extends Component {
render() {
const journals = this.props.journals.map( journal => journal.locationId === this.props.locationId && <Journal key={journal.id} title={journal.title} content={journal.content} id={journal.id} deleteJournal={this.props.deleteJournal} />)
return (
<div>
{journals}
<p></p>
</div>
)
}
}
const mapStateToProps = state => {
return {
journals: state.journals
}
}
export default connect(mapStateToProps, {deleteJournal})(ListFiltered)
in components/Journal.js
class Journal extends Component {
render() {
const { id, title, content } = this.props;
return (
<div>
<ul>
<li>
<h3>{ title } </h3>
<p> { content }</p> <button onClick={() => this.props.deleteJournal(this.props) }>Delete</button>
</li>
</ul>
</div>
)
}
}
export default Journal
So this seems to be giving me an error " Uncaught (in promise) SyntaxError: Unexpected end of JSON input
at deleteJournal.js:48"
I checked my server and it seems to be deleting it from there but nothing in the frontend and when I refresh the item is deleted.
What can I do so it automatically deletes the item from the frontend?
I suspect the issue may be with your line
.then(resp => resp.json())
What is the body of the response when you issue the DELETE request? If it's not a valid JSON object, then JavaScript would throw the error you're seeing.
As you're not using the parsed JSON at all, you could conceivably remove that line:
export const deleteJournal = (journal) => {
return dispatch => {
fetch(`http://localhost:3001/journals/${journal.id}` , {
method: "DELETE" })
.then(journal => { dispatch({ type: "DELETE_JOURNAL", journal })
})
}
}
...but you might want to have some of checking that the DELETE method was executed successfully.
How you do that's up to you and your Rails API, but I'd typically expect to see the response have an HTTP status in the 2xx range when something is successfully deleted, and in the 4xx range if something couldn't be deleted (e.g., if the ID wasn't recognised, or there were dependency issues which meant the deletion request was rejected). That feels outside the scope of this question though.
I have an antd Modal, i am trying to validate a field and provided validation to it. How can i enable/disable the Ok button based on the validation. If the validation is successful then button should be enabled else disabled.
<Form>
<Modal
title={modalHeader}
okText="ADD FIELD"
cancelText="CANCEL"
visible={visible}
onCancel={onCancelHandler}
onOk={() => onAddFieldHandler(fieldName)}
width={600}
okButtonProps={{disabled:true}}
>
<p>Please provide name</p>
<Form.Item
name="fieldName"
rules={[{ required: true, message: 'Please enter name' }]}
>
<FieldNameInput
placeholder="Field name..."
value={fieldName}
onChange={(event) => setFieldName(event.target.value)}
/>
</Form.Item>
</Modal>
</Form>
You can use onFieldsChange from Antd Forms API togehter with geFieldsError and the okButtonProps from Antd Modal API.
const [form] = Form.useForm();
const [buttonDisabled, setButtonDisabled] = useState(true);
return (
<Modal
...
okButtonProps={{ disabled: buttonDisabled }}
>
<Form
form={form}
...
onFieldsChange={() =>
setButtonDisabled(
form.getFieldsError().some((field) => field.errors.length > 0)
)
}
>
Here is a working Stackblitz.
In my case I had Form inside modal and there is onFieldChange prop when you can pass function to perform some operations due to changes on from so you can sth like that:
const SomeModal = ({ visible }) => {
const [form] = Form.useForm();
const [buttonDisabled, setButtonDisabled] = useState(true);
const handleOk = () => form.submit();
const handleAfterClose = () => {
setButtonDisabled(true);
form.resetFields();
}
const handleCancel = () => ...some action to hide modal;
const handleFormFinish = (values) => {
... some logic here
}
return (
<Modal
title={"Some title..."}
visible={visibile}
onOk={handleOk}
onCancel={handleCancel}
afterClose={handleAfterClose}
okButtonProps={{ disabled: buttonDisabled }}
>
<Form
form={form}
layout="vertical"
name="acceptform"
onFinish={handleFormFinish}
initialValues={{
...initial values here
}}
onFieldsChange={() => {
const actualFieldValues = form.getFieldsValue();
const anyError = form.getFieldsError().some((field) => field.errors.length > 0);
.. some logic if error etc..
if (anyError) {
setButtonDisabled(true);
}
else {
setButtonDisabled(false);
}
}}
>
and of course there is need to have some validators on fields
<Form.Item
label={"someLabel"}
id="field"
name="field"
hasFeedback
rules={[
{
type: "string",
validator: async (rule, value) => inputFieldValidate(value, "custom message")
},
]}
>
and validator looks alike:
const inputFieldValidate = async (value, message) => {
if (someCondition)) {
return Promise.reject(message);
}
return Promise.resolve();
};
here is some nice to know that validator isasync and to make it work without any warnings just handle promises
Having the Form inside the Modal, a way to update modal button status would be just running form instance's validateFields, but there are two things to take into account:
This function is a Promise, so the state must update after an await with the validation results.
I've experienced some looping issues when using onFieldsChange (maybe the validation triggers some kind of field update). Instead, onValuesChange has worked good enough for me.
Running the validation into a setTimeout callback seems to be mandatory. Doing it without the setTimeout returns a validation error even when all the fields are valid because of an outOfDate: true. It seems to be because of how the Antd Form update lifecycle works, and waiting until this process has ended (what we can easily achieve with the setTimeout) solves that problem.
A succesful validation returns the form values object, a failed one returns an errorInfo object with the errors list, the outOfDate status and the current form values. You can use the errors list in the latter to get the validation messages returned by Antd to display more descriptive and specific feedback.
In the end, my approach has this structure:
const MyModal = ({onFinish, ...otherProps}) => {
const [canSubmit, setCanSubmit] = useState(false);
const [form] = Form.useForm();
return (
<Modal
{...otherProps}
okButtonProps={{
disabled: !canSubmit
}}
>
<MyFormComponent
form={form}
onFinish={onFinish}
onValuesChange={() => {
setTimeout(() => {
form
.validateFields()
.then(() => {
/*
values:
{
username: 'username',
password: 'password',
}
*/
setCanSubmit(true);
})
.catch((err) => {
/*
errorInfo:
{
values: {
username: 'username',
password: 'password',
},
errorFields: [
{ name: ['password'], errors: ['Please input your Password!'] },
],
outOfDate: false,
}
*/
setCanSubmit(false);
});
});
}}
/>
</Modal>
);
};
I am having an extremely weird issue where I don't even know where to begin to debug since it only happens when I get the app into test flight. Basically I am trying to load channels based on geolocation. Some automatically load and then some are loaded if less than 100 miles from a mountain (lets call these PUBLIC and PRIVATE channels- both of which are in the same list). I have these 2 firebase calls in my action creator and push them into an array and then call dispatch after. I have an issue with the FlatList where it loads the PUBLIC channels and only when I scroll do the PRIVATE channels. There are a bunch of things I have tried including the most common from that specific github issue (flatlist updating) 'removeClippedSubviews={false}', extra data, pure components, etc, but none have worked.
Instead I found a way to get around this (I know it isn't the best, but I just want a solution that works for now) by just setting a timeout and waiting for the channels before dispatching the action:
setTimeout(function(){ dispatch(loadPublicChannelsSuccess(data)); }, 10);
Unfortunately, now is when the crazy issue comes in. Basically, this works for debug, release/ everything I have tried with XCode, but when it gets to my device the render method basically sits at a loading indicator until I switch tabs with react navigation. This makes no sense to me since it doesn't happen always (maybe 80%) of the time, and only in test flight so far. I had never seen this before setting the timeout either so not really sure where to begin:
render() {
const {loadPublicChannels, loading, publicChannels, checkedInMountain, selectedMountain} = this.props;
return !loadPublicChannels && publicChannels && !loading
? (
<MessagePanelPublic publicChannels={publicChannels} selectedMountain={selectedMountain}/>
) : (
<LoadingAnimation />
);
}
actions
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range (geolocation)
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
publicChannelsToDownload.push({
id: channelId,
info: channelInfo
});
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function(){ dispatch(loadPublicChannelsSuccess(data)); }, 10);
});
};
};
reducer related to public channels
case types.LOAD_PUBLIC_CHANNELS:
return {
...state,
loadPublicChannels: true
};
case types.LOAD_PUBLIC_CHANNELS_SUCCESS:
console.log("PUBLIC");
console.log(action.publicChannels);
console.log(action);
return {
...state,
publicChannels: action.publicChannels,
loadPublicChannels: false,
messages: {}
};
case types.LOAD_PUBLIC_CHANNELS_ERROR:
return {
...state,
channelsPublicError: action.error,
loadPublicChannels: false
};
container which calls mapStateToProps and mapDispatchToProps
class MessagePanelPublicContainer extends Component {
constructor(props) {
super(props);
}
// get public and private channels from redux
async componentDidMount() {
this.props.getUserPrivateChannels();
this.props.loadCurrentUser();
// this.props.getUserPublicChannels();
}
componentDidUpdate(prevProps) {
if (this.props.mountainsInRange && prevProps.mountainsInRange !== this.props.mountainsInRange || prevProps.selectedMountain !== this.props.selectedMountain) {
this.props.getUserPublicChannels();
}
}
lessThan12HourAgo = (date) => {
return moment(date).isAfter(moment().subtract(12, 'hours'));
}
render() {
const {loadPublicChannels, loading, publicChannels, checkedInMountain, selectedMountain} = this.props;
return !loadPublicChannels && publicChannels && !loading
? (
<MessagePanelPublic publicChannels={publicChannels} selectedMountain={selectedMountain}/>
) : (
<LoadingAnimation />
);
}
}
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
loadPublicChannels: state.chat.loadPublicChannels,
currentUser: state.chat.currentUser,
loading: state.chat.loadCurrentUser,
mountainsInRange: state.session.mountainsInRange,
selectedMountain: state.session.selectedMountain,
};
}
const mapDispatchToProps = {
loadCurrentUser,
getUserPublicChannels,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MessagePanelPublicContainer);
component
import React, { Component } from "react";
import { View, Text, FlatList, ImageComponent } from "react-native";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { ListItem, Icon, Button } from "react-native-elements";
import { withNavigation } from "react-navigation";
import LinearGradient from "react-native-linear-gradient";
import styles from "./Styles";
import moment from 'moment';
import FastImage from 'react-native-fast-image';
class MessagePanelPublicComponent extends Component {
render() {
// rendering all public channels
const renderPublicChannels = ({ item }) => {
return (
<ListItem
leftAvatar={{
source: { uri: item.info.ChannelPicture },
rounded: false,
overlayContainerStyle: { backgroundColor: "white" },
ImageComponent: FastImage
}}
title={item.info.Name}
titleStyle={styles.title}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
const renderText = () => {
return (
<View style={styles.extraTextContainer}>
<Text style={styles.extraText}>
Get within 100 miles from resort or select a closer resort to see more channels...
</Text>
<Icon
name="map-marker"
type="font-awesome"
size={40}
iconStyle={styles.extraIcon}
/>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
// keyExtractor={item => item.Name}
keyExtractor={(item, index) => index.toString()}
extraData={this.props.publicChannels}
removeClippedSubviews={false}
/>
{this.props.publicChannels.length < 3 ? renderText() : null}
</View>
</View>
);
}
}