I am new to using react native redux and I am facing an issue that the api call is made only once, what if i click on another button which should render a different response based on the params and display it on the component which is a flatlist in my case. Please have a look at my code.
RecordListAction:
import { FETCH_RECORD_LIST, FETCH_RECORD_SUCCESS, FETCH_RECORD_FAILURE } from './types.js'
export const fetchRecordList = () => ({
type: FETCH_RECORD_LIST
})
export const fetchRecordSuccess = json => ({
type: FETCH_RECORD_SUCCESS,
payload: json
})
export const fetchRecordFailure = error => ({
type: FETCH_RECORD_FAILURE,
payload: error
})
export const fetchRecordListApi = () => {
console.log("Now I'm here!")
return async dispatch => {
dispatch(fetchRecordList());
let response = await
fetch(url, {
method: 'POST',
headers: {
'tenantid': '1',
'Content-Type': 'application/json',
'language': '1',
'userid': '11'
},
body: JSON.stringify(global.recordListBody)
}).then((response) => response.json())
.then((responseJson) => {
console.log("RecordList Action Value" + responseJson)
dispatch(fetchRecordSuccess(responseJson.records));
}).catch(error => {
dispatch(fetchRecordFailure(error))
}) }}
recordListReducer.js:
import {FETCH_RECORD_REQUEST,FETCH_RECORD_SUCCESS,FETCH_RECORD_FAILURE}
from "../actions/types"
const initialState = {
isFetching: false,
errorMessage : '',
record :[]
};
const recordListReducer = (state = initialState,action) => {
switch(action.type){
case FETCH_RECORD_REQUEST:
return { ...state, isFetching: true }
case FETCH_RECORD_FAILURE:
return { ...state, isFetching: false, errorMessage: action.payload };
case FETCH_RECORD_SUCCESS:
return{...state, isFetching:false, record:action.payload}
default:
return state
}};
export default recordListReducer;
RecordListContainer.js
import React, { Component } from 'react'
import { Text, View, StyleSheet, ActivityIndicator, Button } from 'react-native'
import PropTypes from 'prop-types';
import {fetchRecordListApi} from "../redux/actions/recordListAction"
import {connect} from "react-redux";
import DetailsViewMode from '../Enums/DetailsViewMode'
import RecordList from '../Components/RecordListComponents/RecordList';
import { Icon, Divider } from 'react-native-elements';
class RecordListContainer extends Component {
constructor(props) {
super(props);
}
componentDidMount(){
this.props.dispatch(fetchRecordListApi());
}
render(){
let content = <RecordList record = {this.props.recordList.record}/>
if(this.props.recordList.isFetching){
content= <ActivityIndicator size="large" />
}
}}
RecordListContainer.propTypes = {
fetchRecordListApi : PropTypes.func.isRequired,
recordList : PropTypes.object.isRequired}
const mapStateToProps = state =>{
return{
recordList: state.posts
};
}
export default connect(mapStateToProps)(RecordListContainer);
rootReducer.js :
import recordListReducer from './recordListReducers';'
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
posts : recordListReducer,
});
export default rootReducer;
You could make recordListBody part of redux state or react context. Or you could make recordListBody observable and respond to changes. Here is an example of making recordListBody observable:
//object combined with global.recordListBody to add listeners
// and notify them of changes
const recordListBodyObserver = ((observers) => {
const removeObserver = (observer) => () => {
observers = observers.filter((o) => o !== observer);
};
return {
notify: (value) =>
observers.forEach((observer) => observer(value)),
add: (observer) => {
observers.push(observer);
return removeObserver(observer);
},
};
})([]);
let recordListBodyValue;
//your global object with recordListBody that will notify
// listeners if a value for recordListBody is set
const global = {
set recordListBody(value) {
//notify all listeners;
recordListBodyObserver.notify(value);
//set the new value
return (recordListBodyValue = value);
},
get recordListBody() {
return recordListBodyValue;
},
};
//function to create increasing id
const id = ((id) => () => id++)(1);
class App extends React.PureComponent {
componentDidMount() {
this.removeListener = recordListBodyObserver.add(
(value) => {
//you can dispatch your action here using value
// do not use global.recordListBody here becasue
// that has the old valuee
console.log(
'recordListBody changed from:',
global.recordListBody,
'to value:',
value
);
}
);
}
componentWillUnmount() {
//clean up listener when component unmounts
this.removeListener();
}
render() {
return (
<button
onClick={() => (global.recordListBody = id())}
>
Change recordListBody
</button>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I am using componentDidUpdate and check if props value is changed, the api is again called when the body coming in props is changed.
Related
I am a new in react native Redux Newest Documentation. I want to use createAsyncThunk
to get my api data using Axios.
Below my code :
App.js
import React, { useState } from 'react';
import { Provider } from 'react-redux';
import { configureStore } from '#reduxjs/toolkit';
import ApiChartingSlice from './redux/slice/api/ApiChartingSlice';
import LoginNavigator from './navigation/LoginNavigator';
const store = configureStore({
reducer: {
ApiChartingMenu: ApiChartingSlice
}
});
export default function App() {
return (
<Provider store={store}>
<LoginNavigator />
</Provider>
);
}
ApiChartingAxios.js
import axios from "axios";
import { BasicAuthUsername, BasicAuthPassword } from "../utility/utility";
export default axios.create({
baseURL: 'https://blablabla.id/index.php',
headers: {
auth: {
username: BasicAuthUsername,
password: BasicAuthPassword
}
}
});
SubApiChartingAxios.js
import ApiChartingAxios from "../ApiChartingAxios";
export const SubApiChartingMenu = async () => {
const response = await ApiChartingAxios.get('/ApiChartingMenu',{
params: null
});
return response;
};
ApiChartingSlice.js
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit'
import { SubApiChartingMenu } from '../../../api/subapi/SubApiChartingAxios';
export const ApiChartingMenuThunk = createAsyncThunk(
'ApiChartingMenu',
async () => {
const response = await SubApiChartingMenu();
console.log(response);
return response.data.Data;
}
)
// status: 'idle' | 'loading' | 'succeeded' | 'failed',
const ApiChartingMenuSlice = createSlice({
name: 'ApiChartingMenu',
initialState: {
apiData: {},
status: 'idle',
error: null
},
reducers: {},
extraReducers: {
[ApiChartingMenuThunk.pending.type]: (state, action) => {
state.playerList = {
status: "loading",
apiData: {},
error: {},
};
},
[ApiChartingMenuThunk.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
apiData: action.payload,
error: {},
};
},
[ApiChartingMenuThunk.rejected.type]: (state, action) => {
state.playerList = {
status: "idle",
apiData: {},
error: action.payload,
};
},
}
});
export default ApiChartingMenuSlice.reducer;
And the last is my screen output:
ChartScreen.js
import { useNavigation } from '#react-navigation/native';
import React, { useEffect, useState, useCallback } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, KeyboardAvoidingView, TextInput, Button } from 'react-native';
import { useDispatch, useSelector } from 'react-redux';
import { ApiChartingMenuSlice, ApiChartingMenuThunk } from '../../redux/slice/api/ApiChartingSlice';
const ChartScreen = () => {
console.log('ChartScreen');
const dispatch = useDispatch();
console.log(dispatch(ApiChartingMenuThunk()));
const chartData = useSelector(state => state.ApiChartingMenu.apiData);
console.log(chartData);
return (
<View>
<Button title="test" onPress={() => {}} />
<ChartComponent />
</View>
);
};
export default ChartScreen;
Problem:
I don't know why in my ChartScreen.js this line : console.log(dispatch(ApiChartingMenuThunk()));
return :
Promise {
"_U": 0,
"_V": 0,
"_W": null,
"_X": null,
"abort": [Function abort],
"arg": undefined,
"requestId": "oqhkA7eyL_VV_ea4FDxr3",
"unwrap": [Function unwrap],
}
But in ApiChartingSlice.js in this line console.log(response);
return the correct value.
So, what is the correct way to retrive the value from the createAsyncThunk from my ChartScreen.js
The Api content is a list menu.
I want when first open the apps It execute the redux and show all my list menu.
But now just try to console.log the ApiChartingMenuThunk in ApiChartingSlice.js is not working.
Can anybody solve and guide me to a solution ? Thank You
I figured it out myself the problem is on this file :
ApiChartingSlice.js
and this line should be :
[ApiChartingMenuThunk.fulfilled.type]: (state, action) => {
state.playerList = {
status: "idle",
apiData: state.apiData.push(action.payload),
error: {},
};
also you need to dispatch using this code :
in file ChartScreen.js
this is how we dispatch it.
const toggleGetMenuHandler = useCallback(() => {
dispatch(ApiChartingMenuThunk())
.unwrap()
.then((originalPromiseResult) => {
// console.log(originalPromiseResult);
})
.catch((rejectedValueOrSerializedError) => {
console.log(rejectedValueOrSerializedError);
})
}, [dispatch]);
useEffect(() => {
toggleGetMenuHandler();
}, [toggleGetMenuHandler]);
},
Now this code : const chartData = useSelector(state => state.ApiChartingMenu.apiData);
will have a correct data.
I am trying to learn redux.
I watch some tutorials and follow along with them. These tutorials are with class component.
So I try to change these into functional component.
Since I am just learning and not trying to make a big project I put actions, reducers and types into 1 file.
This is that file
import axios from 'axios';
export const FETCH_NEWS = 'FETCH_NEWS';
// Reducer
const initialState = {
newsList: [],
};
export const articlesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_NEWS:
return {...state, newsList: action.payload};
default:
return state;
}
};
export const fetchNews = () => (dispatch) => {
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((res) => {
dispatch({
type: FETCH_NEWS,
payload: res.data,
});
})
.catch((err) => {
console.log(err);
});
};
So I am using fetchNews props in News component
News component is like this
import { fetchNews }from '../../ducks/modules/Articles'
useEffect(() => {
fetchNews();
console.log('##############################')
console.log(newsList)
console.log('##############################')
},[])
const News = ({navigation, newsList, fetchNews}) => {
return (<View> .... </View>)
}
News.propTypes = {
fetchNews: PropTypes.func.isRequired,
newsList: PropTypes.array.isRequired
}
const mapStateToProps = state => {
return {
newsList: state.articlesReducer.newsList
}
}
export default connect(mapStateToProps, { fetchNews })(News);
As you can see I am console.logging in the useEffect hooks , I am console logging because no data are being loaded in the device
Here is a picture of empty array when component is mounted
My store component is like this
const reducer = combineReducers({
articlesReducer
});
const store = createStore(reducer, applyMiddleware(thunk,logger));
You are not dispatching the action correctly. I have added simpler way to use redux with function based components. You don't need to use connect.
export const fetchNews = () => (dispatch) => {
axios
.get('https://jsonplaceholder.typicode.com/users')
.then((res) => {
dispatch({
type: FETCH_NEWS,
payload: res.data,
});
})
.catch((err) => {
console.log(err);
});
};
export const selectNewsList = (state) => state.newsList; // this is known as a selector.
And your view will be:
import { useSelector, useDispatch } from 'react-redux';
import { fetchNews, selectNewsList }from '../../ducks/modules/Articles'
const News = () => {
const newsList = useSelector(selectNewsList);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchNews());
},[])
console.log(newsList); // This will print empty array first, but will print again as data is populated.
return (<View> .... </View>)
}
I am having troubles with getting the state in my HomeComponent.js . Every time I try to print it, it return "undefined" .
I've tried different ways to call onPress in my Home component (e.g. onPress={this.printState()}, but none work)
This is my HomeComponent.js
//import statements
const mapStateToProps = state => {
return {
jobTitles: state.jobTitles
}
}
const mapDispatchToProps = dispatch => ({
fetchJobTitles: () => dispatch(fetchJobTitles())
});
class Home extends Component {
constructor(props) {
super(props);
this.state = {
jobInputValue: '',
addressInputValue: ''
};
}
componentDidMount() {
this.props.fetchJobTitles();
}
printState = () => {
console.log('State is: ' +
JSON.stringify(this.state.jobTitles));
}
render() {
return (
<ImageBackground style={styles.bkgImage} source={require('../assets/homepage_background.jpg')}>
//JSX goes here
<Button
title="CAUTÄ‚"
type="outline"
underlayColor={colors.red}
titleStyle={styles.buttonTitleStyle}
color={colors.red}
style={styles.buttonStyle}
onPress={this.printState}
/>
</ImageBackground>
);
}
}
//some styles
export default connect(mapStateToProps, mapDispatchToProps)(Home);
This is my reducer (jobTitles.js):
import * as ActionTypes from '../ActionTypes';
export const jobTitles = (state = { errMess: null,
jobTitles:[]}, action) => {
switch (action.type) {
case ActionTypes.GET_JOB_TITLES:
return {...state, errMess: null, jobTitles: action.payload};
case ActionTypes.JOB_TITLES_FAILED:
return {...state, errMess: action.payload};
default:
return state;
}
};
And this is my Action Creator:
import * as ActionTypes from './ActionTypes';
import { baseUrl } from '../shared/baseUrl';
export const fetchJobTitles = () => (dispatch) => {
return fetch(baseUrl + 'api/jobs/job_keywords')
.then(response => {
if (response.ok) {
return response;
} else {
var error = new Error('Error ' + response.status + ': ' +
response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(jobTitles => dispatch(addJobTitles(jobTitles)))
.catch(error => dispatch(jobTitlesFailed(error.message)));
};
export const jobTitlesFailed = (errmess) => ({
type: ActionTypes.JOB_TITLES_FAILED,
payload: errmess
});
export const addJobTitles = (jobTitles) => ({
type: ActionTypes.GET_JOB_TITLES,
payload: jobTitles
});
This is how the response from the API looks like:
"jobTitles": Object {
"results": Array [
"Engineer",
"Software",
"Software Architect",
"Software Consultant",
"Solution Architect",
"System Architect"
]
}
I expected the console.log() statement from the print() function in the HomeComponent.js to print the JSON response from the API, but instead it returns "undefined". Any ideas why?
Any help will be greatly appreaciated!
In your code :
this.state = {
jobInputValue: '',
addressInputValue: ''
};
What you try to print :
this.state.jobTitles
Of course it's undefined ! Either log this.props.jobTitles or set state jobTitles to print what you want.
You should use this.props.jobTitles
The mapStateToProps puts data from the redux state into the props of the component. this.state only holds the local state of the component. So jobInputValue and addressInputValue in this case. Everything from mapStateToProps and mapDispatchToProps will end up in the props. (As the name of the function indicates)
Hello I'm trying to learn react native from Stephen Grider's react-native course.I'm stuck to load data from my webservice and list them by using redux and lodash .I can successfully get data and can see it in render (console.log) ,and but my props always is null in componentDidUpdate or componentWillMount .
Any help is appreciated,thanks.
Reducer is like this;
import { TEST_FETCH, TEST_LOAD } from "../actions/types";
const INITIAL_STATE = { dataSource: [] };
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case TEST_FETCH:
return { ...state, loading: false, dataSource: action.payload.data };
case TEST_LOAD:
return { ...state, loading: true, error: "" };
default:
return state;
}
};
and action is ;
import { TEST_LOAD, TEST_FETCH } from "./types";
export const getdata = ( ) => {
return dispatch => {
dispatch({ type: TEST_LOAD });
fetch("http://myserver/getdata", {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
}
})
.then(response => {
return response.json();
})
.then(responseData => {
return responseData;
})
.then(data => {
// return data;
dispatch({ type: TEST_FETCH, payload: data });
});
};
};
and page is ;
import _ from 'lodash';
import React, { Component } from "react";
import { View, Text, ListView } from "react-native";
import { connect } from "react-redux";
import { getdata } from "../actions";
class testList extends Component {
componentWillMount() {
this.props.getdata();
}
componentDidMount() {
console.log(this.props.myarray ); // myarray is empty
this.createDataSource(this.props.myarray);
}
componentDidUpdate() {
console.log(this.props.myarray ); // I tried this but still myarray is empty
this.createDataSource(this.props.myarray);
}
createDataSource({ dtsource }) {
// sure dtsource is null too
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2});
this.dataSource = ds.cloneWithRows(dtsource);
}
render() {
console.log(this.props.myarray); // if I write here,I can see my json's output
return <View>
<Text>Employee List</Text>
<ListView dataSource={this.props.myarray} renderRow={rowData => <Text
>
{rowData}
</Text>} />
</View>;
}
}
const mapStateToProps = state => {
const myarray= _.map(state.testForm.dataSource, function(v) {
return { ...v };
});
return { myarray};
};
export default connect(mapStateToProps , { getdata })(testList);
I would recommend you to use a FlatList since ListView is deprecated and has bad performance. In the meantime, you can use the code snippet below to pass the correct object to dataSource. You might need to add some null checks depending on the state of the data you pass to myarray.
import _ from 'lodash';
import React, { Component } from "react";
import { View, Text, ListView } from "react-native";
import { connect } from "react-redux";
import { getdata } from "../actions";
class testList extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2}).cloneWithRows(props.myarray ? props.myarray : [])
};
}
componentDidMount() {
this.props.getdata();
}
componentDidUpdate() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(props.myarray ? props.myarray : [])
});
}
render() {
return <View>
<Text>Employee List</Text>
<ListView dataSource={this.props.myarray} renderRow={rowData => <Text
>
{rowData}
</Text>} />
</View>;
}
}
const mapStateToProps = state => {
const myarray = _.map(state.testForm.dataSource, function(v) {
return { ...v };
});
return { myarray};
};
export default connect(mapStateToProps , { getdata })(testList);
My code is as below:
const LOAD = 'redux-example/LOAD';
const LOAD_SUCCESS = 'redux-example/LOAD_SUCCESS';
const LOAD_FAIL = 'redux-example/LOAD_FAIL';
import axios from 'axios';
const initialState = {
loaded: false
};
export default function info(state = initialState, action = {}) {
switch (action.type) {
case LOAD:
return {
...state,
loading: true
};
case LOAD_SUCCESS:
return {
...state,
loading: false,
loaded: true,
data: action.result
};
case LOAD_FAIL:
return {
...state,
loading: false,
loaded: false,
error: action.error
};
default:
return state;
}
}
export function load() {
return {
types: [LOAD, LOAD_SUCCESS, LOAD_FAIL],
promise: (client) => client.get('http://example.com/getdata')
};
}
I am using https://github.com/erikras/react-redux-universal-hot-example example as starter kit. I want to make promise based api call to example.com/api.But I am not able to do it with async call.I get error in middleware that can not read promise of undefined.My middleware code is as below.
export default function clientMiddleware(client) {
return ({dispatch, getState}) => {
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
const { promise, types, ...rest } = action; // eslint-disable-line no-redeclare
if (!promise) {
return next(action);
}
const [REQUEST,SUCCESS,FAILURE] = types;
next({...rest, type: REQUEST});
const actionPromise = promise(client);
actionPromise.then(
(result) => next({...rest, result, type: SUCCESS}),
(error) => next({...rest, error, type: FAILURE})
).catch((error)=> {
console.error('MIDDLEWARE ERROR:', error);
next({...rest, error, type: FAILURE});
});
return actionPromise;
};
};
}
MY component code is as below
import React, {Component, PropTypes} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {load} from 'redux/modules/info';
#connect(state => ({info: state.info.data}),dispatch => bindActionCreators({load}, dispatch))
export default class InfoBar extends Component {
static propTypes = {
info: PropTypes.object,
load: PropTypes.func.isRequired
}
render() {
const {info, load} = this.props; // eslint-disable-line no-shadow
const styles = require('./InfoBar.scss');
return (
<div className={styles.infoBar + ' well'}>
<div className="container">
This is an info bar
{' '}
<strong>{info ? info.message : 'no info!'}</strong>
<span className={styles.time}>{info && new Date(info.time).toString()}</span>
<button className="btn btn-primary" onClick={load}>Reload from server</button>
</div>
</div>
);
}
}
this is only the reducer. You would want to create an action. An action triggers the event that will make the redux store update its state. The basic flow of redux for something like this goes like:
Mount a component
Dispatch an action
Dispatched action in turn will update the store via the Provider component
this will trigger a re-render of the component.
The following is a basic example using fetch.
import fetch from 'isomorphic-fetch';
export function getUsers() {
return dispatch => {
dispatch({ type: REQUEST_USERS });
return fetch('/api/v1/users')
.then(res => res.json())
.then(users => {
dispatch({ type: RECEIVE_USERS, payload: users });
return users;
});
}
}
Then you can call this in your component level item.
import { getUsers } from 'actions/users';
class UserList extends Component {
componentDidMount() { dispatch(getUsers()) }
}
Check out the example