Using axios with React and Redux - javascript

I want to get data from an API and put them into my documents, and I know how to do that, but I don't know the right way. I've seen in some tutorials saying that we have to do every API calls in middlewares, but I can't find any example.
My code:
listReducer.js
const initialState = {
documents: []
}
export default function documents(state = initialState, action) {
switch (action.type) {
case 'LOAD_DOCS':
//set documents = response from api
default:
return state
}
}
listAction.js
export function loadDocs(offset, range) {
return {
type: 'LOAD_DOCS',
offset,
range
}
}
store.js
import { createStore } from 'redux'
import reducers from './reducers'
const store = createStore(reducers)
export default store

Hi You can create a thunk using a redux middleware "Redux-thunk".
thunk is basically a function in which you can make an API call and after getting the data from the server you can dispatch an action to set the data into the reducer.
You Get the dispatch function in the thunk u will create.
So instead of call the dispatch directly you will call the thunk function

Related

Using React, why is redux saga not intercepting the action?

I'm very new to redux-saga and am trying to get a simple demo working that makes an API call and sends the response data to a reducer so it can be saved to the store. It is my understanding that the redux-saga flow should work as follows.
A component calls an action creator
The action creator then emits an action using a specific type
The watcher sagas all listen for any actions emitted and intercept an action that it is listening for. It then calls the appropriate worker saga.
The worker saga makes an API call and dispatches an action to the reducers with the type of action and the payload.
The reducer listens for any dispatched actions and if it matches, it then uses the supplied data to update the state in the store.
I have laid out my code to follow that flow but things aren't working quite right. Let me show my code and then i'll elaborate on the problem.
components/PostsIndex.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchPosts } from '../actions';
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
renderPosts() {
console.log(this.props.posts);
}
render() {
return (
<div>
{this.renderPosts()}
</div>
);
}
}
const mapStateToProps = state => ({
posts: state.posts
});
export default connect(mapStateToProps, { fetchPosts })(PostsIndex);
actions/index.js
import axios from 'axios';
export const FETCH_POSTS = 'FETCH_POSTS';
export const fetchPosts = () => {
console.log('fetchPosts() in actions');
return {
type: FETCH_POSTS
};
};
sagas/index.js
import 'regenerator-runtime/runtime';
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects';
import axios from 'axios';
import { FETCH_POSTS } from '../actions';
const ROOT_URL = 'https://reduxblog.herokuapp.com/api';
const API_KEY = '?key=asdsd1234';
// Watcher sagas
// Listen for an action and run the appropriate Worker saga
export function* watchFetchPosts() {
yield takeEvery(FETCH_POSTS, workFetchPosts);
}
// Worker sagas
// Respond to the actions that are caught by the watcher sagas
export function* workFetchPosts() {
try {
console.log('workFetchPosts() in sagas');
// Try to call the API
console.log('Attempting to call the posts API');
const uri = `${ROOT_URL}/posts${API_KEY}`;
const response = yield call(axios.get, uri);
console.log('Fetched Posts Response in saga worker: ', response);
yield put({
type: FETCH_POSTS,
payload: response
});
} catch (error) {
// Act on the error
console.log('Request failed! Could not fetch posts.');
console.log(error);
}
}
// Root sagas
// Single entry point to start all sagas at once
export default function* rootSaga() {
console.log('redux saga is running');
yield [watchFetchPosts()];
}
reducers/PostsReducer.js
import { mapKeys } from 'lodash';
import { FETCH_POSTS } from '../actions';
export default (state = {}, action) => {
switch (action.type) {
case FETCH_POSTS:
console.log(action);
// Create a new state object that uses an AJAX request response and grabs the 'id' property from each object in the response to use as its key
return mapKeys(action.payload.data, 'id');
}
return state;
};
It seems like the reducers are still picking up the emitted actions, which is wrong. I did notice that if I run the AJAX call in the action creator as well, then the saga will run, but the saga should intercept the communication between the action creator and the reducer, so something isn't set up quite right. Any ideas?
A full environment of my workflow can be edited at https://stackblitz.com/edit/react-redux-saga-demo. It might be easier to see the problem there.
Sagas do not stop actions from reaching the reducers. The saga middleware explicitly does the equivalent of:
next(action); // pass the action onwards to the reducers
processSagas(action);
So, the reducers will always see an action first, and the saga behavior will be excecuted after that.
The other issue is that it looks like you're trying to use the same action type to trigger the fetch behavior in the saga, and process the results in the reducer. I find that if you're using sagas, you generally have some actions that are "signals" meant to trigger saga behavior, and others that are intended to actually be handled by the reducers and update state. So, in your case, I would suggest using "FETCH_POSTS" as the signal to kick off the fetching, and have the saga then dispatch "FETCH_POSTS_SUCCESS" once the data is received and have the reducer respond to that action instead. (And, after noticing that you had that StackBlitz example up, I confirmed that just dispatching the results as "FETCH_POSTS_SUCCESS" does indeed work as I'd expect it too.)

Second Redux ##INIT action clears the store state which already was modified with another action

I'm new to React/Redux and not sure if I'm not doing something wrong.
I'm having a component which makes an AJAX call on componentDidMount to fetch data from the server to render.
The problem is that Redux is dispatching two #INIT actions and often the second one is dispatched after I already received the response from the server. It comes with an empty (initial) state which is passed to the component props and, as result, I receive a blank screen.
Please see this log produced by the reducer:
I already found that having two ##INIT actions is an expected behavior, the first one is needed to test the reducers and the second one is an actual init (check the discussion here).
The question is how can I solve this issue in a proper way. Is it a race condition or am I doing something wrong? Thanks!
Update
What is interesting is that it definitely relates to the performance of my laptop. The server is also running on my local environment. To allow me to proceed with development while I'm waiting for the answer I temporarily put setTimeout with 100ms delay into componentDidMount. Now I commented it and can't repro the issue.
Update Adding pieces of my code
Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
const middleware = window.devToolsExtension
? compose(
applyMiddleware(thunk),
window.devToolsExtension()
)
: applyMiddleware(thunk);
const store = createStore(reducers, middleware);
export default store;
Reducer (nothing special, just used it to log the action because browser Redux extension shows only one ##INIT action)
import * as types from '../actions/types';
const initialState = {
listings: []
};
export default function(state = initialState, action) {
console.log(action, state);
switch (action.type) {
case types.LISTINGS_FOUND:
return { listings: action.payload };
default: return state;
}
};
Component
import React from 'react';
import { connect } from 'react-redux';
import { search as searchListings } from '../../actions/listing-actions'
import View from './View'
class Container extends React.Component {
componentDidMount() {
if (this.props.listings.length === 0) {
this.props.searchListings();
}
}
render() {
console.log('rendering list', this.props.listings);
return (
<View listings={this.props.listings}/>
);
}
}
Container.propTypes = {
listings: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
searchListings: React.PropTypes.func.isRequired,
};
const mapStateToProps = function(store) {
return {
listings: store.listingSearch.listings
};
};
export default connect(mapStateToProps, { searchListings })(Container);
As I said I can't repro this issue now. I'll try to make some synthetic example to repro this later when I have more time.
You should load on componentDidMount() as recommended in the docu. You can also see in this example from the creator of Redux.

Difficulties when try to map the Redux state with the props of the container

I am trying to get familiar with the flow of the react-boilerplate.
Till now I love how neat clean and easy to understand are things, I although feel that I miss a piece of the puzzle. Would be nice if someone with more experience could help me with that.
The problem I am facing at the moment goes as follows.
I am triggering an action within componentWillMount() of a specific component.
The action is being created in actions.js, its a simple get request made with axios.
The data are being processed in a promise middleware library redux-promise.
The promise is now being passed into the reducer of the specific component, where the whole state and the data that I need are being returned.
Trying to catch this state at the component is where I fail. I am trying to mapStateToProps but cannot find the data that I need there instead a Map {} is being received.
How do I Map this object with my props ?
I am sure I miss something important.
Here is my repo.
https://github.com/paschalidi/blog-react-redux
And here is my code so you can have a brief look.
index.js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
import { fetchPosts } from './actions'
import selectPostsIndex from './selectors'
export class PostsIndex extends React.Component { // eslint-disable-line react/prefer-stateless-function
componentWillMount() {
this.props.fetchPosts();
}
render() {
return (
<div>
<h3>Posts</h3>
<ul className="list-group">
A list would render here.
</ul>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state.posts)
//return { posts: state } //****I dont get why the redux state is not being given here.
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ fetchPosts }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
actions.js
import axios from 'axios'
import { FETCH_POSTS } from './constants';
const ROOT_URL = 'http://reduxblog.herokuapp.com/api';
const API_KEY = '?key=dsklhfksdhfjkdshfkjdshkj';
export function fetchPosts() {
const request = axios.get(`${ROOT_URL}/posts${API_KEY}`);
return {
type: FETCH_POSTS,
payload: request
};
}
store.js
import promise from 'redux-promise';
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
promise
];
reducer.js
import { fromJS } from 'immutable';
import {
FETCH_POSTS
} from './constants';
const initialState = fromJS({ all:[], post: null });
function postsIndexReducer(state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return { ...state, all: action.payload.data };
default:
return state;
}
}
export default postsIndexReducer;
Also the action is being registered in reducers.js
import PostsReducer from 'containers/PostsIndex/reducer'
export default function createReducer(asyncReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
posts: PostsReducer,
form: reduxFormReducer,
...asyncReducers,
});
}
Note I didn't test your code, but it looks like your reducer puts the fetched data in the field all of your global states posts field, but your mapStateToProps doesn't pick that up. Note that mapStateToProps should slice the part of the global state that the given component is interested in.
After a successful fetch the state you receive in mapStateToProps should look something like this:
{
posts: {
all: // whatever fetch returned
post: null
}
}
So your mapStateToProps could look something like this (note that this method receives the global state as an argument, not just for the specific reducer):
function mapStateToProps(state) {
// in component this.props.posts is { all: /* fetch result */, post: null }
return { posts: state.posts }
}
Also try to debug these methods, it becomes clearer once you see the flow of the data!
This GitHub issue covers this exact problem: https://github.com/reactjs/react-redux/issues/60.
I had to manually extract the values from the Map in mapStateToProps function:
const mapStateToProps = (state) => {
return {
posts: state.get('posts'),
};
}
Thanks to this StackOverflow post.

