Dispatching an action does not re-render the app automatically - react-native

I have deleted an item from the redux store. but it does not re-render the application. if I manually rerender the app, then deleted item went. I am doing this method for reading and un-reading the message. but it works fine. deleting also work, but it is not re-render the app automatically. if I trigger the re-render manually then the message was gone.
Reducer.js
import { combineReducers } from 'redux'
import {
DELETE_MESSAGE,
ADD_MESSAGE,
READ_MESSAGE,
UN_READ_MESSAGE,
ARCHIVED_MESSAGE,
UN_ARCHIVED_MESSAGE
} from './Actions'
// reducers
const messageReducer = (state = [], action) => {
switch (action.type) {
case DELETE_MESSAGE:
return state.filter(value => value.key != action.payload.key)
case ADD_MESSAGE:
return [...state, action.payload]
case READ_MESSAGE:
return state.map(value => {
if (value.key == action.payload.key) {
value.read = 'true'
return value
}
return value
})
case UN_READ_MESSAGE:
return state.map(value => {
if (value.key == action.payload.key) {
value.read = 'false'
return value
}
return value
})
case ARCHIVED_MESSAGE:
return state.map(value => {
if (value.key == action.payload.key) {
value.archived = 'true'
return value
}
return value
})
case UN_ARCHIVED_MESSAGE:
return state.map(value => {
if (value.key == action.payload.key) {
value.archived = 'false'
return value
}
return value
})
default:
return state
}
}
// combine reducer
const reducer = combineReducers({
message: messageReducer
})
export default reducer
Actions.js
// action types
export const DELETE_MESSAGE = 'DELETE_MESSAGE'
export const ADD_MESSAGE = 'ADD_MESSAGE'
export const READ_MESSAGE = 'READ_MESSAGE'
export const UN_READ_MESSAGE = 'UN_READ_MESSAGE'
export const ARCHIVED_MESSAGE = 'ARCHIVED_MESSAGE'
export const UN_ARCHIVED_MESSAGE = 'UN_ARCHIVED_MESSAGE'
// action creators
export const deleteMessage = (message) => ({
type: DELETE_MESSAGE,
payload: message
})
export const addMessage = (message) => ({
type: ADD_MESSAGE,
payload: message
})
export const readMessage = (message) => ({
type: READ_MESSAGE,
payload: message
})
export const unReadMessage = (message) => ({
type: UN_READ_MESSAGE,
payload: message
})
export const archivedMessage = (message) => ({
type: ARCHIVED_MESSAGE,
payload: message
})
export const unArchivedMessage = (message) => ({
type: UN_ARCHIVED_MESSAGE,
payload: message
})
deleting page .js
import React from 'react'
import { StyleSheet, View, TouchableHighlight, Alert } from 'react-native'
import { readMessage } from '../../../Redux/Actions'
import { unReadMessage } from '../../../Redux/Actions'
import { deleteMessage } from '../../../Redux/Actions'
import { connect } from 'react-redux'
import Icons from './Icon' // trash-alt
class HiddenRight extends React.Component {
delete = (data) => {
Alert.alert(
'Would you like to Delete?',
'You will permanently remove this message from your mobile local storage',
[
{text: 'Cancel', onPress: () => this.props.delete({})},
{text: 'Delete', onPress: () => this.props.delete(data)}
]
)
}
render () {
return (
<View style={styles.container}>
<TouchableHighlight onPress={() => this.delete(this.props.data) }>
<Icons iconName='trash' title='Delete' backgroundColor='#f80101' />
</TouchableHighlight>
{this.props.data.read == 'false'
? <TouchableHighlight onPress={() => this.props.read(this.props.data)}>
<Icons iconName='envelope' title='Read' backgroundColor='#007AFF' />
</TouchableHighlight>
: <TouchableHighlight onPress={() => this.props.unRead(this.props.data)}>
<Icons iconName='envelope-open' title='UnRead' backgroundColor='gray' />
</TouchableHighlight>
}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end'
}
})
export default connect(null, { delete: deleteMessage, read: readMessage, unRead: unReadMessage})(HiddenRight)
Store.js
import { createStore } from 'redux'
import reducer from './Reducer'
const DEFAULT_STATE = {
message: [{
'key': '0',
'title': 'Hello, It about my self. Keep watch',
'date': '02/89/3456',
'body': 'Nive to here you all, I am not crazy, Well will you find. yes Of course. I will be there on 56-78-2',
'read': 'false',
'archived': 'false'
}, {
'key': '1',
'title': 'Hello, It about my self. Keep watch',
'date': '02/89/3456',
'body': 'Nive to here you all, I am not crazy, Well will you find. yes Of course. I will be there on 56-78-2',
'read': 'false',
'archived': 'false'
}]
}
// store
const store = createStore(reducer, DEFAULT_STATE)
export default store
MessageScreen.js
...
...
function mapStateToProps(state){
return {
listViewData:state
}
}
export default connect(mapStateToProps)(MessageScreen)
I am expecting that if press delete button, then the message should remove.
Note: read and unread are working fine

