redux/redux-observable error: Actions must be plain objects - reactjs

I'm using redux with redux-observable and get this strange error:
Actions must be plain objects. Use custom middleware for async >actions.
/* Component.jsx */
import React from "react"
import { serialNumberCheck } from '../actions'
const Component = props => {
...
<button
onClick={() => props.serialNumberCheck('123456789123456')}
>
Check
</button>
...
}
const mapDispatchToProps = dispatch =>
bindActionCreators({serialNumberCheck}, dispatch)
export default compose(
reduxForm({
...
}),
withStyles(styles),
connect(mapDispatchToProps)
)(Component)
/* actions.js */
export const SERIAL_NUMBER_CHECK = 'SERIAL_NUMBER_CHECK'
export const SERIAL_NUMBER_CHECK_SUCCESS = 'SERIAL_NUMBER_CHECK_SUCCESS'
export const serialNumberCheck = (serialNumber) => ({
type: SERIAL_NUMBER_CHECK,
payload: serialNumber
})
export const serialNumberCheckSuccess = (data) => ({
type: SERIAL_NUMBER_CHECK,
payload: data
})
/* epics.js */
...
import { serialNumberCheck } from "../actions"
import ... from 'rxjs'
...
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
serialNumberCheckSuccess({success: true});
}
})
}
...
export const rootEpic = combineEpics(
...
serialNumberCheckEpic
);
/* reducer.js */
import {
SERIAL_NUMBER_CHECK_SUCCESS,
} from '../actions'
...
export default function epicReducer(state = initialState, action) {
switch (action.type) {
case SERIAL_NUMBER_CHECK_SUCCESS:
return {
...state,
success: action.payload
}
}
}
/* JSON-SERVER RESPONSE */
[
{
"id": 1,
"sn": "123456789123456"
}
]
Inside component i'am calling function serialNumberCheck() and passing inside sierial number that we need to check.
Inside Epic im passing serial number to json-server that checks if this number exists in my "database". If serial number exists, server response is .json containing some parameters.
So if response isn't empty we need to write success: true inside redux store.
But in the end we get successfull GET request, and then error: Actions must be plain objects. Use custom middleware for async actions., but no changes inside redux-store and nothing from SERIAL_NUMBER_CHECK_SUCCESS action.

Finally, I found the solution. I've just missed the return before calling action inside my epic.
function serialNumberCheckEpic(action$) {
return action$
.ofType(SERIAL_NUMBER_CHECK)
.switchMap((data) => {
return ajax.getJSON(`http://localhost:3004/sn/?sn=${data.payload}`)
.map((data) => data)
})
.map(data => {
if(data.length !== 0) {
+ return serialNumberCheckSuccess({success: true});
}
})
}

Related

Unable to display information after fetching data from useSelector

I'm trying to access data from my API using Redux but when redux tool kit is showing me its an empty array. The api I've populated using postman and the post method seem to work perfectly fine, but attempting to use the get method to access that data it shows an empty array. My DB has the data though. My Data is an array of Object i.e. [ {...} , {...} , {...} ]
API
import axios from "axios";
const url = "http://localhost:5000/info"
export const fetchInfo = () => axios.get(url);
export const createInfo = (newInfo) => axios.post(url, newInfo);
ACTIONS
import * as api from "../api/index.js";
//constants
import { FETCH_ALL, CREATE } from "../constants/actiontypes";
export const getInfo = () => async (dispatch) => {
try {
const { data } = await api.fetchInfo();
console.log(data);
dispatch({ type: FETCH_ALL, payload: data });
} catch (error) {
console.log(error);
}
};
export const createInfo = (info) => async (dispatch) => {
try {
const { data } = await api.createInfo(info);
dispatch({ type: CREATE, payload: data });
} catch (error) {
console.log(error);
}
};
REDUCER
import { FETCH_ALL, CREATE } from "../constants/actiontypes";
export default (infos = [], action) => {
switch (action.type) {
case FETCH_ALL:
return action.payload;
case CREATE:
return [...infos, action.payload];
default:
return infos;
}
};
COMBINE REDUCERS
import {combineReducers} from "redux";
import infos from "./info"
export default combineReducers({infos})
Component I'm trying to to display it in
import React from "react";
//redux
import { useSelector } from "react-redux";
//component
import MovieDetail from "./MovieDetail"
const MovieTitles = () => {
const infos = useSelector((state) => state.infos);
console.log(infos) // shows me empty array
return (
<div>
{infos.map((i) => (
<MovieDetail info={i} />
))}
</div>
);
};
export default MovieTitles;
Is there something else I'm missing which allows to me to access the data?
thanks

