REACT NATIVE / my child component don't re-render despite props change - react-native

I'm facing an issue on the code below (sandbox here https://codesandbox.io/s/silly-rubin-9ghc5?fontsize=14&hidenavigation=1&theme=dark ), the props change but doesn't refresh the component accordingly.
I would like to be able to keep track of different votes / options within a state in the parent node, hence i'm using an array where the index is the index of my option and the value the number of votes.
import { useEffect, useState } from "react";
import { Button, View } from "react-native";
import "./styles.css";
const myList = [
{
cid: 0,
cTx: "Paris"
},
{
cid: 1,
cTx: "Lyon"
},
{
cid: 2,
cTx: "Marseille"
},
{
cid: 3,
cTx: "Valence"
},
{
cid: 4,
cTx: "Bordeaux"
}
];
interface mTprop {
myItemId: number;
myItemTxt: string;
myItemSt: number[];
myItemClck: () => void;
}
const MyItem = (props: mTprop) => {
const onBtPress = () => {
props.myItemClck();
console.log(props.myItemSt);
};
return (
<View
style={{
flexDirection: "row"
}}
>
<h3>{props.myItemTxt}</h3>
<Button onPress={onBtPress} title="Learn More" />
<h3>{props.myItemSt[props.myItemId]}</h3>
</View>
);
};
export default function App() {
const [selectedOpt, setSelectedOpts] = useState(new Array(0));
useEffect(() => {
setSelectedOpts(new Array(myList.length).fill(0));
}, []);
const updateOptions = (ix: number) => {
++selectedOpt[ix];
setSelectedOpts(selectedOpt);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
{myList.map((itm, ix) => {
return (
<MyItem
key={ix}
myItemId={ix}
myItemTxt={itm.cTx}
myItemSt={selectedOpt}
myItemClck={() => updateOptions(ix)}
/>
);
})}
</div>
);
}
Thanks a million in advance !
Please let me know if further info is needed

In order to change data in list. You need to do this
const updateOptions = (ix: number) => {
const newList = selectedOpt.map((m, i) => {
if (i === ix) {
return m + 1;
}
return m;
});
setSelectedOpts(newList);
};

Related

Apollo cache query fields policy offsetLimitPagination() doesnt work with subscriptions