I have solved this issue. The mistake I did is, I was storing the prop value which comes from the store directly inside the component state. So it's not re-render because when props change only the render & componentPropsWillChange run. so storing the value in the state means data not change the state first time when a prop changes. Thus the case I have faced.

Related

useEffect not triggered

I'm trying to use redux with useEffect to update/get the redux state but useEffect is totally not running at all but I have no idea what is going on. I can't even get the "hi"
import { useSelector, useDispatch } from 'react-redux';
import { setDisplayLogsheet, getLogsheets } from '../redux/actions';
...
const { displayLogsheet, logsheets } = useSelector(state => state.logsheetReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log("hi")
dispatch(getLogsheets());
dispatch(setDisplayLogsheet(logsheets));
}, []);
Any help please? Thanks
UPDATE: here's more code to understand
App.js:
I have added the store inside the provider
const Stack = createStackNavigator();
export default function App() {
return(
<Provider store={Store}>
<NavigationContainer>
...
<Provider />
}
home.js:
tried to useSelector to get the logsheets and displayLogsheets and useEffect to dispatch, but the the useEffect is totally not running
export default function Home({navigation}) {
const { displayLogsheet, logsheets } = useSelector(state => state.logsheetReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log('getting logsheets...')
dispatch(getLogsheets())
}, [dispatch])
useEffect(() => {
console.log('setting displayLogsheet...')
if(logsheets){
dispatch(setDisplayLogsheet(logsheets))
}
}, [dispatch, logsheets])
console.log(logsheets)
console.log(displayLogsheet)
return (
<>
<SafeAreaView>
<ScrollView>
<HomeTopStack logsheet={displayLogsheets} iterateDocket={iterateDocket} />
<ScanBarcodeButton navigation={navigation} />
{displayLogsheets.data.DO.map(logsheet => (
<TouchableOpacity onPress={() => navigation.navigate('Details', logsheet)}>
<DOCards logsheet={displayLogsheets} />
</TouchableOpacity>
))}
</ScrollView>
</SafeAreaView>
</>
)
}
store.js:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logsheetReducer from './reducers';
const rootReducer = combineReducers({ logsheetReducer });
export const Store = createStore(rootReducer, applyMiddleware(thunk));
reducer.js:
this is the reducer to set display logsheet and also to get the dummy logsheet data
import { SET_DISPLAY_LOGSHEET, GET_LOGSHEETS } from "./actions";
const initialState = {
logsheets: {},
displayLogsheet: {},
}
function logsheetReducer(state = initialState, action) {
switch (action.type) {
case SET_DISPLAY_LOGSHEET:
console.log("inside logsheetReducer, SET_DISPLAY_LOGSHEET")
return { ...state, displayLogsheet: action.payload };
case GET_LOGSHEETS:
console.log("inside logsheetReducer, GET_LOGSHEET")
return { ...state, logsheets: action.payload };
default:
return state;
}
}
export default logsheetReducer;
actions.js:
import CreateFakeLogsheets from "../data/logsheet";
export const SET_DISPLAY_LOGSHEET = 'SET_DISPLAY_LOGSHEET';
export const GET_LOGSHEETS = 'GET_LOGSHEETS';
const logsheets = CreateFakeLogsheets(2,3)
export const getLogsheets = () => {
console.log("inside getLogsheets")
try {
return dispatch => {
dispatch({
type: GET_LOGSHEETS,
payload: logsheets
})
}
} catch (error) {
console.log(error)
}
}
export const setDisplayLogsheet = displayLogsheet => {
console.log("inside setDisplayLogsheets")
return dispatch => {
dispatch({
type: SET_DISPLAY_LOGSHEET,
payload: displayLogsheet
});
}
};
here's most of the code with redux and also the useEffect. Any help please
Without knowing how the rest of the code is structured, I would split the effect in two, like this:
useEffect(() => {
console.log('getting logsheets...')
dispatch(getLogsheets())
}, [dispatch])
useEffect(() => {
console.log('setting displayLogsheet...')
if(logsheets){ // only dispatch this if logsheets have been fetched
dispatch(setDisplayLogsheets(logsheets))
}
}, [dispatch, logsheets])

