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

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};
}

Related

Replace form.change from react-final-form in react-admin v4

I have this component made for react-admin v3 that allows me to generate an id code. Now I'm upgrading to version 4 of react-admin and I don't know how to replace the part of the code where I do.
form.change("referredCode", code);
Here is the complete code of the component.
import React, { useEffect, useState } from "react";
import { TextInput, useDataProvider, LoadingIndicator } from "react-admin";
import { useForm } from "react-final-form";
import { randomIdGenerator } from "../../helpers/randomIdGenerator";
export default function UserReferredCode({ record }) {
const { referredCode } = record;
const [code, setCode] = useState("");
const [isLoading, setIsLoading] = useState(false);
const dataProvider = useDataProvider();
const form = useForm();
useEffect(() => {
if (!referredCode) {
// Generar id aleatorio
setIsLoading(true);
setCode(randomIdGenerator(6));
}
}, []);
useEffect(() => {
if (code) {
dataProvider
.getList("users", {
pagination: { page: 1, perPage: 1 },
filter: { referredCode: code },
})
.then(({ data }) => {
if (data.length > 0) {
setCode(randomIdGenerator(6));
} else {
setIsLoading(false);
}
})
.catch((e) => {
setIsLoading(false);
console.log(e);
});
}
form.change("referredCode", code);
}, [code]);
return (
<>
{isLoading ? (
<LoadingIndicator />
) : (
<TextInput
disabled
source="referredCode"
name="referredCode"
type="text"
placeholder="Code"
initialValue={referredCode || code}
/>
)}
</>
);
}
You should have a Form component wrapping all of this.
Check the Form documentation https://marmelab.com/react-admin/doc/4.0/Form.html
then you you can access it from useFormContext() (from react-hook-form).
the form has action setValue that accepts name and value
https://react-hook-form.com/api/useform/setvalue

React navigation and loading component in react native

Hello I have a react component like which either display a list of items or opens another component which allowes me to select some value. Here is the code
import React, { PureComponent } from "react";
import { Text, View } from "react-native";
import { withNavigationFocus } from "react-navigation-is-focused-hoc";
import { NavigationActions } from "react-navigation";
import { connect } from "react-redux";
import ClassListing from '../../components/ClassListing/ClassListing';
import Actions from "../../state/Actions";
import { default as stackStyles } from "./styles";
import {
StyleCreator
} from "../../utils";
let styles = StyleCreator(stackStyles.IndexStyles)();
#withNavigationFocus
#connect(() => mapStateToProps, () => mapDispatchToProps)
export default class ClassList extends PureComponent {
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
return {
header: null,
tabBarOnPress({ jumpToIndex, scene }) {
params.onTabFocus();
jumpToIndex(scene.index);
},
};
};
constructor(props) {
super(props);
this.state = {};
console.ignoredYellowBox = ["Warning: In next release", "FlatList"];
}
componentDidMount() {
console.log("componentDidMount");
this.props.navigation.setParams({
onTabFocus: this.handleTabFocus
});
}
componentDidUpdate() {
console.log("I am updated");
}
_handleNavigationBar = () => (
<View style={styles.headerContainer}>
<Text style={styles.headerLeftTitle}>Klasselister</Text>
</View>
);
handleTabFocus = () => {
this.props.resetClassList();
// setTimeout(() => {
// this._openChildSchoolSelector();
// },500);
};
_openChildSchoolSelector = () => {
if (
!this.props.childrenSelection.selectedDependant ||
!this.props.childrenSelection.selectedSchool
) {
console.log("navigate to child selector");
this.props.navigation.dispatch(
NavigationActions.navigate({
routeName: "ChildSchoolSelector",
})
);
}
};
render() {
return (
<View>
{this._handleNavigationBar()}
{this.props.classList &&
this.props.classList.classList &&
this.props.classList.classList.length > 0 ? (
<ClassListing list={this.props.classList.classList} />
) : (
null
// this._openChildSchoolSelector()
)}
</View>
);
}
}
const mapStateToProps = (state) => {
const { classList, childrenSelection } = state.appData;
console.log("[Classlist] mapStateToProps", classList);
console.log("[Classlist] mapStateToProps", childrenSelection);
return {
classList,
childrenSelection
};
};
const mapDispatchToProps = (dispatch) => ({
resetClassList: () => {
dispatch(Actions.resetClassList());
},
});
My issue is that when I come to this tab, I want to reset the list which came from server and then open the school selector again, which I am doing in handleTabFocus. But there I have to first call
this.props.resetClassList();
this._openChildSchoolSelector()
The issue is that this._openChildSchoolSelector() is called while this.props.resetClassList() hasn't fininshed.
this.props.resetClassList() works like this
it calls an action like this
static resetClassList() {
return {
type: ActionTypes.RESET_CLASS_LIST
};
}
which then cleans the classlist like this
case ActionTypes.RESET_CLASS_LIST:
return createDefaultState();
in ClassListReducer.js
and also in ChildrenSelectionRedcuer.js
case ActionTypes.RESET_CLASS_LIST:
return createDefaultState();
Any hints to solve this? My project uses very old React navigation v1
One thing which I did was to use this
componentWillReceiveProps(nextProps) {
console.log(this.props);
console.log(nextProps);
if (
nextProps.isFocused &&
nextProps.focusedRouteKey == "ClassList" &&
(!nextProps.childrenSelection.selectedDependant ||
!nextProps.childrenSelection.selectedSchool)
) {
console.log("componentWillReceiveProps");
this._openChildSchoolSelector();
}
if (
this.props.isFocused &&
this.props.focusedRouteKey === "ClassList" &&
nextProps.focusedRouteKey !== "ClassList"
) {
console.log("reset");
this.props.resetClassList();
}
}
But not sure if this is an elegant way of doing this

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};
}