React Redux Confusion

Redux has proven a little tricky for me to wrap my head around, and I was wondering if someone could help point me in the right direction of what piece I am not grasping to get my desired results. Just a forewarning: I am using ES6 syntax.
Okay, so I have setup somewhat of a sandbox to test out how redux works, and this is the current file setup I am working with.
-actions
--index.js
-reducers
--index.js
--reducer_user.js
-containers
--ReduxTest.js
In my container, ReduxTest.js, I have the following code.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from '../actions/index';
class ReduxTest extends Component {
render() {
return (
<div>
{console.log(this.props.fetchUser())}
{console.log(this.props.user)}
</div>
)
}
}
export default connect( null, { fetchUser } ) (ReduxTest);
When I render ReduxTest.js to the screen, the first console.log statement shows up as,
Object { type: "FETCH_USER", payload: "This is just a test."}
The second one however, shows up as "undefined".
Here is what my actions index.js looks like,
export const FETCH_USER = 'FETCH_USER';
export function fetchUser() {
const testing = "This is just a test.";
return {
type: FETCH_USER,
payload: testing
}
}
Here is my reducer_user.js file
import { FETCH_USER } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case FETCH_USER:
return action.payload;
}
return state;
}
and finally, here is my index.js in the reducer folder
import { combineReducers } from 'redux';
import UserReducer from './reducer_user';
const rootReducer = combineReducers({
user: UserReducer
});
export default rootReducer;
I am using a video tutorial from Udemy, so that is where I am getting some of my syntax and what not. I was under the impression that I would be able to access "this.props.user" from the index.js reducer, but I am doing something wrong, or missing a step. Any help would be appreciated.
Just so I am clear, all my intention is, is to successfully have the ReduxTest container console log JUST the string that is in the payload. if you can help with that, I think I can carry it on from there. Thanks =)
You're only passing the action creator to your component. If you want to access your props.user than you have to provide it. You can achieve this by the first argument of the connect function.
const mapStateToProps = state => ({
user: state.user,
});
export default connect(mapStateToProps, { fetchUser })(ReduxTest);
The first argument of connect must be a callable function. The only argument of this function is the current state. The function must return an object, containing all properties you want to access inside your component.
Please notice that the state of your user reducer is set to null initially. Redux fires multiple, internal actions. If you log your current state in your render method, it can happen, that your state gets logged before you are calling your own actions. This can be confusing.
You can change the initial state of your reducer this way:
import { FETCH_USER } from '../actions/index';
export default function(state = 'User not fetched yet', action) {
switch(action.type) {
case FETCH_USER:
return action.payload;
}
return state;
}