I am using redux in react native application to fetch and display data but its not updating on data change from backend

I am using Redux in my React-Native application.
I am fetching the data from api call and on success rendoring it on ListItem.
I am able to fetch and display data but data is not auto updating unless and until I revisit the page.
Even values are not storing into the app
I am calling method from actions in constructor and componentDidMount method.
Can you Please check the code and tell me where am I going wrong.
Here is action.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './types';
export const fetchingProductRequest = () => ({
type : FETCHING_PRODUCT_REQUEST
});
export const fetchingProductSuccess = (json) => ({
type : FETCHING_PRODUCT_SUCCESS,
payload : json
});
export const fetchingProductFailure = (error) => ({
type : FETCHING_PRODUCT_FAILURE,
payload : error
});
export const fetchProduct = () => {
return async dispatch => {
dispatch(fetchingProductRequest());
try {
let response = await fetch("http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop");
let json = await response.json();
dispatch(fetchingProductSuccess(json));
} catch(error) {
dispatch(fetchingProductFailure(error));
}
}
}
My reducer.js
import {
FETCHING_PRODUCT_REQUEST,
FETCHING_PRODUCT_SUCCESS,
FETCHING_PRODUCT_FAILURE
} from './../actions/types';
const initialState = {
loading : false,
errorMessage : '',
shops: []
}
const products = ( state = initialState, action ) => {
switch(action.type) {
case FETCHING_PRODUCT_REQUEST :
return { ...state, loading: true} ;
case FETCHING_PRODUCT_SUCCESS :
return { ...this.state, loading: false, shops: action.payload };
case FETCHING_PRODUCT_FAILURE :
return { ...state, loading: false, errorMessage: action.payload};
}
};
export default products;
product.js
import * as React from 'react';
import { FlatList , ActivityIndicator} from 'react-native';
import { ListItem } from 'react-native-elements';
import { fetchProduct } from './../../actions/products';
import { connect } from 'react-redux';
import propTypes from 'prop-types';
class Product extends React.Component {
constructor(props) {
super(props);
this.props.fetchProduct();
this.state = {
loading : true,
shops : '',
isFetching: false,
active : true,
}
}
fetchProducts() {
const shopid = 13;
fetch(`http://phplaravel-325095-1114213.cloudwaysapps.com/api/shop/shop`)
.then(response => response.json())
.then((responseJson)=> {
this.setState({
loading: false,
shops: responseJson
})
alert(JSON.stringify(this.state.shops));
})
.catch(error=>console.log(error)) //to catch the errors if any
}
componentDidMount(){
// this.fetchProducts();
this.props.fetchProduct().then(this.setState({loading : false}));
}
renderItem = ({ item }) => (
<ListItem
title={item.name}
subtitle={item.name}
leftAvatar={{
source: item.avatar && { uri: item.avatar },
title: item.name[0]
}}
bottomDivider
chevron
/>
)
render () {
if(!this.state.loading)
{
if(this.props.shopsInfo.loading)
{
return (
<ActivityIndicator/>
)
}
else
{
return (
<FlatList
vertical
showsVerticalScrollIndicator={false}
data={this.props.shopsInfo.shops}
renderItem={this.renderItem}
/>
)
}
}
else
{
return (
<ActivityIndicator/>
)
}
}
}
Product.propTypes = {
fetchProduct: propTypes.func.isRequired
};
const mapStateToProps = (state) => {
return { shopsInfo: state };
}
function mapDispatchToProps (dispatch) {
return {
fetchProduct: () => dispatch(fetchProduct())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Product);
1. Not updating on data change from backend.
You have to call an api on regular interval to get updated data. Redux implementation doesn't mean it will fetch data from server whenever there is any change.
2. Even values are not storing into the app
If you are expecting redux will store data even if you will close/kill an application than it will not. You have persist data in-order to use it or store it in cache. Take a look at redux-persist
The problem is your passing wrong props in mapStateToProps function.
In reducer your updating the response value in shop props.
In order to get the updated value you need to pass shops property to get the value.
const mapStateToProps = (state) => {
const { shops: state };
return {shops};
}

Connect react-redux to stateless component. Properties inside the function are not updated

Problem:
I have very simple todo app. There is one action - add todo. When I add a task, I simulate sending it to the server using a setTimeout.
When I receive a response from the server, I immediately check to see if there is an error to avoid further action. In stateful component, everything works, and in stateless component it doesn't.
See the code to better understand the problem.
Environment:
"react": "16.8.6",
"react-native": "0.60.5",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
№ 1. Stateful component:
import React, {Component} from 'react';
import {View, Button, ActivityIndicator} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
class MainScreen extends Component {
todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
sendTodoToServer = async () => {
const todo = this.todoGenerator();
const {addTodo} = this.props;
await addTodo(todo);
// this
const {error} = this.props;
if (error) {
console.log('error', error);
}
};
render() {
const {isLoading} = this.props;
return (
<View>
<Button title="Generate todo" onPress={this.sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
}
}
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreen);
№ 1. Stateful component. Console:
As you can see,
const {error} = this.props;
if (error) {
console.log('error', error);
}
it's work. Okay, let's move on to functional components
№ 2. Stateless component with redux connect:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenFC = ({isLoading, addTodo, error}) => {
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await addTodo(todo);
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenFC);
№ 2. Stateless component with redux connect. Console:
The error did not display in the console, although it is in the reducer
№ 3. Stateless component with redux HOOKS:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect, shallowEqual, useDispatch, useSelector} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenReduxHooks = () => {
const todos = useSelector((state: AppState) => state.todos, shallowEqual);
const error = useSelector((state: AppState) => state.error, shallowEqual);
const isLoading = useSelector(
(state: AppState) => state.isLoading,
shallowEqual,
);
const dispatch = useDispatch();
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await dispatch(addTodoAction(todo));
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenReduxHooks);
№ 3. Stateless component with redux HOOKS. Console:
It's the same here, as in the second example.
Questions:
Can redux be connected to a stateless component?
How do you make the second and third example work the same way as the first?
Other code:
App.js
import React from 'react';
import {Provider} from 'react-redux';
import {MainScreen, MainScreenFC, MainScreenReduxHooks} from './src/screens';
import store from './src/redux';
const App = () => {
return (
<Provider store={store}>
<MainScreenFC />
</Provider>
);
};
export default App;
store.js:
import {applyMiddleware, createStore} from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducer';
export default createStore(rootReducer, applyMiddleware(thunk, logger));
reducer.js:
const ADD_TODO_REQUEST = 'ADD_TODO_REQUEST';
const ADD_TODO_SUCCESS = 'ADD_TODO_SUCCESS';
const ADD_TODO_FAILURE = 'ADD_TODO_FAILURE';
const initialState = {
todos: [],
isLoading: false,
error: undefined,
};
export const addTodo = data => async dispatch => {
dispatch({
type: ADD_TODO_REQUEST,
payload: {
isLoading: true,
},
});
try {
const todo = await new Promise((resolve, reject) => {
setTimeout(() => {
reject('Ooops, error');
}, 3000);
});
dispatch({
type: ADD_TODO_SUCCESS,
payload: {
todo,
isLoading: false,
},
});
} catch (e) {
dispatch({
type: ADD_TODO_FAILURE,
payload: {
isLoading: false,
error: e,
},
});
}
};
export default function(state = initialState, {type, payload}) {
switch (type) {
case ADD_TODO_REQUEST: {
return {
...state,
isLoading: true,
};
}
case ADD_TODO_SUCCESS: {
return {
...state,
isLoading: false,
todos: [...state.todos, payload.todo],
};
}
case ADD_TODO_FAILURE: {
return {
...state,
isLoading: false,
error: payload,
};
}
default:
return state;
}
}
I think the problem with your code is that you try to wait for a response in the component, it's a bad idea for both stateful and stateless components. What would I recommend you to do, is to handle error somewhere in your middleware (redux-thunk, redux-saga, etc). In this case, your component should just represent data, and if you have to display an error, just take it from props, I believe it is stored somewhere in redux.
In any way, stateless components shouldn't be an async function, because the result of an async function is a promise but not a component. There are some libraries like async-reactor, but personally, I prefer to go with another approach.
If you tell me your use case, what would you like to do once you got an error, I'll give you a more useful answer.
Update:
export const addTodo = data => dispatch => {
// tell your application that request is sending,
// so you can handle it in UI (show a progress indicator)
dispatch({
type: ADD_TODO_REQUEST,
payload: data
});
try {
const response = await createTodo(data);
dispatch({
type: ADD_TODO_SUCCESS,
payload: response
});
// here you can dispatch navigation action as well
} catch (error) {
dispatch({
type: ADD_TODO_FAILURE,
error
});
// and here you can dispatch action with a toast
// to notify users that something went wrong
}
};

Loading parameter from store is undefined

I have a screen with jobs that I fetch from an API. In my screen, I want to show a message until the jobs are fetched, then display them on the screen. I am triggering the jobs fetch in componentDidMount(), then trying to display them in RenderJobs(). The problem is that props.isLoading is undefined for whatever reason.
I am using hardcoded values for the API call at the moment. Once I get the data to display properly, I'll change this.
Here is my JobsComponent:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {fetchJobs} from '../redux/ActionCreators';
const mapStateToProps = state => {
return {
jobs: state.jobs
}
}
const mapDispatchToProps = dispatch => ({
fetchJobs: (address, jobTitle) => dispatch(fetchJobs(address, jobTitle))
});
function RenderJobs(props) {
console.log('In RenderJobs, props is: ' + props.jobsData + ' / ' + props.isLoading);
const renderJobItem = ({item, index}) => {
return (
//UI view to show data
);
}
if (props.isLoading) {
return (
<View>
<Text style={{fontSize: 30, color: colors.white}}>The data is loading...</Text>
</View>
);
}
else if (props.errMess) {
return(
<View>
<Text style={{fontSize: 30, color: colors.white}}>{props.errMess} </Text>
</View>
);
}
else {
return (
//UI view to show data
);
}
}
class Jobs extends Component {
componentDidMount() {
this.props.fetchJobs([26.1410638, 44.4346588], "Developer");
}
render() {
return(
<ScrollView contentContainerStyle={styles.bkg}>
<RenderJobs
jobsData={this.props.jobs}
isLoading={this.props.jobs.isLoading}
errMess={this.props.jobs.errMess}
/>
</ScrollView>
)
}
}
This is my reducer:
import * as ActionTypes from '../ActionTypes';
export const jobs = (state = { isLoading: true,
errMess: null,
jobs:[]}, action) => {
switch (action.type) {
case ActionTypes.GET_JOBS:
return {...state, isLoading: false, errMess: null, jobs: action.payload};
case ActionTypes.JOBS_LOADING:
return {...state, isLoading: true, errMess: null, jobs: []}
case ActionTypes.JOBS_FAILED:
return {...state, isLoading: false, errMess: action.payload};
default:
return state;
}
};
And this is the action creator:
export const fetchJobs = (address, job) => async (dispatch) => {
dispatch(jobsLoading());
var obj = {"origin": [26.1410638, 44.4346588], "job_details": ["Developer"]};
//fetch the data
.then(response => response.json())
.then(jobs => dispatch(addJobs(jobs)))
.catch(error => dispatch(jobsFailed(error.message)));
};
export const addJobs = (jobs) => ({
type: ActionTypes.GET_JOBS,
payload: jobs
});
export const jobsLoading = () => ({
type: ActionTypes.JOBS_LOADING
});
export const jobsFailed = (errmess) => ({
type: ActionTypes.JOBS_FAILED,
payload: errmess
});
I am expecting for 2 things to happen.
In the RenderJobs() function, I am counting on props.isLoading to give me the loading state. However, it is undefined. I can see in the logs that the JOBS_LOADING action is dispatched, and that the jobs data is correctly fetched.
Once the jobs data is fetched, I expect it to be displayed in the UI. However, this is not the case - I just see a blank screen.
Any help will be greatly appreciated!
Looks like you have forgotten to add isLoading in your mapStateToProps.
isLoading will be undefined on first render as you have not defined any default value. You could provide default value using default props.
Jobs.defaultProps = {
jobs: {
isLoading: false
}
}
I found out what the issue was. In my configureStore.js, I had forgotten to add the jobs reducer to the store. Thanks to everyone for your answers!
import {jobTitles} from './reducers/jobTitles';
import {jobs} from './reducers/jobs';
import {persistStore, persistCombineReducers} from 'redux-persist';
import storage from 'redux-persist/es/storage';
export const ConfigureStore = () => {
const config = {
key: 'root',
storage,
debug: true
};
const store = createStore(
persistCombineReducers(config, {
jobTitles,
jobs //I added this line and it fixed the problem!
}),
applyMiddleware(thunk, logger)
);
const persistor = persistStore(store);
return {persistor, store};
}

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')
);