Fetch API in react-native using redux

I have started redux with react-native. I am trying to fetch data from API listing that data using map. Following is my code to fetch data from API.
App.js
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
const App = () => {
return (
<Provider store={store}>
<ProductList />
</Provider>
);
};
export default App;
productList.js
class ProductList extends React.Component {
componentDidMount() {
this.props.dispatch(fetchProducts());
}
render() {
const { products } = this.props;
return (
<View>
{products.map(product => (
<Text key={product.title}>{product.title}</Text>
))}
</View>
);
}
}
const mapStateToProps = state => ({
products: state.products.items,
});
export default connect(mapStateToProps)(ProductList);
productAction.js
async function getProducts() {
return fetch("https://rallycoding.herokuapp.com/api/music_albums")
.then(res => res.json());
}
export function fetchProducts() {
return async dispatch => {
return getProducts()
.then(json => {
dispatch(fetchProductsSuccess(json));
return json;
})
.catch(error =>
console.log(error)
);
};
}
export const FETCH_PRODUCTS_SUCCESS = "FETCH_PRODUCTS_SUCCESS";
export const fetchProductsSuccess = products => ({
type: FETCH_PRODUCTS_SUCCESS,
payload: { products }
});
productReducer.js
const initialState = {
items: [],
};
export default function productReducer(
state = initialState,
action
) {
switch (action.type) {
case FETCH_PRODUCTS_SUCCESS:
return {
...state,
items: action.payload.products
};
default:
return state;
}
}
rootReducer.js
export default combineReducers({
products
});
It is working perfectly. It is showing the list as well.
But can anyone please tell me is it a correct way if I will use this method in big projects then will it be useful or should I follow some other method? Thanks in advance.
I've not used fetch with react-native, but I think this should work fine. I've used axis though. And it is easy to use
import * as axios from 'axios';
import Constant from './../utilities/constants';
axios.defaults.baseURL = Constant.api_base_url;;
axios.defaults.headers.post['Content-Type'] = 'application/json';
// Axios interceptor for handling common HTTP errors
// Need to use it with reducers
axios.interceptors.response.use(res => res, err => Promise.reject(error));
/**
* HTTP request to search item in The MovieDB
*
* #returns {object | Promise}
*/
const getConfiguration = () => {
return axios.get(`/configuration?${Constant.api_key}`)
}
export { getConfiguration }
You can view complete code https://github.com/SandipNirmal/React-Native-MovieDB.

React native show a strange behavior. Can someone explain?

I'am creating a simple application with authentication. To change a state using redux with react-native-navigation (v1). For example, index.js
...
import { Navigation, } from 'react-native-navigation';
import { Provider, } from 'react-redux';
import store from './src/store';
import registerScreens from './src/screens';
registerScreens(store, Provider);
class App {
constructor () {
this.auth = false;
store.subscribe(this.onStoreUpdate.bind(this));
this.start();
}
onStoreUpdate () {
const state = store.getState();
if (this.auth != state.auth) {
this.auth = state.auth;
this.start();
}
}
start () {
switch (this.auth) {
case false:
Navigation.startTabBasedApp({
tabs: [{
screen: 'navigation.AuthScreen',
}, {
screen: 'navigation.RegisterScreen',
},],
});
break;
case true:
Navigation.startSingleScreenApp({
screen: {
screen: 'navigation.MainScreen',
},
});
break;
}
}
}
const application = new App();
Store is listening an update and change an application layout if need.
AuthScreen show a simple ActivityIndicator, when server request is perform. For example, auth.js
...
import { bindActionCreators, } from 'redux';
import { connect, } from 'react-redux';
import * as actions from './../actions';
...
class AuthScreen extends Component {
constructor (props) {
super(props);
this.state = {
loading: false,
...
};
this.handlePressEnter = this.handlePressEnter.bind(this);
}
handlePressEnter () {
...
this.loadingState(true);
jsonFetch(url, {
method: 'POST',
body: JSON.stringify({...}),
}).then((value) => {
this.loadingState();
this.props.actions.auth(true);
}).catch((errors) => {
this.loadingState();
console.log('Error while auth', errors);
});
}
...
loadingState (state = false) {
this.setState({
loading: state,
});
}
render () {
return (<View>
...
<Modal visible={this.state.loading} transparent={true} animationType="none" onRequestClose={() => {}}>
<View>
<ActivityIndicator size="large" animating={this.state.loading} />
</View>
</Modal>
</View>);
}
}
function mapStateToProps (state, ownProps) {
return {};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps) (AuthScreen);
I'am starting application with iOS simulator and try to authenticate. It show me activity indicator, then indicator disappear, but layout does not change. And strange behavior, if I comment this.loadingState(true); and this.loadingState(); in auth.js layout changes with success.
Can someone explain to me, why layout does not change from auth to main when activity indicator using?
I think that you can use dispatch props for loading.
For example When you call this.props.actions.auth(true);
You can return loading reducers.
handlePressEnter () {
...
dispatch({ type:'loading', loading: true });
jsonFetch(url, {
method: 'POST',
body: JSON.stringify({...}),
}).then((value) => {
dispatch({ type:'loading', loading: false });
this.props.actions.auth(true);
}).catch((errors) => {
this.loadingState();
console.log('Error while auth', errors);
});
}
And than you can use
<ActivityIndicator size="large" animating={this.props.loading} />
But dont forget the reducers return