Redux: Are only synchronous calls allowed from reducer functions?

I have a reactJs app and right now I'm learning Redux to use it as Flux implementation.
I've created a store and I have created my first reducer function but now I have some questions that come to my mind, please help me to understand.
As you can see I have an action called 'FIND_PRODUCTS' which is basically fetching data from a backend service. To call this backend service I use basically an asynchronous ajax call, so basically the problem I'm facing is that the state is returned from the reducer function before my backend call has finished, so then the state is not updated correctly and the subscribers to the store are getting incorrect data. This problem is solved if I switch to a synchronous call, but then, the first warning I get is that synchronous call should be avoided because it might decrease the user's experience (performance).
So my question, can we only fetch data synchronously from a reducer function?
Should the fetching data happens in the reducer function or there is another way to do that? if so, what is it?
Does this model of redux of having a single object tree to maintain the state scales well with large applications? If I have 1000 actions the switch in my reducer function will be huge! How can we avoid that?
Thank you!!
const initialState = {
availableLocales: [{text: 'En'}, {text: 'Es'}, {text: 'Fr'}],
selectedLocale: 'En',
translations: i18n.getTranslations(),
products: []
};
const reducer = (state = initialState, action = {type: 'NONE'})=> {
//To make the reducer a pure function
deepFreeze(state);
deepFreeze(action);
switch (action.type) {
case 'SWITCH_LOCALE':
let newState = Object.assign({}, state, {
selectedLocale: action.locale,
translations: i18n.getTranslations(action.locale)
});
return newState;
case 'FIND_PRODUCTS':
let newState = Object.assign({}, state, {
products:ProductHelper().findProductsByProductType(action.productType)
});
return newState;
default:
return state
}
return state;
}
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
const store = createStore(reducer);
// You can subscribe to the updates manually, or use bindings to your view layer.
store.subscribe(() =>
console.log(store.getState())
);
export default store;
Consider this:
Create actions.js file and export the actions functions like this:
import * as types from '../constants/action_types';
import * as api from '../utils/api'
export function something1(someId){
return (dispatch) => {
dispatch({type: `${types.SOMETHING1}_PENDING`});
api.getSomething(someId)
.then((res) => {
dispatch({
type: `${types.SOMETHING1}_SUCCEEDED`,
somethings: res.body
});
.catch((err) => {
dispatch({
type: `${types.SOMETHING1}_FAILED`,
errors: err.body
})
});
}
}
export function something2(someOtherId){
return (dispatch) => {
dispatch({type: `${types.SOMETHING2}_PENDING`});
api.getSomething2(someOtherId)
.then((res) => {
dispatch({
type: `${types.SOMETHING2}_SUCCEEDED`,
otherThings: res.body
});
.catch((err) => {
dispatch({
type: `${types.SOMETHING2}_FAILED`,
errors: err.body
})
});
}
}
Then the state only change when you have the data
Next separate your reducers in separate files and create one file to export them all
like this reducers/index.js:
export { default as reducer1 } from './reducer1';
export { default as reducer2 } from './reducer2';
export { default as reducer3 } from './reducer3';
export { default as reducer4 } from './reducer4';
Then config your store like this:
configure_store.js
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';
const rootReducer = combineReducers(reducers);
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
export default function configureStore(initialState) {
return createStoreWithMiddleware(rootReducer, initialState);
}
Finally add this to your root:
import configureStore from '../store/configure_store';
const store = configureStore();
class Root extends Component {
render() {
return (
...
<Provider store={ store } >
...
</Provider>
);
}
}
export default Root;
First, you CAN'T fetch data in reducer, because it needs to be pure by redux definition. You should create action creator, that would fetch data asynchronously and pass it to reducer. Actions CAN be impure.
Here you can read more http://redux.js.org/docs/advanced/AsyncActions.html
Also you can use middleware like redux-thunk to simplify this. https://github.com/gaearon/redux-thunk
As for the second question, you can have more than one reducer in your app. and than combine them with combineReducers(...) function http://redux.js.org/docs/basics/Reducers.html
As redux documentation said, reducers should be pure functions, so it shouldn't do ajax requests.
Better way to do so is use redux-thunk middleware, that allows you to call dispatch several times in one action.
So, in your example you do something like this:
// definition of action creator
function loadProducts(productType) {
return {type: 'FIND_PRODUCTS', productType: productType}
}
...
// calling dispatch of your action
dispatch(loadProducts(productType));
But with redux-thunk your action creator will be something like this:
function loadProducts(productType) {
return function(dispatch){
dispatch({type: 'FIND_PRODUCT_STARTED'});
// I don'h know how findProductsByProductType works, but I assume it returns Promise
ProductHelper().findProductsByProductType(productType).then(function(products){
dispatch({type: 'FIND_PRODUCT_DONE', products: products});
});
}
}
And your reducer will become pure function:
...
case 'FIND_PRODUCTS_DONE':
let newState = Object.assign({}, state, {
products: action.products,
});
return newState;
...
In this case you can also handle loading state, i.e. set loading flag in your state to true when action.type is FIND_PRODUCT_STARTED.
In my example I assume that findProductsByProductType returns Promise. In this case you can even use redux-promise-middleware without redux-thunk, it will do all work for you:
function loadProducts(productType) {
return {
type: 'FIND_PRODUCT',
payload: {
promise: ProductHelper().findProductsByProductType(productType)
}
}
}
You should not use ProductHelper() in your reducer to request data.
Instead, you should use an action creator to dispatch an action that requests the data from your API. Your API middleware would return a promise that on completion would dispatch an action intent with payload for your reducer to consume and for it to return the next state.
I recommend you look at Redux Thunk and Redux API middleware

Categories

Resources