React-Redux action is not dispatched properly?

I have following Action:
import axios from 'axios';
export function getAPIData(id)
{
return (dispatch) =>
{
axios.get('http://localhost:5000/api/' + id)
.then(res =>
{
dispatch(
{
type: 'DONE',
payload: res.data
});
});
}
}
Then in my Component I`m dispatching the action:
componentDidMount()
{
this.props.dispatch(getAPIData());
}
And then:
function mapStateToProps(state)
{
console.log(state);
return {
data: state.result
};
}
export default connect(mapStateToProps)(Rows);
In console, when I try to find the payload, it says what is bellow.
function()
arguments: TypeError: 'arguments', 'callee', and 'caller' cannot be
accessed in this context.
caller: TypeError: 'arguments', 'callee', and 'caller' cannot be
accessed in this context.
length: 1
name: ""
Where is problem? Thanks a lot.
to dispatch an action you need to provide mapDispatchToProps .
First import your action
import { getAPIData } from "../store/actions/getAPIData";
then build mapDispatchToProps
const mapDispatchToProps = (dispatch) => {
return {
getAPIData: (props = null) => {
dispatch(getAPIData(props));
},
};
};
add this alongside mapStateToProps
export default connect(mapStateToProps , mapDispatchToProps)(Rows);
now you can call the action like this
componentDidMount()
{
this.props.getAPIData();
}

Fetching flag does not update when using a single fetching reducer

I am in the process of cleaning up my fetching flags. By following the best practice, I am using a separate reducer to store all isFetching flags. In doing so I do not have to maintain multiple isFetchingFlags in my reducers.
Although I followed the explanation exactly, my isFetching flag does not jump from IsFetching: true (data currently being fetched) to IsFetching: false (data successfully fetched) in this new configuration. My fetching flag remains at IsFetching: false all the time. I have checked my code several times, but I cannot find my error.
Story Action:
// GET STORY
export const getStory = () => (dispatch, getState) => {
dispatch ({type: GET_STORY_REQUEST});
dispatch(showLoading());
axios.get( apiBase + "/story/retrieve/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_STORY_SUCCESS,
payload: res.data
});
dispatch(hideLoading());
})
.catch(err =>{
dispatch({
payload: returnErrors(err.response.data, err.response.status),
type: GET_STORY_FAILURE });
dispatch(hideLoading());
})
};
Loading Reducer
import {GET_STORY_SUCCESS,GET_STORY_REQUEST, GET_STORY_FAILURE} from "../actions/types.js";
const loadingReducer = (state = {}, action) => {
const { type } = action;
const matches = /(.*)_(REQUEST|SUCCESS|FAILURE)/.exec(type);
// not a *_REQUEST / *_SUCCESS / *_FAILURE actions, so we ignore them
if (!matches) return state;
const [, requestName, requestState] = matches;
return {
...state,
// Store whether a request is happening at the moment or not
// e.g. will be true when receiving GET_STORY_REQUEST
// and false when receiving GET_STORY_SUCCESS / GET_STORY_FAILURE
[requestName]: requestState === 'REQUEST',
};
Loading Selector
import _ from 'lodash';
export const createLoadingSelector = (actions) => (state) => {
// returns true only when all actions is not loading
return _(actions)
.some((action) => _.get(state, `api.loading.${action}`));
};
Story Component
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { getStory} from '../../actions/story';
import { createLoadingSelector } from '../common/loading';
export class Story extends Component {
static propTypes = {
story: PropTypes.array.isRequired,
getStory: PropTypes.func.isRequired,
};
componentDidMount() {
this.props.getStory();
}
render() {
const { story } = this.props.story;
return (
<Fragment>
<h2>Stories</h2>
</Fragment>
);
}
}
const loadingSelector = createLoadingSelector(['GET_STORY']);
function mapStateToProps(state, ownProps) {
const story = state.story
const isFetching = loadingSelector(state)
console.log (isFetching)
console.log (story)
return { story, isFetching}
};
export default connect(
mapStateToProps,
{ getStory}
)(Story);
I'm happy for every clarification.
Are you using a middleware?
Remember that redux does not support asynchronous actions by default.
If not try to configure the redux-thunk middleware.
https://github.com/reduxjs/redux-thunk

Redux Thunk action with axios returning multiple values

I have a React app that uses redux-thunk and axios to fetch an API. The action fires successfully, but returns multiple payloads which means it is firing multiple times.
How can I make it fire only one time?
Code
Actions
import Axios from "axios";
import { fetchEnglishLeagueTable } from "./ActionTypes";
export function fetchEnglishTable() {
var url = "https://api.football-data.org/v2/competitions/PL/matches";
var token = "52c8d88969d84ac9b17edb956eea33af";
var obj = {
headers: { "X-Auth-Token": token }
};
return dispatch => {
return Axios.get(url, obj)
.then(res => {
dispatch({
type: fetchEnglishLeagueTable,
payload: res.data
});
})
.catch(e => {
console.log("Cant fetch ", e);
});
};
}
Reducers
import { fetchEnglishLeagueTable } from "../actions/ActionTypes";
const initialState = {
EnglishTable: {}
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case fetchEnglishLeagueTable:
return {
...state,
EnglishTable: action.payload
};
default:
return state;
}
};
export default rootReducer;
Page
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, [props.leagueTable]);
console.log(props.leagueTable);
return <p>ihi</p>;
};
const mapStateToProps = state => ({
leagueTable: state.EnglishTable
});
const mapDispatchToProps = dispatch => {
return { getLeagueTable: () => dispatch(fetchEnglishTable()) };
};
export default connect(mapStateToProps, mapDispatchToProps)(League);
Store
import rootReducer from "./Reducer";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
Here is what it returns
Just remove leagueTable from useEffect's dependency array, so it'll fetch them only once component is mounted. Because now you have a loop:
Get leagues -> leagueTable updates -> useEffect sees that leagueTable changed in dependency array and calls to get leagues again and we've got a loop.
const League = props => {
useEffect(() => {
props.getLeagueTable();
}, []); // <~ no props.leagueTable here
console.log(props.leagueTable);
return <p>ihi</p>;
};
Hope it helps :)

TypeError: this.setState is not a function, inside Redux Actions file

I'm attempting to reconfigure a PixaBay clone application to Redux. The application retrieves photos as the user types a search text. However, it breaks as soon as I type inside the input.
From what I've researched, you can only call setState in a class so I gave fetchPhotos an arrow function, but that didn't work. I also tried to .bind(this), but that gave me a parsing error. Could someone kindly tell me what I'm doing wrong? Here are the following errors, along with my code.
ERRORS
TypeError: this.setState is not a function
fetchPhotos
src/actions/actions.js:10
7 |
8 | export function fetchPhotos(e) {
9 | const url = `${ROOT_URL}/?key=${API_KEY}&q=${searchText}&image_type=photo`;
> 10 | const request = this.setState({searchText: e.target.value}, () => {
11 | axios.get(url)
12 | .then(response => {
13 | this.setState({images: response.data.hits});
fetchPhotos
node_modules/redux/es/redux.js:475
Search._this.FetchPhotosHandler [as onChange]
src/components/search/Search.js:11
8 | class Search extends Component {
9 |
10 | FetchPhotosHandler = (e) => {
> 11 | this.props.fetchPhotos(e);
12 | }
13 |
14 | render() {
SEARCH CONTAINER
import React, { Component } from 'react';
import { fetchPhotos } from '../../actions/actions';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import TextField from 'material-ui/TextField';
import ImageResults from '../imageResults/ImageResults';
class Search extends Component {
state = {
searchText: '',
images: []
}
FetchPhotosHandler = (e) => {
this.props.fetchPhotos(e);
}
render() {
return (
<div>
<TextField
name="searchText"
value={this.props.searchText}
onChange={this.FetchPhotosHandler}
floatingLabelText="Search for photos"
fullWidth={true} />
<br />
<ImageResults images={this.props.images} />
</div>
);
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPhotos, dispatch});
}
export default connect(null, mapDispatchToProps)(Search);
ACTION
import axios from 'axios';
export const FETCH_PHOTOS = 'FETCH_PHOTOS';
const ROOT_URL = 'https://pixabay.com/api';
const API_KEY = '10264275-868d83de96a4d0c47db26f9e0';
const searchText = '';
export function fetchPhotos(e) {
const url = `${ROOT_URL}/?key=${API_KEY}&q=${searchText}&image_type=photo`;
const request = this.setState({searchText: e.target.value}, () => {
axios.get(url)
.then(response => {
this.setState({images: response.data.hits});
})
.catch(error => {
console.log(error)
});
});
return {
type: FETCH_PHOTOS,
payload: request
};
}
REDUCER
import { FETCH_PHOTOS } from '../actions/actions';
const initialState = {
searchText: '',
images: []
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case FETCH_PHOTOS:
return {
...state,
images: action.data.hits
};
default:
return state;
}
}
export default reducer;
You should avoid attempting to use setState() in your action as it goes against Redux entirely. setState() is meant for managing the local of a React.Component. As you are attempting to utilize Redux, you should instead dispatch actions from your actions creators that update the store via your reducers and finally mapping store values to your component's props via connect(). Below is an example of your code restructured similar to the Async Redux example.
Instead of attempting to call setState() in the action, instead an action is dispatched containing the image payload. The Search component utilizes mapStateToProps (1st argument of connect()) to map store properties such the images array to the component's props. These props are used to render a list of data. This completely eliminates the need to have an images local state property on Search as values are being retrieved from the store as changes happen via actions/reducers. This example uses redux-thunk middleware to handle async actions, but there are plenty of other options out there that you could consider.
store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const middleware = [ thunk ];
const store = createStore(
rootReducer,
applyMiddleware(...middleware)
);
export default store;
actions:
export const FETCH_PHOTOS = 'FETCH_PHOTOS';
export const RECEIVE_PHOTOS = 'RECEIVE_PHOTOS';
// fake data
const testPhotos = [
{ id: 1, src: 'https://placehold.it/250' },
{ id: 2, src: 'https://placehold.it/250' }
];
// fake API call as promise
const getTestPhotos = () => {
return new Promise((resolve) => {
setTimeout(() => {
return resolve(testPhotos);
}, 500);
});
}
const fetchPhotos = (searchText) => ({
type: FETCH_PHOTOS
});
const receivePhotos = (photos) => ({
type: RECEIVE_PHOTOS,
data: {
hits: photos
}
});
export const searchPhotos = (searchText) => dispatch => {
// dispatch an action to handle loading/waiting for API response
dispatch(fetchPhotos(searchText));
// dispatch another action with payload within then()
return getTestPhotos()
.then(photos => dispatch(receivePhotos(photos)));
}
reducer:
import { FETCH_PHOTOS, RECEIVE_PHOTOS } from '../actions';
const initialState = {
loading: false,
searchText: '',
images: []
}
const photos = (state = initialState, action) => {
switch(action.type) {
case FETCH_PHOTOS:
return {
...state,
loading: true
};
case RECEIVE_PHOTOS:
return {
...state,
loading: false,
images: action.data.hits
};
default:
return state;
}
}
export default photos;
Search:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { searchPhotos } from './actions';
class Search extends Component {
constructor(props) {
super(props);
this.state = {
searchText: ''
};
this.fetchPhotosHandler = this.fetchPhotosHandler.bind(this);
}
fetchPhotosHandler(e) {
const { value: searchText } = e.target;
this.setState({ ...this.state, searchText }, () => {
this.props.dispatch(searchPhotos(e));
})
}
render() {
const { loading, images } = this.props;
return (
<div>
<h1>Search</h1>
<div>
<label htmlFor="search">Search:</label>
<input name="search" id="search" type="text" value={this.state.searchText} onChange={this.fetchPhotosHandler} />
</div>
{loading ? (
<div>Loading...</div>
) : (
<ul>
{images.map(image => <li key={image.id}>{image.src}</li>)}
</ul>
)}
</div>
);
}
}
const mapStateToProps = ({ photos: { loading, images } }) => ({ loading, images });
export default connect(mapStateToProps)(Search);
I've created an example to show this functionality in action at a basic level.
Hopefully that helps!
You can bind component class instance to your action and it should work.
FetchPhotosHandler = (e) => {
this.props.fetchPhotos.bind(this)(e);
}
Since you have fetchPhotos exported from different module and in order to do setState there you need to pass this context to fetchPhotos as a param and use the param to do setState. That's how this context will be available
Pass this to fetchPhotos as a param
FetchPhotosHandler = (e) => {
this.props.fetchPhotos(e, this);
}
And here access this and do seState
export function fetchPhotos(e, this) {
const url = `${ROOT_URL}/?key=${API_KEY}&q=${searchText}&image_type=photo`;
const request = this.setState({searchText: e.target.value}, () => {
axios.get(url)
.then(response => {
this.setState({images: response.data.hits});
})
.catch(error => {
console.log(error)
});
});
return {
type: FETCH_PHOTOS,
payload: request
};
}

Resources