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>
);
}
}
Related
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!
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'm using rails as backend and react native as front end, I'm trying to upload one photo using formdata in react native and using active storage in rails to save it.
using one model name Room.rb and has_one_attached :photo.
Room.rb
class Room < ApplicationRecord
has_one_attached :photo
end
here is the params received by rails, there are two (room_name and photo)
{
"room_name"=>"Guest Room",
"photo"=>
<ActionController::Parameters {
"uri"=>"file:///Users/MyName/Library/Developer/CoreSimulator/Devices/guest_room.jpg",
"name"=>"guest_room.jpg",
"type"=>"image/jpg"
} permitted: true >
}
room_controller.rb to save and receive file as follow
def create
#room = Room.create(room_params)
if #room.save
render json: RoomSerializer.new(#room).serializable_hash, status: :created
else
render json: { errors: #room.errors }, status: :unprocessable_entity
end
end
I get an error inside #room.save, saying 'TypeError - hash key "uri" is not a Symbol:'
my expectation after I choose an image from mobile phone (client) and press save button, it will automatically download an image, this is also the reason I send using FormData from react native.
Update 2:
here is part of react native that upload photo,
const preparePhoto = (uriPhoto) => {
// ImagePicker saves the taken photo to disk and returns a local URI to it
const localUri = uriPhoto;
const name = localUri.split('/').pop();
// Infer the type of the image
const match = /\.(\w+)$/.exec(name);
const type = match ? `image/${match[1]}` : `image`;
return [name, type];
};
const createRoom = dispatch => async ({ room_name, uriPhoto }) => {
const [name, type] = preparePhoto(uriPhoto);
const photo = { uri: uriPhoto, name, type };
const room = { room_name, photo };
const formData = new FormData();
formData.append('room', JSON.stringify(room));
const config = { headers: {
Accept: 'application/json',
'Content-Type': 'multipart/form-data',
} };
try {
const response = await serverApi.post('/rooms', formData, config);
dispatch({ type: 'clear_error' });
} catch (err) {
console.log('error: ', err);
dispatch({ type: 'add_error', payload: 'Sorry we have problem' });
}
};
update 3:
source code to choose an image and send it to context
import React, { useState } from 'react';
import Constants from 'expo-constants';
import {
ActivityIndicator,
Button,
Clipboard,
Image,
Share,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import * as Permissions from 'expo-permissions';
const RoomUploadPhoto = ({ uriPhoto, onPhotoChange }) => {
const [uploading, setUploading] = useState(false);
const renderUploadingIndicator = () => {
if (uploading) {
return <ActivityIndicator animating size="large" />;
}
};
const askPermission = async (type, failureMessage) => {
const { status, permissions } = await Permissions.askAsync(type);
if (status === 'denied') {
alert(failureMessage);
}
};
const handleImagePicked = (pickerResult) => {
onPhotoChange(pickerResult.uri);
};
const takePhoto = async () => {
await askPermission(
Permissions.CAMERA,
'We need the camera permission to take a picture...'
);
await askPermission(
Permissions.CAMERA_ROLL,
'We need the camera-roll permission to read pictures from your phone...'
);
const pickerResult = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
});
handleImagePicked(pickerResult);
};
const pickImage = async () => {
await askPermission(
Permissions.CAMERA_ROLL,
'We need the camera-roll permission to read pictures from your phone...'
);
const pickerResult = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [4, 3],
});
handleImagePicked(pickerResult);
};
const renderControls = () => {
if (!uploading) {
return (
<View>
<View style={styles.viewSatu}>
<Button
onPress={pickImage}
title="Pick an image from camera roll"
/>
</View>
<View style={styles.viewSatu}>
<Button onPress={takePhoto} title="Take a photo" />
</View>
</View>
);
}
};
return (
<React.Fragment>
<Text>upload photo</Text>
{renderUploadingIndicator()}
{renderControls()}
</React.Fragment>
);
};
const styles = StyleSheet.create({
viewSatu: {
marginVertical: 8
}
});
export default RoomUploadPhoto;
Make sure you post File object or base64 content to backend. Your photo is just a json object at the moment contains file path and name.
Please remove the photo param from your room_params.
def room_params
params.require(:room).permit(
:room_name
)
end
And attach your photo when you create the room:
def create
#room = Room.new(room_params)
#room.attach params[:photo]
...
My app is a dashboard that allows the public to view certain items but not CRUD. If a user logs in, full CRUD is accessible. I'm using home spun JWT/Bcrypt auth in Rails backend and React/Redux for the frontend and state management. I'm wondering the best strategy to have the React DOM immediately reflect when a user login/logout and have certain items like create buttons appear/disappear based on login status. Right now, this.props.currentUser coming from the Redux store doesn't seem to help even though the Redux store has updated.
I'm using JSX ternary operators to display certain items based on currentUser state. I've tried this.props.currentUser ? <button>Example</button> : null or this.props.currentUser !== null ? <button>Example</button> : null and this.props.currentUser.length !== 0 ? <button>example</button> : null, none of which I get any consistency (might work for one compnonent but on page refresh no longer works, etc).
Here's an example component:
import React, { Component } from "react";
import { Link, withRouter } from "react-router-dom";
import { Container, Grid, Image } from "semantic-ui-react";
import { connect } from 'react-redux';
import logo from "../../images/logo-2-dashboard";
import { clearCurrentUser } from "../actions/clearCurrentUserAction";
class Header extends Component {
logout = () => {
localStorage.clear();
this.props.clearCurrentUser()
this.props.history.push("/");
}
render() {
return (
<>
<Container fluid>
<Grid divided>
<Grid.Row columns={2}>
<Grid.Column>
<Image
src={logo}
size="large"
style={{ margin: "3px", padding: "2px" }}
></Image>
</Grid.Column>
<Grid.Column>
// Here's the kitchen sink approach lol
{this.props.currentUser && this.props.currentUser === null ||
this.props.currentUser && this.props.currentUser.length === 0 ? (
<Link
to={"/login"}
onClick={this.props.login}
style={{ marginLeft: "200px" }}
>
Login
</Link>
) : (
<Link
to={"/"}
onClick={this.logout}
style={{ marginLeft: "200px" }}
>
Logout
</Link>
)}
// SAME HERE
{this.props.currentUser && this.props.currentUser !== null ||
this.props.currentUser && this.props.currentUser.length !== 0 ? (
<div>Logged in as: {this.props.currentUser.username}</div>
) : null}
</Grid.Column>
</Grid.Row>
</Grid>
</Container>
</>
);
}
}
const mapStateToProps = state => {
return {
currentUser: state.currentUser.currentUser
}
}
const mapDispatchToProps = dispatch => {
return {
clearCurrentUser: () => dispatch(clearCurrentUser())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Header));
Here is my Redux Action Thunk to set CurrentUser back to null on logout (I'm also clearing localHistory):
export const CLEAR_CURRENT_USER = 'CLEAR_CURRENT_USER'
export const clearCurrentUser = () => dispatch => {
return dispatch({ type: 'CLEAR_CURRENT_USER', payload: null })
}
and the Reducer for currentUser:
const initialState = {
currentUser: [],
};
export const currentUserReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_CURRENT_USER":
return { ...state, currentUser: action.payload }
case "GET_CURRENT_USER":
return { currentUser: action.payload }
case "CLEAR_CURRENT_USER":
return { currentUser: action.payload }
default:
return state;
}
};
Perhaphs this is the wrong approach altogether. I'm a junior working on my own at a company.
you are checking if currentUser is truthy but your initialstate is an array. your initialState for currentUser reducer should be null instead of an empty array.
const initialState = {
currentUser: null,
};
export const currentUserReducer = (state = initialState, action) => {
switch (action.type) {
case "SET_CURRENT_USER":
return { ...state, currentUser: action.payload }
case "GET_CURRENT_USER":
return { currentUser: action.payload }
case "CLEAR_CURRENT_USER":
return { currentUser: action.payload }
default:
return state;
}
};
I'm using Pubnub to push and pull data into a React Native app where I'm displaying it in a list. For some reason the history callback is never reached, though I am getting messages through the channel I'm subscribed to. Storage & playback is enabled. Any idea what's going on here?
import React from 'react'
import {
StyleSheet,
Text,
View,
TouchableHighlight,
ListView,
} from 'react-native'
import PubNub from 'pubnub';
var username = 'Jenny';
const channel = 'list';
const publish_key = 'XXXXXXXXXXXXXXXXXXXXXXXXX';
const subscribe_key = 'XXXXXXXXXXXXXXXXXXXXXXXXX';
const listSections = ['NOW', 'LATER', 'PROJECTS'];
const pubnub = new PubNub({
publishKey : publish_key,
subscribeKey : subscribe_key,
ssl: true,
uuid: username
});
export default class MyList extends React.Component{
constructor(){
super();
var ds = new ListView.DataSource({
getSectionHeaderData: (dataBlob, sectionID) => dataBlob[sectionID],
getRowData: (dataBlob, sectionID, rowID) => dataBlob[sectionID + ':row' + rowID],
rowHasChanged: (row1, row2) => row1 !== row2,
sectionHeaderHasChanged : (s1, s2) => s1 !== s2,
});
}
}
componentWillMount() {
this.connect();
pubnub.addListener({
message: (m) => this.success([m.message])
});
pubnub.subscribe({
channels: [channel],
});
}
connect() {
console.log("connect");
pubnub.history(
{
channel: channel,
count: 50,
callback: (response) => {
console.log(response);
}
);
}
success(m){
/*Do some data manipulation for the list here */
}
render(){
return(
<View style={styles.container}>
<ListView
dataSource = {this.state.dataSource}
renderRow = {(rowData) =>
<View style={styles.rowContainer}>
<Text style={styles.rowText}>{rowData}</Text>
</View>}
renderSectionHeader = {(headerData) =>
<Text style={styles.header}>{headerData}</Text>}
enableEmptySections = {true}
/>
</View>
)
}
}
You are using our v4 JavaScript SDK. Your callback needs to be a separate parameter:
pubnub.history(
{
channel: channel,
count: 50
},
function (status, response) {
console.log(status, response);
}
);
This is subtle change in v4 and you can review the v3 to v4 migration guide for other minor changes.