I use apollo client for react native.
When I use offsetLimitPagination() for pagination my subscriptions doesn't update cache.
Subscriptions works correctly but doesn't update flatlist data.
When i remove offsetLimitPagination function it works. I can't use together subscriptions and offsetLimitPagination function on cache.
Is there any solution for that?`
Thanks.
Cache
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
chatDetail: offsetLimitPagination(),
}
}
},
});
ChatDetailPage
import React, { useState, useCallback } from 'react'
import { StyleSheet, Text, View, FlatList } from 'react-native'
import { ActivityIndicator } from 'react-native-paper';
import { useQuery } from '#apollo/client'
import { useSelector } from 'react-redux'
import { CHAT_DETAIL } from '../../../Graphql/Queries/Message'
import { MESSAGE_SUB } from '../../../Graphql/Subscriptions/Message'
import MainFlow from './Components/Flow/MainFlow'
const ChatDetailMain = () => {
const user = useSelector(state => state.auth.user)
const currentRoom = useSelector(state => state.room.currentRoom)
const [hasNext, setHasNext] = useState(true)
const limit = 15
const { error, loading, data, refetch, fetchMore, subscribeToMore } = useQuery(CHAT_DETAIL, {
variables: { userId: user._id, roomId: currentRoom._id, limit }, fetchPolicy: "cache-and-
network",
nextFetchPolicy: "cache-first" })
// render item
const renderItem = (
({item} ) => {
return <MainFlow item={item} />
}
)
if (error) {
console.warn('CHAT_DETAIL QUERY ERROR: ', error)
console.log(error.message);
return (
<View>
<Text>
An Error Occured: {error.message}
</Text>
</View>
)
}
if (loading || data == undefined || data == null) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator size={50} color="gray" />
</View>
)
}
// fetchMore
const fetchMoreData=()=>{
// console.log("fetchMore runnig hasnext limit, data.chatDetail.length >= limit ", hasNext, limit, data.chatDetail.length >= limit);
if(hasNext && data.chatDetail.length >= limit){
fetchMore({
variables:{
offset: data.chatDetail.length,
limit: data.chatDetail.length+limit
}
}).then((flowMoredata)=>{
if(flowMoredata.data.chatDetail.length==0 || flowMoredata.data.chatDetail.length === data.chatDetail.length){
setHasNext(false)
}
})
}
}
// subscription area
const subscribeQ = () => subscribeToMore({
document: MESSAGE_SUB,
variables: {
userId: user._id
},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const { messageSub } = subscriptionData.data
let current = {}
let others = []
switch(messageSub.type){
case 'update':
prev.chatDetail.map(message => {
if (message._id != messageSub.message._id) others.push(message)
if (message._id == messageSub.message._id) current = messageSub.message
})
return { chatDetail: [ current, ...others ]}
case 'remove':
prev.chatDetail.map(message => {
if (message._id != messageSub.message._id) others.push(message)
if (message._id == messageSub.message._id) current = messageSub.message
})
return { chatDetail: [ ...others ]}
case 'create':
return { chatDetail: { ...prev, chatDetail: [ messageSub.message, ...prev.chatDetail] }}
default: return { ...prev }
}
}
})
if (subscribeToMore != undefined && subscribeToMore) {
subscribeQ()
}
return (
<View>
<FlatList
data={data.chatDetail}
renderItem={renderItem}
keyExtractor={(item, index) => String(index)}
onEndReached={fetchMoreData}
onEndReachedThreshold={0.2}
contentContainerStyle={{ paddingTop: 80 }}
inverted={true}
/>
</View>
)
}
export default ChatDetailMain
const styles = StyleSheet.create({})
It was about cache merge issue. If you want to cache data, you shoul give a key to apollo client "Cache according to the what, for each room has an id or roomName for keyArgs param it should uniqe value like that
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
chatDetail: {
keyArgs:['roomId'],
merge(incoming=[], existing=[]){
.
.
.
return offsetLimitPagination()
}}
}
}
},
});

How I can resolve this : Warning: Encountered two children with the same key, `%s`

I am new to react-native and this is not me who program this app.
Could someone help me to fix this error, I think its the flatlist who cause this because it happen only I load the page or search something on the list. I know there is a lot a question about this error but I don't find a solution for me.
Warning: Encountered two children with the same key,%s. Keys should be unique so that components maintain their identity across updates.
ContactScreen.js
import React from 'react';
import { Button, View, FlatList, Alert, StyleSheet, KeyboardAvoidingView } from 'react-native';
import { ListItem, SearchBar } from 'react-native-elements';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Contacts } from 'expo';
import * as Api from '../rest/api';
import theme from '../styles/theme.style';
import { Contact, ContactType } from '../models/Contact';
class ContactsScreen extends React.Component {
static navigationOptions = ({ navigation }) => {
return {
headerTitle: "Contacts",
headerRight: (
<Button
onPress={() => navigation.popToTop()}
title="Déconnexion"
/>
),
}
};
constructor(props) {
super(props);
this.state = {
contacts: [],
search: '',
isFetching: false,
display_contacts: []
}
}
async componentDidMount() {
this.getContactsAsync();
}
async getContactsAsync() {
const permission = await Expo.Permissions.askAsync(Expo.Permissions.CONTACTS);
if (permission.status !== 'granted') { return; }
const contacts = await Contacts.getContactsAsync({
fields: [
Contacts.PHONE_NUMBERS,
Contacts.EMAILS,
Contacts.IMAGE
],
pageSize: 100,
pageOffset: 0,
});
const listContacts = [];
if (contacts.total > 0) {
for(var i in contacts.data) {
let contact = contacts.data[i];
let id = contact.id;
let first_name = contact.firstName;
let middle_name = contact.middleName;
let last_name = contact.lastName;
let email = "";
if ("emails" in contact && contact.emails.length > 0) {
email = contact.emails[0].email;
}
let phone = "";
if ("phoneNumbers" in contact && contact.phoneNumbers.length > 0) {
phone = contact.phoneNumbers[0].number;
}
listContacts.push(new Contact(id, first_name, middle_name, last_name, email, phone, ContactType.UP));
}
}
const soemanContacts = await Api.getContacts();
if (soemanContacts.length > 0) {
for(var i in soemanContacts) {
let contact = soemanContacts[i];
let id = contact.contact_id.toString();
let first_name = contact.contact_first_name
let last_name = contact.contact_last_name;
let email = contact.contact_email;
let phone = contact.contact_phone.toString();
listContacts.push(new Contact(id, first_name, "", last_name, email, phone, ContactType.DOWN));
}
}
listContacts.sort((a, b) => a.name.localeCompare(b.name));
this.setState({contacts: listContacts});
this.setState({ isFetching: false });
this.updateSearch(null);
}
async addContactAsync(c) {
const contact = {
[Contacts.Fields.FirstName]: c.firstName,
[Contacts.Fields.LastName]: c.lastName,
[Contacts.Fields.phoneNumbers]: [
{
'number': c.phone
},
],
[Contacts.Fields.Emails]: [
{
'email': c.email
}
]
}
const contactId = await Contacts.addContactAsync(contact);
}
onRefresh() {
this.setState({ isFetching: true }, function() { this.getContactsAsync() });
}
updateSearch = search => {
this.setState({ search });
if(!search) {
this.setState({display_contacts: this.state.contacts});
}
else {
const res = this.state.contacts.filter(contact => contact.name.toLowerCase().includes(search.toLowerCase()));
console.log(res);
this.setState({display_contacts: res});
console.log("contact display "+ this.state.display_contacts);
}
};
toggleContact(contact) {
switch(contact.type) {
case ContactType.SYNC:
break;
case ContactType.DOWN:
this.addContactAsync(contact);
break;
case ContactType.UP:
Api.addContact(contact);
break;
}
/*Alert.alert(
'Synchronisé',
contact.name + 'est déjà synchronisé'
);*/
}
renderSeparator = () => (
<View style={{ height: 0.5, backgroundColor: 'grey', marginLeft: 0 }} />
)
render() {
return (
<View style={{ flex: 1 }}>
<KeyboardAvoidingView style={{ justifyContent: 'flex-end' }} behavior="padding" enabled>
<SearchBar
platform="default"
lightTheme={true}
containerStyle={styles.searchBar}
inputStyle={styles.textInput}
placeholder="Type Here..."
onChangeText={this.updateSearch}
value={this.state.search}
clearIcon
/>
<FlatList
data={this.state.display_contacts}
onRefresh={() => this.onRefresh()}
refreshing={this.state.isFetching}
renderItem={this.renderItem}
keyExtractor={contact => contact.id}
ItemSeparatorComponent={this.renderSeparator}
ListEmptyComponent={this.renderEmptyContainer()}
/>
</KeyboardAvoidingView>
</View>
);
}
renderItem = (item) => {
const contact = item.item;
let icon_name = '';
let icon_color = 'black';
switch(contact.type) {
case ContactType.SYNC:
icon_name = 'ios-done-all';
icon_color = 'green';
break;
case ContactType.DOWN:
icon_name = 'ios-arrow-down';
break;
case ContactType.UP:
icon_name = 'ios-arrow-up';
break;
}
return (
<ListItem
onPress={ () => this.toggleContact(contact) }
roundAvatar
title={contact.name}
subtitle={contact.phone}
//avatar={{ uri: item.avatar }}
containerStyle={{ borderBottomWidth: 0 }}
rightIcon={<Ionicons name={icon_name} size={20} color={icon_color}/>}
/>
);
}
renderEmptyContainer() {
return (
<View>
</View>
)
}
}
const styles = StyleSheet.create({
searchBar: {
backgroundColor: theme.PRIMARY_COLOR
},
textInput: {
backgroundColor: theme.PRIMARY_COLOR,
color: 'white'
}
});
export default ContactsScreen;
I use react-native and expo for this application.
Just do this in you flatlist
keyExtractor={(item, index) => String(index)}
I think that your some of contact.id's are same. So you can get this warning. If you set the index number of the list in FlatList, you can't show this.
keyExtractor={(contact, index) => String(index)}
Don't build keys using the index on the fly. If you want to build keys, you should do it BEFORE render if possible.
If your contacts have a guaranteed unique id, you should use that. If they do not, you should build a key before your data is in the view using a function that produces unique keys
Example code:
// Math.random should be unique because of its seeding algorithm.
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
// after the decimal.
const keyGenerator = () => '_' + Math.random().toString(36).substr(2, 9)
// in component
key={contact.key}
Just do this in your Flatlist
keyExtractor={(id) => { id.toString(); }}
I got same error and I fixed in this case:
do not code in this way (using async) - this will repeat render many times per item (I don't know why)
Stub_Func = async () => {
const status = await Ask_Permission(...);
if(status) {
const result = await Get_Result(...);
this.setState({data: result});
}
}
componentDidMount() {
this.Stub_Func();
}
try something like this (using then):
Stub_Func = () => {
Ask_Permission(...).then(status=> {
if(status) {
Get_Result(...).then(result=> {
this.setState({data:result});
}).catch(err => {
throw(err);
});
}
}).catch(err => {
throw(err)
});
}
componentDidMount() {
this.Stub_Func();
}

How can I make an entire row clickable in a DetailsList component? (office-ui-fabric)

I've been using the DetailsList component but haven't found a way to make an entire row clickable - is there a sample code snippet or pointers on how this can be achieved?
https://developer.microsoft.com/en-us/fabric#/components/detailslist
Overriding onRenderRow worked for me.
const _columns: IColumn[] = [
{
key: 'name',
name: 'Name',
fieldName: 'name',
minWidth: 100,
maxWidth: 200,
isResizable: true
},
{
key: 'value',
name: 'Value',
fieldName: 'value',
minWidth: 100,
maxWidth: 200,
isResizable: true,
}
];
const _items = Array.apply(null, Array(10)).map((_: any, num: number) => ({
key: num.toString(),
name: `Item ${num.toString()}`,
value: num.toString()
}));
class Content extends React.Component {
private _renderRow(props: Fabric.IDetailsRowProps, defaultRender: any): JSX.Element {
return (
<Fabric.DetailsRow {...props} onClick={() => alert('click')}/>
);
}
public render() {
return (
<Fabric.Fabric>
<Fabric.DetailsList
items={ _items }
columns={ _columns.concat(..._columns, ..._columns, ..._columns) }
onRenderRow={this._renderRow}
/>
</Fabric.Fabric>
);
}
}
ReactDOM.render(
<Content />,
document.getElementById('content')
);
Here is th e single click solution,
Use onActiveItemChanged prop like:
const _onActiveItemChanged = (item): void => {
alert(`Item invoked: ${JSON.stringify(item)}`);
};
here is the DetailList
<DetailsList
items={assessmentList}
compact={false}
columns={columns}
onActiveItemChanged={_onActiveItemChanged }
/>
Please onItemInvoked Property and method. But it works on double click. I am also looking for single click solution
const onItemInvoked = (item): void => {
alert(`Item invoked: ${JSON.stringify(item)}`);
};
<DetailsList
items={assessmentList}
compact={false}
columns={columns}
selectionMode={SelectionMode.multiple}
getKey={_getKey}
setKey="multiple"
layoutMode={DetailsListLayoutMode.justified}
checkboxVisibility={CheckboxVisibility.hidden}
isHeaderVisible={true}
selectionPreservedOnEmptyClick={false}
enterModalSelectionOnTouch={true}
ariaLabelForSelectionColumn="Toggle selection"
ariaLabelForSelectAllCheckbox="Toggle selection for all items"
checkButtonAriaLabel="Select Checkbox"
onRenderRow={onRenderRow}
onItemInvoked={onItemInvoked}
onRenderDetailsHeader={(headerProps, defaultRender) => {
return (
<Sticky
stickyPosition={StickyPositionType.Header}
isScrollSynced={true}
stickyBackgroundColor="transparent"
>
<div className="text-center">{defaultRender(headerProps)}</div>
</Sticky>
);
}}
/>
Use onRenderRow and cloneElement to attach an onClick listener:
import React, { useCallback, cloneElement } from 'react';
import { DetailsList } from '#fluentui/react';
const Component = () => {
const onItemInvoked = useCallback( ( item ) => {
console.log( item );
}, [] );
const onRenderRow = useCallback( ( row, defaultRender ) => {
return cloneElement( defaultRender( row ), { onClick: () => onItemInvoked( row.item ) } )
}, [ onItemInvoked ] );
return (
<DetailsList
onRenderRow={ onRenderRow }
/>
);
};

React native - Can't dispatch action in component because state gets undefined

In my react native android app, when I try to dispatch an action in BoardsScreen or in the root of the app, the following error pops up:
However, when I remove it, the app doesn't crashes.
BoardsScreen.js
import React from 'react';
import { connect } from 'react-redux';
import { Container, Content, Text, List, Button, Icon, ListItem } from 'native-base';
import { ListView, StatusBar } from 'react-native';
import { ConfirmDialog } from 'react-native-simple-dialogs';
import ActionButton from 'react-native-action-button';
import { removeBoard } from '../actions/configurationActions';
class BoardsScreen extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
boardDeleteDialog: false,
secId: null,
rowId: null,
rowMap: null,
};
}
deleteRow(secId, rowId, rowMap) {
rowMap[`${secId}${rowId}`].props.closeRow();
const newData = [...this.props.boards];
newData.splice(rowId, 1);
this.props.removeBoard(newData);
this.setState({
rowId: null,
secId: null,
rowMap: null,
boardDeleteDialog: false,
});
}
dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
render() {
console.log(this.props.boards);
return (
<Container>
<StatusBar backgroundColor="#00C853" />
<ConfirmDialog
title="Delete board?"
animationType="fade"
visible={this.state.boardDeleteDialog}
positiveButton={{
title: 'Delete',
titleStyle: {
color: '#2ecc71',
},
onPress: () => this.deleteRow(this.state.secId, this.state.rowId, this.state.rowMap),
}}
negativeButton={{
titleStyle: {
color: '#2ecc71',
},
title: 'Cancel',
onPress: () =>
this.setState({
boardDeleteDialog: false,
secId: null,
rowId: null,
rowMap: null,
}),
}}
/>
<Content>
{this.props.boards.length >= 1 ? (
<List
style={{ backgroundColor: '#D9534F' }}
dataSource={this.dataSource.cloneWithRows(this.props.boards)}
renderRow={data => (
<ListItem
style={{ paddingLeft: 14, backgroundColor: 'transparent' }}
button
onPress={() =>
this.props.navigation.navigate('Board', {
board: data.board,
boardName: data.boardName,
})
}
>
<Text>{data.boardName}</Text>
</ListItem>
)}
renderRightHiddenRow={(data, secId, rowId, rowMap) => (
<Button
full
danger
onPress={() =>
this.setState({
boardDeleteDialog: true,
secId,
rowId,
rowMap,
})
}
>
<Icon active name="trash" />
</Button>
)}
disableRightSwipe
rightOpenValue={-75}
/>
) : (
<Text>No boards added.</Text>
)}
</Content>
<ActionButton
buttonColor="#2ecc71"
fixNativeFeedbackRadius
onPress={() => this.props.navigation.navigate('AddBoard')}
/>
</Container>
);
}
}
const mapStateToProps = state => ({
boards: state.configurationReducer.boards,
});
const mapDispatchToProps = dispatch => ({
removeBoard: (board) => {
dispatch(removeBoard(board));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(BoardsScreen);
App.js
import React from 'react';
import { connect } from 'react-redux';
import MainNavigator from './src/config/Router';
import { addBoardToList } from './src/actions/configurationActions';
import { Board } from './src/API';
class App extends React.PureComponent {
componentDidMount() {
Board.getList(true).then(response => this.parseDataFromJSONResponse(response));
}
parseDataFromJSONResponse(response) {
for (let i = 0; i < response.length; i += 1) {
this.props.addBoardToList(response[1]);
}
}
render() {
return <MainNavigator />;
}
}
const mapStateToProps = state => ({
boards: state.configurationReducer.boards,
});
const mapDispatchToProps = dispatch => ({
addBoardToList: (board) => {
dispatch(addBoardToList(board));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
configurationReducer.js
const initialState = {
theme: 1,
obscure: false,
boards: [],
boardsList: [],
};
const configurationReducer = (state = initialState, action) => {
let newState = { ...state };
switch (action.type) {
case 'ADD_BOARD':
newState = {
boards: [...state.boards, action.payload],
};
return newState;
case 'REMOVE_BOARD':
newState = {
boards: action.payload,
};
return newState;
case 'ADD_BOARD_TO_LIST':
newState = {
boardsList: [...state.boardsList, action.payload],
};
return newState;
default:
return state;
}
};
export default configurationReducer;
configurationActions.js
function addBoard(board) {
return {
type: 'ADD_BOARD',
payload: board,
};
}
function removeBoard(board) {
return {
type: 'REMOVE_BOARD',
payload: board,
};
}
function addBoardToList(board) {
return {
type: 'ADD_BOARD_TO_LIST',
payload: board,
};
}
export { addBoard, removeBoard, addBoardToList };
I really don't have a clue what is causing this, maybe it's a bug but I don't know if is react-redux fault or react native itself.
When you remove the board, it looks like in you reducer, you return a strange new state:
case 'REMOVE_BOARD':
newState = {
boards: action.payload,
};
return newState;
Should the boards to be an array always? I think you missed something, for example:
boards: state.boards.filter ((it) => it.id !== action.payload.id),

How to re render sub component on prop change with redux?

I have a react native app using redux and immutable js. When i dispatch an action from my main screen, it goes through my actions, to my reducer and then back to my container, however, the view doesn't update and componentWillReceieveProps is never called. Furthermore, the main screen is a list whose items are sub components Item. Here's the relevant code for the issue, if you want to see more let me know.
Render the row with the data:
renderRow(rowData) {
return (
<Item item={ rowData } likePostEvent={this.props.likePostEvent} user={ this.props.user } removable={ this.props.connected } />
)
}
The part of Item.js which dispatches an action, and shows the result:
<View style={{flex: 1, justifyContent:'center', alignItems: 'center'}}>
<TouchableOpacity onPress={ this.changeStatus.bind(this, "up") }>
<Image source={require('../img/up-arrow.png')} style={s.upDownArrow} />
</TouchableOpacity>
<Text style={[s.cardText,{fontSize:16,padding:2}]}>
{ this.props.item.starCount }
</Text>
<TouchableOpacity onPress={ this.changeStatus.bind(this, "down") }>
<Image source={require('../img/up-arrow.png')} style={[s.upDownArrow,{transform: [{rotate: '180deg'}]}]} />
</TouchableOpacity>
</View>
The action dispatched goes to firebase, which has an onChange handler that dispatches another action.
The reducer:
const initialState = Map({
onlineList: [],
offlineList: [],
filteredItems: [],
connectionChecked: false,
user: ''
})
...
...
case ITEM_CHANGED:
list = state.get('onlineList')
if(state.get('onlineList').filter((e) => e.id == action.item.id).length > 0){
let index = state.get('onlineList').findIndex(item => item.id === action.item.id);
list[index] = action.item
list = list.sort((a, b) => b.time_posted - a.time_posted)
}
return state.set('onlineList', list)
.set('offlineList', list)
The container:
function mapStateToProps(state) {
return {
onlineItems: state.items.get('onlineList'),
offlineItems: state.items.get('offlineList'),
filteredItems: state.items.get('filteredItems'),
connectionChecked: state.items.get('connectionChecked'),
connected: state.items.get('connected'),
user: state.login.user
}
}
Where I connect the onChange:
export function getInitialState(closure_list) {
itemsRef.on('child_removed', (snapshot) => {
closure_list.removeItem(snapshot.val().id)
})
itemsRef.on('child_added', (snapshot) => {
closure_list.addItem(snapshot.val())
})
itemsRef.on('child_changed', (snapshot) => {
closure_list.itemChanged(snapshot.val())
})
connectedRef.on('value', snap => {
if (snap.val() === true) {
closure_list.goOnline()
} else {
closure_list.goOffline()
}
})
return {
type: GET_INITIAL_STATE,
connected: true
}
}
Calling get initial state:
this.props.getInitialState({
addItem: this.props.addItem,
removeItem: this.props.removeItem,
goOnline: this.props.goOnline,
goOffline: this.props.goOffline,
itemChanged: this.props.itemChanged
})
Any suggestions are welcome, thanks so much!
The source of your issue could be with the call to Firebase. If it is an asynchronous call, it's return callback might not be returning something that can be consumed by your action.
Do you know if it is returning a Promise? If that is the case, middleware exists that handle such calls and stops the calling of an action until a correct response is received. One such middleware is Redux-Promise.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore,combineReducers } from 'redux' //Redux.createStore
import { Provider,connect } from 'react-redux';
//Функція яка змінює store
const hello = (state= {message:'none'}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign({}, state, {message:"hello world"});
break
case 'buy':
return Object.assign({}, state, {message:"buy"});
break;
case 'DELETE':
return Object.assign({}, state, {message:"none"});
break;
default :
return state;
}
};
const price = (state= {value:0}, action) => {
switch (action.type) {
case 'HELLO':
return Object.assign({}, state, {value: state.value + 1 });
break;
default :
return Object.assign({}, state, {value:0});
}
};
const myApp = combineReducers({
hello,price
});
//створюємо store
let store = createStore(myApp);
let unsubscribe = store.subscribe(() => console.log(store.getState()))
//VIEW
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>value: {this.props.price}</p>
<a href="#" onClick={this.props.onClick}>click</a><b>{this.props.message}</b>
</div>
)
}
}
//mapStateToProps() для чтения состояния и mapDispatchToProps() для передачи события
const mapStateToProps = (state, ownProps) => {
return {
message: state.hello.message,
price: state.price.value
}
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
var items= ['HELLO','buy','DELETE','error']
var item = items[Math.floor(Math.random()*items.length)];
dispatch({ type: item })
}
}
}
const ConnectedApp = connect(
mapStateToProps,
mapDispatchToProps
)(App);
ReactDOM.render(
<Provider store={store}>
<ConnectedApp />
</Provider>,
document.getElementById('app')
);