I'm fetching data via API and want to show error message if the request fails. I'm dispatching setDuplicatesError that should set state.error property to error message. This is how my reducer looks like:
export function setDuplicatesPending(loading) {
return {
type: 'FETCH_DUPLICATES_PENDING',
loading
}
}
export function setDuplicates(duplicates) {
return {
type: 'FETCH_DUPLICATES_SUCCESS',
duplicates
};
}
export function setDuplicatesError(error) {
return {
type: 'FETCH_DUPLICATES_FAILURE',
error
};
}
export default function duplicatesData(state = {loading: true}, action) {
switch (action.type) {
case 'FETCH_DUPLICATES_FAILURE':
console.log("Failure action is dispatched.", action.error);
return {...state, error: action.error};
case 'FETCH_DUPLICATES_PENDING':
console.log("Loading action is dispatched.")
return {...state, loading: action.loading };
case 'FETCH_DUPLICATES_SUCCESS':
return {...state, rows: action.duplicates, loading: false };
default:
return state;
}
}
action.error has the actual error message which I can see in console:
Failure action is dispatched. TypeError: Failed to execute 'fetch' on 'Window': Request cannot be constructed from a URL...
but when I try displaying it in my component
render() {
return (<div> {JSON.stringify(this.props.state)}</div>)
}
I'm getting the following:
{"duplicatesData":{"error":{}}}
Loading action works fine and shows true/false in state when I display it.
What's causing this behavior?
This is how I create store:
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from 'redux-thunk';
import duplicatesData from "./myReducer";
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export default () => {
const store = createStore(
combineReducers({
duplicatesData
}),
composeEnhancer(applyMiddleware(thunk))
);
store.asyncReducers = {};
return store;
};
and link it it to the component:
function mapStateToProps(state) {
return {
state
};
}
function mapDispatchToProps(dispatch) {
return {
fetchDuplicates: () => dispatch(fetchDuplicates())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DuplicatesTable);
if loading actions works fine you can forget to connect your reducers to the store
an example of the store configuring
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunkMiddleware from 'redux-thunk';
import duplicatesData from './duplicatesData';
const reducers = combineReducers({
duplicatesData
});
const initialState = {};
createStore(
reducers,
initialState,
compose(applyMiddleware(thunkMiddleware))
);
Related
I'm using userReducer to manage user state across the app but while updating the state using reducer the state is updates before reducer is able to update it.
Inspect
here you can see that the previous state variable is updated to new value in payload.
store.js
import { compose, applyMiddleware } from "redux";
import { legacy_createStore as createStore } from "redux";
import { logger } from "redux-logger";
import { rootReducer } from "./rootReducer";
export const store = createStore(rootReducer,applyMiddleware(logger));
rootReducer.js
import { combineReducers } from "redux";
import { postReducer } from "./posts/posts.reducer";
import { userReducer } from "./user/user.reducer";
export const rootReducer = combineReducers({
post: postReducer,
user:userReducer
});
userReducer
import { User_Action_Types } from "./user.types";
const INITIAL_STATE = {
data: {
_id: "",
name: "",
email: "",
password: "",
},
};
export const userReducer = (state = INITIAL_STATE, action) => {
const { type, payload } = action;
console.log({ action, state });
switch (type) {
case User_Action_Types.SetUser: {
state.data = payload;
return state;
}
case User_Action_Types.ResetUser: {
return INITIAL_STATE;
}
default:
return state;
}
};
I tried to change actions then reinstalled the modules but nothing worked.
Please help to fix the issue.
current your mutating state return new state in reducer
case User_Action_Types.SetUser: {
return {
...state,
data: payload
};
}
I am trying to insert items in redux but when i click the button I get the following error :
TypeError: state.reduxCart is not iterable
My Reducer code :
const INITIAL_STATE = {
reduxCart: [],
reduxCartCounter: 0
}
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADDITEM':
return {
...state,
reduxCart: [action.payload , ...state.reduxCart] // ERROR
}
case 'ADDCOUNTER':
return ({
...state,
reduxCartCounter: action.payload
})
default:
return state;
}
}
Here is my Store code :
import rootReducer from './reducer';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);
export { store, persistor };
My Actions Code :
export function AddToReduxCart(item) {
return dispatch => {
dispatch({ type: 'ADDITEM', payload: item })
}
}
How to solve this issue ? Any help would be appreciated.
On your actions file, in your addItem function you can use getState function. then concat the two of the arrays in your action and dispatch them as the payload.
should be something like this:
import store from '../store';
export function addItem(nextItem) {
const currentReduxCart = store.getState().reduxCart.map(item => ({...item}));
const payload = [nextItem , ...currentReduxCart];
return {
type: 'ADDITEM',
payload: payload,
}
}
const INITIAL_STATE = {
reduxCart: [],
reduxCartCounter: 0
}
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case 'ADDITEM':
return {
...state,
reduxCart: action.payload
}
case 'ADDCOUNTER':
return ({
...state,
reduxCartCounter: action.payload
})
default:
return state;
}
}
If you want to add your actions code I will be able to help You to adjust it for your code.
Sory for my English!
I am doing test work on React.js. The task is to make a regular blog. I ran into an unwanted problem. As a rule, componentDidMount makes entries, ready data and is called once.
I invoke the loadPosts action in the CDM to get the data.
The takeEvery effect sees the necessary saga, but does not cause it, but skips it.
When I press a button, everything works fine.
I'm new to React. All i tried is google
repository with project
branch - dev-fullapp
index.js
import store from "./redux/store";
const app = (
<BrowserRouter>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter>
);
store.js
import { createStore, compose, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import apiSaga from "./sagas/index";
import rootReducer from "./reducers/index";
const initialiseSagaMiddleware = createSagaMiddleware();
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
rootReducer,
storeEnhancers(applyMiddleware(initialiseSagaMiddleware))
);
initialiseSagaMiddleware.run(apiSaga);
export default store;
sagas.js
import { put, call, takeEvery } from "redux-saga/effects";
import { fetchGetPosts } from "../apis/index";
import { setErrorPosts, setPosts } from "../actions/actions-posts";
function* workerGetPosts() {
try {
const posts = yield call(fetchGetPosts);
yield put(setPosts(posts));
} catch (err) {
yield put(setErrorPosts(err));
}
}
export default function* watchSaga() {
yield takeEvery(POSTS.LOADING, workerGetPosts);
}
actions.js
import { POSTS } from "../constants";
export const loadPosts = () => {
console.log('action-load')
return {
type: POSTS.LOADING
}
};
export const setPosts = payload => ({
type: POSTS.LOAD_SUCCESS,
payload
});
export const setErrorPosts = error => ({
type: POSTS.ERROR,
error
});
rootReducer.js
import { combineReducers } from "redux";
import postsReducer from "./reducer-posts";
import loadReducer from "./reducer-load";
const rootReducer = combineReducers({
posts: postsReducer,
isLoad: loadReducer
});
export default rootReducer;
reducer-posts.js
import { POSTS } from "../constants";
const postState = {
posts: []
};
function postsReducer(state = postState, action) {
switch (action.type) {
case POSTS.LOAD_SUCCESS:
return {
...state,
posts: [...action.payload]
};
default:
return state;
}
}
export default postsReducer;
reducer-load.js
import { POSTS } from "../constants";
import { combineReducers } from "redux";
const loadReducerPosts = (state = false, action) => {
switch (action.type) {
case POSTS.LOADING: return false;
case POSTS.LOAD_SUCCESS: return true;
case POSTS.ERROR: return false;
default: return state;
}
};
const loadReducer = combineReducers({
isLoadPost: loadReducerPosts,
});
export default loadReducer;
news.jsx
class News extends Component {
componentDidMount() {
loadPosts();
}
render() {
CONST { loadPosts }this.props
return (
<main>
// code
<button onClick={loadPosts}>Test Button</button>
</main>
);
}
}
const mapStateToProps = (
{ posts: { loading, posts, success } }
) => ({
posts,
loading,
success
});
export default connect(
mapStateToProps,
{ loadPosts }
)(News);
loadPosts method is available as props to the React component in current case. Unlike in componentDidMount, on button click you are calling the function from props. You have to use this.props.loadPosts() on both places
I am new to redux and it might be some silly error. I am trying to make an Api call in Action and pass the data to the Reducer. I can see the data passed correctly in the reducer with action.data. I think the problem is in mapStateToProps in the component therefore I am not able to pass the state and render the component. Please find below action - reducers - store.js - home.js
ACTION.JS
export const DATA_AVAILABLE = 'DATA_AVAILABLE';
export function getData(){
return (dispatch) => {
//Make API Call
fetch("MY API URL").then((response) => {
return response.json();
}).then((data) => {
var data = data.articles;
console.log(data)
dispatch({type: DATA_AVAILABLE, data:data});
})
};
}
this is Reducers.JS
import { combineReducers } from 'redux';
import { DATA_AVAILABLE } from "../actions/" //Import the actions types constant we defined in our actions
let dataState = {
data: [],
loading:true
};
const dataReducer = (state = dataState, action) => {
switch (action.type) {
case DATA_AVAILABLE:
state = Object.assign({}, state, {
data: [
...action.data //update current state data reference
],
loading: false
});
console.log(action.data);
return state;
default:
return state;
}
};
// Combine all the reducers
const rootReducer = combineReducers({
dataReducer
// ,[ANOTHER REDUCER], [ANOTHER REDUCER] ....
})
export default rootReducer;
this is Store.js with Redux-thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from '../app/reducers/index'; //Import the reducer
// Connect our store to the reducers
export default createStore(reducers, applyMiddleware(thunk));
and finally home.js component when I need to pass the new state and render it
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
FlatList,
View,
Text,
ActivityIndicator
} from 'react-native';
import {bindActionCreators} from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions'; //Import your actions
class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount() {
this.props.getData(); //call our action
}
render() {
if (this.props.loading) {
return (
<View style={styles.activityIndicatorContainer}>
<ActivityIndicator animating={true}/>
</View>
);
} else {
console.log(this.state)
return (
<View style={styles.row}>
<Text style={styles.title}>
{this.props.data}
fomrmo
</Text>
<Text style={styles.description}>
</Text>
</View>
);
}
}
};
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.dataReducer.loading,
data: state.dataReducer.data
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/home.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Assuming that:
case DATA_AVAILABLE:
console.log(action.data.length)
will console.log something more than 0
change your reducer action:
const dataReducer = (state = dataState, action) => {
switch (action.type) {
case DATA_AVAILABLE:
return {
...state,
data: action.data,
loading: false
});
default:
return state;
}
};
To address:
Objects are not valid as a React child(found objects with Keys
{source, author, title, description, url })
that's because you try to render Object:
{this.props.data}
but if you do:
{
this.props.data.map((el, i) =>
<p key={i}>Element nr {i}</p>
)
}
It should work.
I'm a bit new to using redux and react. I'm trying to make a simple API call with redux and having it render in react. I can see the API call working as it's in the payload in redux dev tools, but I can't seem to get it to update the state possibly in the `connect?.
actions/index
import FilmAPI from '../api/api';
export const FETCH_FILMS = 'FETCH_FILMS';
export const RECEIVE_FILMS = 'RECEIVE_FILMS';
export const receiveFilms = (films) => {
return {
type: RECEIVE_FILMS,
films
};
}
export const fetchFilmsRequest = () => {
return dispatch => {
return axios.get('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => {
dispatch(receiveFilms(response.data))
})
}
}
export default fetchFilmsRequest;
reducers/FilmReducer
import RECEIVE_FILMS from '../actions/index';
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return [...state, action.films];
default:
return state;
}
}
reducers/index
import { combineReducers } from 'redux';
import { films } from './FilmsReducer';
export default combineReducers({
films,
});
containers/FilmListContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchFilmsRequest } from '../actions';
import FilmList from '../components/FilmList'
class FilmListContainer extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchFilmsRequest();
}
render() {
return (
<div>
<FilmList films={this.props.films}/>
</div>
);
}
}
const mapStateToProps = state => ({
films: state.films
})
export default connect(mapStateToProps, {fetchFilmsRequest: fetchFilmsRequest})(FilmListContainer);
configureStore.js
import { createStore, compose, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// options like actionSanitizer, stateSanitizer
}) : compose;
const enhancer = composeEnhancers(
applyMiddleware(thunk)
);
return createStore(
rootReducer,
initialState,
enhancer
);
}
As mentioned, Redux DevTools show the films in the payload, but films still remain 0 in its state. Could anyone please point me in the right direction?
You can get updated state by subscribing store and use store.getState()
Steps:
Write subscribe function in constructor of component class.
Set state of class by store.getState().
import store from '../js/store/index';
class MyClass extends Component {
constructor(props, context) {
super(props, context);
this.state = {
classVariable: ''
}
store.subscribe(() => {
this.setState({
classVariable: store.getState().MyStoreState.storeVariable
});
});
}
}
You are close, your action needs to send the data to the store by dispatching an event which your reducer can then catch. This is done using the type attribute on the dispatch object.
https://redux.js.org/basics/actions
return fetch('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => {
dispatch({
type: RECEIVE_FILMS,
payload: response,
})
})
You then need to grab the data in your reducer and put it in the store
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return {
...state,
films: action.payload.films
};
default:
return state;
}
}
It looks like you just need to import your action type constant into your reducer using a named import instead of a default export.
i.e. import {RECEIVE_FILMS} from '../actions' rather than import RECEIVE_FILMS from '../actions'
Just dispatch result of resolved fetch promise like so:
if the payload is json, then:
export const fetchFilmsRequest = () => {
return dispatch => {
return fetch('https://www.snagfilms.com/apis/films.json?limit=10')
.then(response => response.json())
.then(response => {
dispatch({
type: RECEIVE_FILMS,
payload: response
})
})
}
Your reducer would need modifying slightly to:
export function films (state = [], action) {
switch (action.type) {
case RECEIVE_FILMS:
return [...action.payload]; // assuming response is jus array of films
default:
return state;
}
}