I have been trying to use redux and redux-thunk to help get a json file from a api and have been getting a warning stating that action must be a plain object. I am really confused as to where the issue is in the code. i have tried following many other stackoverflow posts and a couple of guides online and have not really got a good grasp of where I am going wrong. I understand that this is a problem with how I am referencing async and dispatch but do not know how to fix it.
This is the function that causes the warning to appear in the simulator
export const fetchData = url => {
console.log("Should enter async dispatch");
return async (dispatch) => {
dispatch(fetchingRequest());
fetch("https://randomuser.me/api/?results=10")
.then(response => {
if (response.ok) {
let json = response.json();
dispatch(fetchingSuccess(json));
console.log("JSON", json);
}
})
.catch(error => {
dispatch(fetchingFailure(error));
console.log("Error", error);
});
};
};
Here is the output in the console
Possible Unhandled Promise Rejection (id: 0):
Error: Actions must be plain objects. Use custom middleware for async actions.
Error: Actions must be plain objects. Use custom middleware for async actions.
Edit: including setup of middleware
I have the middleware setup in the index.js file of my app
index.js
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import { Provider } from "react-redux";
import React, { Components } from "react";
import { createStore, applyMiddleware } from "redux";
import appReducer from "./src/data/redux/reducers/appReducer";
import thunk from "redux-thunk";
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(appReducer);
console.log("Store", store.getState());
const AppContainer = () => (
<Provider store = {store}>
<App />
</Provider>
);
AppRegistry.registerComponent(appName, () => AppContainer);
I learned this implementation of store from a Youtube Tutorial.
Edit 2: Adding in the fetchData call
I call fetchData in a _onPress function like this
_onPress = () => {
const {fetchData} = this.props;
let url = "https://randomuser.me/api/?results=10";
fetchData(url);
console.log("should have fetched");
};
this is how my app has been connected to redux
const mapStateToProps = state => {
return { response: state };
};
const mapStateToDispatch = dispatch => ({
fetchData: url => dispatch(fetchData(url)),
});
export default connect(
mapStateToProps,
mapStateToDispatch
)(SearchScreen);
these are the action in my app
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
I was able to figure out the problem thanks to working through the steps in the comments thanks to Michael Cheng. I ended up finding that the problem was that i had actions with plain objects but they were not returning anything.
The original actions were
export const fetchingRequest = () => {
{
type: FETCHING_REQUEST;
}
};
export const fetchingSuccess = json => {
{
type: FETCHING_SUCCESS;
payload: json;
}
};
export const fetchingFailure = error => {
{
type: FETCHING_FAILURE;
payload: error;
}
};
to this
export const fetchingRequest = () => {
return {
type: FETCHING_REQUEST
}
};
export const fetchingSuccess = json => {
return {
type: FETCHING_SUCCESS,
payload: json
}
};
export const fetchingFailure = error => {
return {
type: FETCHING_FAILURE,
payload: error
};
};
with including the return for each action
Related
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>)
}
So I dispatch my redux-saga action once from my react-native app and it makes two API calls. I'm trying to figure out why this is, and how to only have it send one.
App.js
const initFetch = async () => {
const userToken = await AsyncStorage.getItem("userToken");
dispatch(fetchLiked({ page: 0, search: "", userToken }));
};
useEffect(() => {
initFetch();
}, []);
configureStore.js
import { createStore, combineReducers, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import AsyncStorage from "#react-native-community/async-storage";
import likedReducer from "./reducers/liked";
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas/rootSaga";
const rootReducer = combineReducers({
liked: likedReducer,
});
const persistConfig = {
key: "primary",
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
const sagaMiddleware = createSagaMiddleware();
export default () => {
let store = createStore(persistedReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
let persistor = persistStore(store);
return { store, persistor };
};
rootSaga.js
import { take, call, all } from "redux-saga/effects";
import { watchFetchLikedSaga } from "./likedSaga";
export default function* rootSaga() {
yield all([watchFetchLikedSaga()]);
}
likedSaga.js
import { takeLatest, call, put } from "redux-saga/effects";
import Server from "../../utils/Server";
import { fetchLikedSuccess } from "./../actions/liked";
import { types } from "../actions/types";
function* asyncFetchLiked(data) {
console.log("sending async fetch");
const { page, search, userToken } = data.payload;
try {
const response = yield call(() =>
Server.get("/api/titles/getliked", {
headers: { "auth-token": userToken },
params: { page: page, search: search },
})
);
yield put(fetchLikedSuccess(response.data));
} catch (e) {
console.log(e);
}
}
export function* watchFetchLikedSaga() {
yield takeLatest(types.SEND_REQUEST, asyncFetchLiked);
}
export const fetchLiked = (data) => {
return {
type: types.SEND_REQUEST,
payload: data,
};
};
actions/liked.js
export const fetchLiked = (data) => {
console.log("fetchLiked");
return {
type: types.SEND_REQUEST,
payload: data,
};
};
export const fetchLikedSuccess = (data) => {
console.log("fetchLikedSuccess");
return {
type: types.SEND_REQUEST_SUCCESS,
payload: data,
};
};
export const fetchLikedFailure = (error) => {
return {
type: types.SEND_REQUEST_FAILURE,
payload: {},
error: error,
};
};
My console.log output looks like this. You can see the action is only being dispatched once, but it is sending two async requests and calling the reducer success action twice.
fetchLiked
sending async fetch
sending async fetch
fetchLikedSuccess
fetchLikedSuccess
Can successfully register the user using my action creator but it returns undefined. I think it's the way how am returning my dispatch
axiosInstance
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
// import base url
import {API_URL} from '../constants';
const instance = axios.create({
baseURL: API_URL,
timeout: 2000,
});
instance.interceptors.request.use(
async(config) => {
const token = await AsyncStorage.getItem('token');
if(token) {
config.headers.Autherization = `${token}`;
}
return config;
},`enter code here`
(err) => {
return Promise.reject(err);
}
)
export default instance;
SignUP Action.
import axiosInstance from '../../api/axiosInstance';
import {REGISTER_USER_SUCCESS, REGISTER_USER_FAIL} from '../actionTypes/index';
const registerSuccess = (payload) => {
return{
type: REGISTER_USER_SUCCESS,
data: payload
}
};
const registerError = (payload) => {
return {
type: REGISTER_USER_FAIL,
data: payload
}
};
export const SignUp = (registerData) => async dispatch => {
axiosInstance.post('/users/register', registerData)
.then((response)=> {
dispatch(registerSuccess(response.data));
})
.catch((error) => {
dispatch(registerError(error));
});
}
Here is how am using my action creator .. the result is undefined . I want to have a check some that I can redirect the screen to another login screen or home screen
SignUP Submit function
dispatch(registerAction.SignUp(values))
.then( (result) => {
console.log('klhadsghaj',result.status);
if(result.success) {
try {
navData.navigation.navigate("Login");
}catch (err) {
console.log(err)
}
} else {
Alert.alert('Registration failed. Try Again')
}
})
.catch(err => console.log(err))
I have been trying to implement react server-side-rendering using next, and i am using the with-redux-observable-app example, the example works fine, but i would like to improve the project a little bit by doing
redux modular pattern
fractal project structure
If possible, i would like to implement stateless components
Because #2, i can no longer use react state lifecycle, to solve that i usually took advantage of react router onEnter props, but this suggest that i should use componentWillMount, which doesn't meet my #2 condition
I have put the project on github, with this particular problem committed on this branch
Here's the summary of what i did so far
to achieve #1
// ./redux/index.js
...
import rootEpics from './root/epics'
import rootReducers from './root/reducers'
export default function initStore(initialState) {
const epicMiddleware = createEpicMiddleware(rootEpics)
const logger = createLogger({ collapsed: true })
const middlewares = applyMiddleware(thunkMiddleware, epicMiddleware, logger)
return createStore(rootReducers, initialState, middlewares)
}
// ./redux/root/epics.js
import { fetchCharacterEpic, startFetchingCharactersEpic } from '../ducks/Character/epics'
const rootEpics = combineEpics(
fetchCharacterEpic,
startFetchingCharactersEpic,
)
export default rootEpics
// ./redux/root/reducers.js
import { combineReducers } from 'redux'
import Character from '../ducks/Character'
const rootReducers = combineReducers({
Character,
})
export default rootReducers
// ./redux/ducks/Character/index.js
import * as types from './types'
const INITIAL_STATE = {
data: {},
error: {},
id: 1,
}
const Character = (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case types.FETCH_CHARACTER_SUCCESS:
return {
...state,
data: payload.response,
id: state.id + 1,
}
case types.FETCH_CHARACTER_FAILURE:
return {
...state,
error: payload.error,
}
default:
return state
}
}
export default Character
// ./redux/ducks/Character/types.js
export const FETCH_CHARACTER = 'FETCH_CHARACTER'
export const FETCH_CHARACTER_SUCCESS = 'FETCH_CHARACTER_SUCCESS'
export const FETCH_CHARACTER_FAILURE = 'FETCH_CHARACTER_FAILURE'
export const START_FETCHING_CHARACTERS = 'START_FETCHING_CHARACTERS'
export const STOP_FETCHING_CHARACTERS = 'STOP_FETCHING_CHARACTERS'
// ./redux/ducks/Character/actions.js
import * as types from './types'
export const startFetchingCharacters = () => ({
type: types.START_FETCHING_CHARACTERS,
})
export const stopFetchingCharacters = () => ({
type: types.STOP_FETCHING_CHARACTERS,
})
export const fetchCharacter = id => ({
type: types.FETCH_CHARACTER,
payload: { id },
})
export const fetchCharacterSuccess = response => ({
type: types.FETCH_CHARACTER_SUCCESS,
payload: { response },
})
export const fetchCharacterFailure = error => ({
type: types.FETCH_CHARACTER_FAILURE,
payload: { error },
})
// ./redux/ducks/Character/epics.js
import 'rxjs'
import { of } from 'rxjs/observable/of'
import { takeUntil, mergeMap } from 'rxjs/operators'
import { ofType } from 'redux-observable'
import ajax from 'universal-rx-request'
import * as actions from './actions'
import * as types from './types'
export const startFetchingCharactersEpic = action$ => action$.pipe(
ofType(types.START_FETCHING_CHARACTERS),
mergeMap(() => action$.pipe(
mergeMap(() => of(actions.fetchCharacter())),
takeUntil(ofType(types.STOP_FETCHING_CHARACTERS)),
)),
)
export const fetchCharacterEpic = (action$, id) => action$.pipe(
ofType(types.FETCH_CHARACTER),
mergeMap(() => ajax({
url: 'http://localhost:8010/call',
method: 'post',
data: {
method: 'get',
path: `people/${id}`,
},
})
.map(response => actions.fetchCharacterSuccess(
response.body,
true,
))
.catch(error => of(actions.fetchCharacterFailure(
error.response.body,
false,
)))),
)
to achieve #2
// ./pages/index/container/index.js
import React from 'react'
import { connect } from 'react-redux'
import { of } from 'rxjs/observable/of'
import rootEpics from '../../../redux/root/epics'
import { fetchCharacter } from '../../../redux/ducks/Character/actions'
import Index from '../component'
const mapStateToProps = state => ({
id: state.Character.id,
})
const mapDispatchToProps = dispatch => ({
async setInitialCharacter(id) {
const epic = of(fetchCharacter({ id }))
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
},
})
export default connect(mapStateToProps, mapDispatchToProps)((props) => {
props.setInitialCharacter(props.id)
return (<Index />)
})
// ./pages/index/component/index.js
import React from 'react'
import Link from 'next/link'
import Helmet from 'react-helmet'
import Info from '../container/info'
const Index = () => (
<div>
<Helmet
title="Ini index | Hello next.js!"
meta={[
{ property: 'og:title', content: 'ini index title' },
{ property: 'og:description', content: 'ini index description' },
]}
/>
<h1>Index Page</h1>
<Info />
<br />
<nav>
{/* eslint-disable jsx-a11y/anchor-is-valid */}
<Link href="/other"><a>Navigate to other</a></Link><br />
<Link href="/about"><a>Navigate to about</a></Link>
{/* eslint-enable jsx-a11y/anchor-is-valid */}
</nav>
</div>
)
export default Index
// ./pages/index/container/info.js
import { connect } from 'react-redux'
import Info from '../../../components/Info'
const mapStateToProps = state => ({
data: state.Character.data,
error: state.Character.error,
})
export default connect(mapStateToProps)(Info)
with those above, the fetch works fine, but...
i don't want the fetch to keep running, i want it to run just once onEnter.
As an attempt to achieve that, i wrote an epic called startFetchingCharactersEpic(), and an action called startFetchingCharacters(), and lastly add mergeMap(() => of(actions.stopFetchingCharacters())), at the end of fetchCharacterEpic() pipe arguments, with the following scenario in mind
dispatch actions.startFetchingCharacters(), in container
that will trigger startFetchingCharactersEpic()
that will do so until types.STOP_FETCHING_CHARACTERS
that will dispatch actions.fetchCharacter()
that will trigger fetchCharacterEpic()
that will dispatch actions.stopFetchingCharacters()
that will trigger #3
setInitialCharacter
// ./pages/index/container/index.js
const mapDispatchToProps = dispatch => ({
async setInitialCharacter(id) {
const epic = of(startFetchingCharacters())
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
},
})
but by doing that i got TypeError: Cannot read property 'type' of undefined, the console doesn't give me enough information than saying that the error is coming from setInitialCharacter
Tried googling the issue, but found nothing related to my problem
UPDATE
I manage to make it work again based on #jayphelps' answer below, which brought me back to some of my original problems, which are
How to fully use stateless component without utilizing react state lifecycle, especially replacing onEnter
How to just call the fetchCharacterEpic just once on page load
but i guess these 2 worth another post, as i realized i am asking too many question on this post
Totally guessing here, but it's possible that the error is coming from the fact that you're dispatching a Promise here:
const resultAction = await rootEpics(
epic,
id,
).toPromise()
dispatch(resultAction)
Your question doesn't mention, but that means you must have middleware that intercepts that promise since redux (and redux-observable) only expected POJOs { type: string }.
It's also possible that the promise isn't resolving to anything other than undefined, in which case the ofType operator in your epics will choke because it only works on those POJO actions { type: string }.
Sorry I can't help more specifically, it's tough to follow what the intent is.
e.g. this await rootEpics(epic, id) seems odd as rootEpics is the root epic and expects the arguments to be (action$, store) and UI components should not directly call epics?
I am trying to fetch some data from an api using Redux. My code looks like this:
Action:
// Import libraries
import axios from 'axios';
// Import types
import {
GET_ALL_PICKS
} from './types';
export const getAllPicks = ({ token }) => {
const getPicks = (dispatch) => {
axios({
method: 'get',
url: 'http://myapi/',
headers: {
Authorization: `Bearer ${token}`
}
})
.then((response) => {
console.log(response.data); // First log here returns data just fine
dispatch({
type: GET_ALL_PICKS,
payload: response.data
});
})
.catch((error) => {
console.log(error);
});
};
return getPicks;
};
Reducer:
// Import types
import {
GET_ALL_PICKS
} from '../actions/types';
// Set Initial State
const INITIAL_STATE = {
allPicks: {},
loading: false,
error: ''
};
// Make pick reducers
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_ALL_PICKS:
return { ...state, allPicks: action.payload }; // Logging action.payload here returns data just fine
default:
return state;
}
};
Component:
// Import Libraries
import React, { Component } from 'react';
import { Text } from 'react-native';
import { connect } from 'react-redux';
import {
getAllPicks
} from '../actions/picks';
// Make Component
class HomeScreen extends Component {
// Fetch Data
componentWillMount() {
const { token } = this.props;
this.props.getAllPicks({ token });
}
// Test response
componentDidMount() {
console.log(this.props.allPicks); // This log returns empty object, why?!
}
render() {
return (
<Text>Test</Text>
);
}
}
const mapStateToProps = ({ auth, picks }) => {
const { token } = auth;
const { allPicks } = picks;
return {
token,
allPicks
};
};
export default connect(mapStateToProps, { getAllPicks })(HomeScreen);
When I run the app I see the data in the action console.log and if I run a console.log(action.payload) in the reducer I see the data just fine but in component I see an empty array which suggests I'm not hooking up the data in my reducer correctly? Here's a screen shot of the logs:
I have also tried this in my reducer after some Googling:
return Object.assign({}, state, {
allPicks: action.payload
});
but again I got the same result. Can anyone explain to me what I am doing wrong?
You are confusing the component lifecycle and the API lifecycle.
In practice, what's happening is:
componentWillMount
getAllPicks
componentDidMount (at which point, the API didn't return, the picks are empty)
[... wait for the API to return]
then the API returns with the data, but too late
What you need to do then is check for your "picks" state in the render() function, which will be updated each time your state changes (which happens when the API returns), thanks to the connect() function.
You can also check that the picks are updated properly using componentWillUpdate, not componentDidMount which again has nothing to do with the props being updated.