How to fetch .json data from URL? - javascript

I have problem with fetching data from URL.
When I write data inside of a file, app works great, but when I try to call same data from URL, I get error.
I made a test with small app where everything was inside of a App.js file, and it worked. But new app is kinda devided in multiple files, and this is where problem starts.
Here is events.js where I call data and code works:
import {
TOGGLE_FAVORITE_EVENT
} from '../const';
import toggle from './toggle';
let data = [
{
type: 'PARTY',
title: 'Party in the Club',
adress: 'New York',
date: '9. 9. 2019.',
image: '',
text: [
'Party description...'
],
coordinates: [50, 50],
id: 'events_1'
}
];
let events = (state = data, action) => {
switch(action.type){
case TOGGLE_FAVORITE_EVENT:
return toggle(state, action.payload.id);
default:
return state;
}
}
export default events;
This is how I try to fetch data, which doesn't work:
import {
TOGGLE_FAVORITE_EVENT
} from '../const';
import toggle from './toggle';
// WP REST API
const REQUEST_URL = 'http://some-url.com/test.json';
let data = fetch(REQUEST_URL)
.then(response => response.json() )
.then(data => console.log(data) )
.catch(error => console.log(error));
let events = (state = data, action) => {
switch(action.type){
case TOGGLE_FAVORITE_EVENT:
return toggle(state, action.payload.id);
default:
return state;
}
}
export default events;
NOTE: .json file should be fine, becasue it works in small app.

I think you are trying to initialize the state with the content of a json file loaded from an URL: if I were you, I would create an action specifically to do that. You'll need a library to handle asynchronous processes, like redux-thunk or redux-saga.
Here is a quick example with redux-thunk:
// store
import thunk from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'
import reducer from 'state/reducers'
export const configureStore = () => {
/* thunk is a redux middleware: it lets you call action creators that return a function instead of
an object. These functions can call dispatch several times (see fetchFavorites) */
const middlewares = [thunk]
let store = createStore(reducer, applyMiddleware(...middlewares))
return store
}
// actions
// standard action returning an object
export const receiveFavorites = function(response){
return {
type: "RECEIVE_FAVORITES",
response
}
}
// this action returns a function which receives dispatch in parameter
export const fetchFavorites = function(){
return function(dispatch){
console.log('send request')
return fetch('http://some-url.com/test.json')
.then(response => response.json())
.then(response => {
dispatch(receiveFavorites(response))
})
.catch(error => {
console.log(error)
})
}
}
Now, with a reducer implemented for the action RECEIVE_FAVORITES, you can call the function fetchFavorites: it will send the request and fill the state however you do it in the reducer.

Related

combineReducers is overwriting state instead of setting up different states

I'm trying to learn Redux.
I have this action which fetches data:
export const FETCH_SUCCESS = "FETCH_SUCCESS";
import axios from "axios";
export const fetchData = () => {
return async dispatch => {
try {
const result = await axios(
`url`
);
dispatch({ type: FETCH_SUCCESS, payload: { result } });
} catch (error) {
console.log(error);
}
};
};
And this reducer:
import { FETCH_SUCCESS } from "../actions/mainCategories";
const initialState = {
mainCategories: []
};
const mainCategoriesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_SUCCESS:
return { data: action.payload.result.data };
default:
return state;
}
};
export default mainCategoriesReducer;
I use this twice, one for the mainCategories and the exact same code for my subcategories but in different files, so that I can combine it in App.js like this:
import mainCategoriesReducer from "./store/reducers/mainCategories";
import subCategoriesReducer from "./store/reducers/subCategories";
const rootReducer = combineReducers({
mainCategories: mainCategoriesReducer,
subCategories: subCategoriesReducer,
});
const store = createStore(rootReducer, applyMiddleware(ReduxThunk));
I wrap the Provider with the stored store around my App:
<Provider store={store}>
<App />
</Provider>
In the components I use it like this:
import { fetchData } from "../store/actions/mainCategories";
const mainCategories = useSelector(state => state.mainCategories);
useEffect(() => {
dispatch(fetchData());
}, [dispatch]);
And the same for the subCategories but with the corresponding imports and states (state.subCategories)
Everything is working as expected. When the App loads, it fetches my mainCategories. I can navigate to my subcategories but when I go back, the mainCategories are overwritten by the subCategories.
It seems like the combineReducers merges / overwrites instead of creating the different states. What am I doing wrong? Thank you
You need different type names to make this work. When you combine reducers, it's important that the app knows how to route your actions to the correct store. If you want to call them both FETCH_SUCCESS, I think that's fine, but add the path in front of the definition:
in your main categories actions file:
export const FETCH_SUCCESS = "mainCategories/FETCH_SUCCESS";
and in your subcategories actions file:
export const FETCH_SUCCESS = "subCategories/FETCH_SUCCESS";
That should work, but you may also need to rename the FETCH_SUCCESS variables if it's still not working.

Dispatch actions the proper way

Please, check the Edit
I'm trying to implement sagas in my app.
Right now I am fetching the props in a really bad way.
My app consists mainly on polling data from other sources.
Currently, this is how my app works:
I have containers which have mapStateToProps, mapDispatchToProps.
const mapStateToProps = state => {
return {
someState: state.someReducer.someReducerAction,
};
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({someAction, someOtherAction, ...}, dispatch)
};
const something = drizzleConnect(something, mapStateToProps, mapDispatchToProps);
export default something;
and then, I have actions, like this:
import * as someConstants from '../constants/someConstants';
export const someFunc = (someVal) => (dispatch) => {
someVal.methods.someMethod().call().then(res => {
dispatch({
type: someConstants.FETCH_SOMETHING,
payload: res
})
})
}
and reducers, like the below one:
export default function someReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case types.FETCH_SOMETHING:
return ({
...state,
someVar: action.payload
});
I combine the reducers with redux's combineReducers and export them as a single reducer, which, then, I import to my store.
Because I use drizzle, my rootSaga is this:
import { all, fork } from 'redux-saga/effects'
import { drizzleSagas } from 'drizzle'
export default function* root() {
yield all(
drizzleSagas.map(saga => fork(saga)),
)
}
So, now, when I want to update the props, inside the componentWillReceiveProps of the component, I do:
this.props.someAction()
Okay, it works, but I know that this is not the proper way. Basically, it's the worst thing I could do.
So, now, what I think I should do:
Create distinct sagas, which then I'll import inside the rootSaga file. These sagas will poll the sources every some predefined time and update the props if it is needed.
But my issue is how these sagas should be written.
Is it possible that you can give me an example, based on the actions, reducers and containers that I mentioned above?
Edit:
I managed to follow apachuilo's directions.
So far, I made these adjustments:
The actions are like this:
export const someFunc = (payload, callback) => ({
type: someConstants.FETCH_SOMETHING_REQUEST,
payload,
callback
})
and the reducers, like this:
export default function IdentityReducer(state = INITIAL_STATE, {type, payload}) {
switch (type) {
case types.FETCH_SOMETHING_SUCCESS:
return ({
...state,
something: payload,
});
...
I also created someSagas:
...variousImports
import * as apis from '../apis/someApi'
function* someHandler({ payload }) {
const response = yield call(apis.someFunc, payload)
response.data
? yield put({ type: types.FETCH_SOMETHING_SUCCESS, payload: response.data })
: yield put({ type: types.FETCH_SOMETHING_FAILURE })
}
export const someSaga = [
takeLatest(
types.FETCH_SOMETHING_REQUEST,
someHandler
)
]
and then, updated the rootSaga:
import { someSaga } from './sagas/someSagas'
const otherSagas = [
...someSaga,
]
export default function* root() {
yield all([
drizzleSagas.map(saga => fork(saga)),
otherSagas
])
}
Also, the api is the following:
export const someFunc = (payload) => {
payload.someFetching.then(res => {
return {data: res}
}) //returns 'data' of undefined but just "return {data: 'something'} returns that 'something'
So, I'd like to update my questions:
My APIs are depended to the store's state. As you may understood,
I'm building a dApp. So, Drizzle (a middleware that I use in order
to access the blockchain), needs to be initiated before I call
the APIs and return information to the components. Thus,
a. Trying reading the state with getState(), returns me empty contracts
(contracts that are not "ready" yet) - so I can't fetch the info - I
do not like reading the state from the store, but...
b. Passing the state through the component (this.props.someFunc(someState), returns me Cannot read property 'data' of undefined The funny thing is that I can console.log the
state (it seems okay) and by trying to just `return {data:
'someData'}, the props are receiving the data.
Should I run this.props.someFunc() on, for e.g., componentWillMount()? Is this the proper way to update the props?
Sorry for the very long post, but I wanted to be accurate.
Edit for 1b: Uhh, so many edits :)
I solved the issue with the undefined resolve. Just had to write the API like this:
export function someFunc(payload) {
return payload.someFetching.then(res => {
return ({ data: res })
})
}
I don't want to impose the pattern I use, but I've used it with success for awhile in several applications (feedback from anyone greatly appreciated). Best to read around and experiment to find what works best for you and your projects.
Here is a useful article I read when coming up with my solution. There was another, and if I can find it -- I'll add it here.
https://medium.com/#TomasEhrlich/redux-saga-factories-and-decorators-8dd9ce074923
This is the basic setup I use for projects.
Please note my use of a saga util file. I do provide an example of usage without it though. You may find yourself creating something along the way to help you reducing this boilerplate. (maybe even something to help handle your polling scenario).
I hate boilerplate so much. I even created a tool I use with my golang APIs to auto-generate some of this boilerplate by walking the swagger doc/router endpoints.
Edit: Added container example.
example component
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { getResource } from '../actions/resource'
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getResource
},
dispatch
)
class Example extends Component {
handleLoad = () => {
this.props.getResource({
id: 1234
})
}
render() {
return <button onClick={this.handleLoad}>Load</button>
}
}
export default connect(
null,
mapDispatchToProps
)(Example)
example action/resource.js
import { useDispatch } from 'react-redux'
const noop = () => {}
const empty = []
export const GET_RESOURCE_REQUEST = 'GET_RESOURCE_REQUEST'
export const getResource = (payload, callback) => ({
type: GET_RESOURCE_REQUEST,
payload,
callback,
})
// I use this for projects with hooks!
export const useGetResouceAction = (callback = noop, deps = empty) => {
const dispatch = useDispatch()
return useCallback(
payload =>
dispatch({ type: GET_RESOURCE_REQUEST, payload, callback }),
// eslint-disable-next-line react-hooks/exhaustive-deps
[dispatch, ...deps]
)
}
Fairly basic redux action file.
example reducers/resource.js
export const GET_RESOURCE_SUCCESS = 'GET_RESOURCE_SUCCESS'
const initialState = {
resouce: null
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case GET_RESOURCE_SUCCESS: {
return {
...state,
resouce: payload.Data,
}
}
}
Fairly standard reducer pattern - NOTE the use of _SUCCESS instead of _REQUEST here. That's important.
example saga/resouce.js
import { takeLatest } from 'redux-saga/effects'
import { GET_RESOUCE_REQUEST } from '../actions/resource'
// need if not using the util
import { GET_RESOURCE_SUCCESS } from '../reducers/resource'
import * as resouceAPI from '../api/resource'
import { composeHandlers } from './sagaHandlers'
// without the util
function* getResourceHandler({ payload }) {
const response = yield call(resouceAPI.getResouce, payload);
response.data
? yield put({ type: GET_RESOURCE_SUCCESS, payload: response.data })
: yield put({
type: "GET_RESOURCE_FAILURE"
});
}
export const resourceSaga = [
// Example that uses my util
takeLatest(
GET_RESOUCE_REQUEST,
composeHandlers({
apiCall: resouceAPI.getResouce
})
),
// Example without util
takeLatest(
GET_RESOUCE_REQUEST,
getResourceHandler
)
]
Example saga file for some resource. This is where I wire up the api call with the reducer call in array per endpoint for the reosurce. This then gets spread over the root saga. Sometimes you may want to use takeEvery instead of takeLatest -- all depends on the use case.
example saga/index.js
import { all } from 'redux-saga/effects'
import { resourceSaga } from './resource'
export const sagas = [
...resourceSaga,
]
export default function* rootSaga() {
yield all(sagas)
}
Simple root saga, looks a bit like a root reducer.
util saga/sagaHandlers.js
export function* apiRequestStart(action, apiFunction) {
const { payload } = action
let success = true
let response = {}
try {
response = yield call(apiFunction, payload)
} catch (e) {
response = e.response
success = false
}
// Error response
// Edit this to fit your needs
if (typeof response === 'undefined') {
success = false
}
return {
action,
success,
response,
}
}
export function* apiRequestEnd({ action, success, response }) {
const { type } = action
const matches = /(.*)_(REQUEST)/.exec(type)
const [, requestName] = matches
if (success) {
yield put({ type: `${requestName}_SUCCESS`, payload: response })
} else {
yield put({ type: `${requestName}_FAILURE` })
}
return {
action,
success,
response,
}
}
// External to redux saga definition -- used inside components
export function* callbackHandler({ action, success, response }) {
const { callback } = action
if (typeof callback === 'function') {
yield call(callback, success, response)
}
return action
}
export function* composeHandlersHelper(
action,
{
apiCall = () => {}
} = {}
) {
const { success, response } = yield apiRequestStart(action, apiCall)
yield apiRequestEnd({ action, success, response })
// This callback handler is external to saga
yield callbackHandler({ action, success, response })
}
export function composeHandlers(config) {
return function*(action) {
yield composeHandlersHelper(action, config)
}
}
This is a very shortened version of my saga util handler. It can be a lot to digest. If you want the full version, I'll see what I can do. My full one handles stuff like auto-generating toast on api success/error and reloading certain resources upon success. Have something for handling file downloads. And another thing for handling any weird internal logic that might have to happen (rarely use this).

Cannot access data request Axios, React-Redux

I am trying to make an API request using Axios in React-Redux environment. On the console everything seems to be fine, however if I try to access any of the data I either get undefined or empty array.
This is my component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { discoverMovie } from '../actions'
//Home component
class Home extends Component {
//make request before the render method is invoked
componentWillMount(){
this.props.discoverMovie();
}
//render
render() {
console.log('movie res ',this.props.movies.movies.res);
console.log('movie ',this.props.movies);
return (
<div>
Home
movie
</div>
)
}
};
const mapStateToProps = (state) => {
return{
movies : state.movies
}
}
export default connect(mapStateToProps, { discoverMovie })(Home);
This is my action
import { DISCOVER_MOVIE } from '../constants';
import axios from 'axios';
//fetch movie
const fetchMovie = () => {
const url = 'https://api.themoviedb.org/3/discover/movie?year=2018&primary_release_year=2018&page=1&include_video=false&include_adult=false&sort_by=vote_average.desc&language=en-US&api_key=72049b7019c79f226fad8eec6e1ee889';
let result = {
res : [],
status : ''
};
//make a get request to get the movies
axios.get(url).
then((res) => {
result.res = res.data.results;
result.status = res.status;
return result;
});
//return the result after the request
return result;
}
//main action
const discoverMovie = () =>{
const result = fetchMovie();
//return the action
return {
type : DISCOVER_MOVIE,
payload : result
}
}
export default discoverMovie;
This is the reducer
import { DISCOVER_MOVIE } from '../constants';
//initial state
const initialState = {
movies : {},
query : '',
};
//export module
export default (state = initialState, actions) =>{
switch(actions.type){
case DISCOVER_MOVIE :
return {
...state,
movies : actions.payload
};
default :
return state;
}
}
this is the log that I get from the console
as you can see if I log the entire object I see all data, however if go deep and try to access the result I either get an undefined or an empty array and using redux-dev-tools I noticed that the state does not contain any value.
I read on internet including this portal similar issue but could not find any solution for my issue.
Solution
From official docs:
You may use a dedicated status field in your actions
Basically you need to dispatch action for each state to make an async action to work properly.
const searchQuery = () => {
return dispatch => {
dispatch({
type : 'START',
})
//make a get request to get the movies
axios.get(url)
.then((res) => {
dispatch({type : 'PASS', payload : res.data});
})
.catch((err) => {
dispatch({type : 'FAILED', payload : res.error});
});
}
With redux-thunk it's pretty simple to set up. You just have to make some changes to your store. Out the box, I'm pretty sure redux isn't the most friendly with async and that's why thunk is there.
import { ..., applyMiddleware } from "redux";
import thunk from "redux-thunk";
...
const store = createStore(reducer, applyMiddleware(thunk));
...
Then in your action you'll need to return dispatch which will handle your logic for your axios call.
const fetchMovie = () => {
return dispatch => {
const url = //Your url string here;
axios.get(url).then(res => {
dispatch(discoverMovie(res.data.results, res.status);
}).catch(err => {
//handle error if you want
});
};
};
export const discoverMovie = (results, status) => {
return {
type: DISCOVER_MOVIE,
payload: results,
status: status
};
};
Your reducer looks fine, though with the way my code is typed you'll have status separately. You can combine them into it's own object before returning in discoverMovie, if you need status with the results.
This is my first answer on stack so let me know if I can clarify anything better!

React Redux - Actions must be plain objects. Use custom middleware for async actions

I try to deal with ajax data using axom in my learning react,redux project and I have no idea how to dispatch an action and set the state inside a component
In component will mount
componentWillMount(){
this.props.actions.addPerson();
}
Store
import { createStore, applyMiddleware } from "redux";
import rootReducer from "../reducers";
import thunk from "redux-thunk";
export default function configureStore() {
return createStore(rootReducer, applyMiddleware(thunk));
}
In Action :
import * as types from "./action-types";
import axios from "axios";
export const addPerson = person => {
var response = [];
axios
.get(`&&&&&&&&&&&`)
.then(res => {
response = res.data;
return {
type: types.ADD_PERSON,
response
};
});
};
In reducer
import * as types from "../actions/action-types";
export default (state = [], action) => {
console.log("action======>", action);
switch (action.type) {
case types.ADD_PERSON:
console.log("here in action", action);
return [...state, action.person];
default:
return state;
}
};
I am getting Actions must be plain objects. Use custom middleware for async actions.
You should use dispatch for async function. Take a look of the redux-thunk's documentation: https://github.com/gaearon/redux-thunk
In Action:
import * as types from "./action-types";
import axios from "axios";
export const startAddPerson = person => {
return (dispatch) => {
return axios
.get(`https://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
dispatch(addPersons(res.data));
});
}
};
export const addPersons = personList => {
return {
type: types.ADD_PERSON,
personList
};
}
In PersonComponent:
class Person extends Component {
constructor(props){
super(props);
}
componentWillMount() {
this.props.dispatch(startAddPerson())
}
render() {
return (
<div>
<h1>Person List</h1>
</div>
);
}
}
export default Redux.connect()(Person);
You need two actions here: postPerson and addPerson.
postPerson will perform the API request and addPerson will update the store:
const addPerson = person => {
return {
type: types.ADD_PERSON,
person,
}
}
const postPerson = () => {
return (dispatch, getState) => {
return axios.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => dispatch(addPerson(res.data)))
}
}
in your component, call postPerson()
You use the redux-thunk library which gives you access to the "getState" and "dispatch" methods. I see that that has been added by Chenxi to your question. Run your async operation first within your action and then call "dispatch" with your simple action action creator, which will return the simple object that redux is looking for.
Here is what your async action creator and your simple action creator(broken out into two action creators) will look like:
export const addPersonAsync = (person) => {
return (dispatch) => {
var response = [];
axios
.get(`http://599be4213a19ba0011949c7b.mockapi.io/cart/Cart`)
.then(res => {
response = res.data;
dispatch(addPerson(response));
});
};
};
export const addPerson = (response) => ({
type: types.ADD_PERSON,
response
});
From your component, you'll now call the "addPersonAsync" action creator.

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