Calling one action from another action creator - reactjs

I'm working on a Redux app in which many filter components can change the nature of a search to be performed. Any time the state of one of those filter components changes, I want to re-run a search action. I can't seem to call the search action from each of the filter components correctly, however.
Here's the main search action:
// actions/search.js
import fetch from 'isomorphic-fetch';
import config from '../../server/config';
export const receiveSearchResults = (results) => ({
type: 'RECEIVE_SEARCH_RESULTS', results
})
export const searchRequestFailed = () => ({
type: 'SEARCH_REQUEST_FAILED'
})
export const fetchSearchResults = () => {
return (dispatch, getState) => {
// Generate the query url
const query = getSearchQuery(); // returns a url string
return fetch(query)
.then(response => response.json()
.then(json => ({
status: response.status,
json
})
))
.then(({ status, json }) => {
if (status >= 400) dispatch(searchRequestFailed())
else dispatch(receiveSearchResults(json))
}, err => { dispatch(searchRequestFailed()) })
}
}
fetchSearchResults works fine when I call it from connected React components. However, I can't call that method from the following action creator (this is one of the filter action creators):
// actions/use-types.js
import fetchSearchResults from './search';
export const toggleUseTypes = (use) => {
return (dispatch) => {
dispatch({type: 'TOGGLE_USE_TYPES', use: use})
fetchSearchResults()
}
}
Running this yields: Uncaught TypeError: (0 , _search2.default) is not a function. The same happens when I run dispatch(fetchSearchResults()) inside toggleUseTypes.
How can I resolve this problem and call the fetchSearchResults method from the actions/use-types.js action?

I see 2 errors:
You're importing the fetchSearchResults function incorrectly.
This is where the TypeError _search2.default is coming from:
Uncaught TypeError: (0 , _search2.default) is not a function
You're dispatching the fetchSearchResults action/thunk incorrectly
Error 1: Incorrect import
// This won't work. fetchSearchResults is not the default export
import fetchSearchResults from './search';
// Use named import, instead.
import {fetchSearchResults} from './search';
Error 2: Incorrect action usage
// This won't work, it's just a call to a function that returns a function
fetchSearchResults()
// This will work. Dispatching the thunk
dispatch(fetchSearchResults())

Related

Getting TypeError after Async API Call with axios

I'm trying to figure out some things that are occurring on my code.
I'm fetching some data from an Rest API I've made, and after an async call I'm getting:
TypeError: Cannot read property 'field' of undefined
I can't figure out what is happening, because when I log the data on the console, I can see my Object populated with the correct info. Just to be sure that I've not made a type mistake, I've copied the data from the console.log(response) and created an Object and it worked.
Here's the call:
import axios from 'axios';
const url_service_villain = "https://some_url/"
export const findRandomVillain = async () => {
try {
const {data} = await axios.get(url_service_villain + "find/random");
return await data
} catch (err) {
console.log(err);
};
};
And here is where I call the method:
import React from 'react';
import Character from './components/Character'
import {findRandomVillain} from './services/VillainService';
async function getRandomVillain(){
const data = await findRandomVillain();
console.log(data);
return data;
}
function CharacterList(){
const char = getRandomVillain();
return (
<section className="characterList">
<Character key={char.id} images={char.images.md} name={char.name} powerstats={char.powerstats}/>
</section>
)
};
export default CharacterList;
On the method getRandomVillain() the console.log(data) prints the object that I want,
but on the CharacterList I get the TypeError I listed above.
I also noticed on the top on the console window the following:
Promise {<pending>} App.js:17
Promise {<pending>} App.js:17
App.js:20 Uncaught TypeError: Cannot read property 'md' of undefined
at CharacterList (App.js:20)
...{/*long list of errors here*/}
{id: "123", name: "Sinestro", powerstats: {…}, images: {…}} App.js:11
{id: "123", name: "Sinestro", powerstats: {…}, images: {…}} App.js:11
{id: "123", name: "Sinestro", powerstats: {…}, images: {…}} App.js:11
I think that is something related to the async call, and I don't know why the last line repeat 3 times, since I'm only logging it once.
When the react components is rendering. It will not wait the async call completed. When the first time the react component mounted, the char is empty, but you are getting attributes values from char: "id"; "name". So you need add an exception to make sure when to render Component Character with char with successful API call.
{char !== undefined (exceptions here) && }
you need to handle the promise using then(), catch() to Read more
the solution is to call findRandomVillain() when the component is ready by using useEffect and using useState to set the char
import React, { useEffect, useState } from "react";
import Character from "./components/Character";
import { findRandomVillain } from "./services/VillainService";
function CharacterList() {
const [char, setChar] = useState();
// using useEffect to make sure component is Ready
useEffect(() => {
findRandomVillain()
.then((res) => setChar(res)) // handle promising
.catch((err) => console.log(err));
}, []);
if (!char) return <div>loading</div>;
return (
<section className="characterList">
<Character
key={char.id}
images={char.images.md}
name={char.name}
powerstats={char.powerstats}
/>
</section>
);
}
export default CharacterList;

Subscribing to a redux action in a react component

I have an async thunk that fetches some information from a web service, it can dispatch three types of actions
FETCH_REQUESTED
FETCH_SUCCEEDED
FETCH_FAILED
Finally, if it's succeeded; it returns the actual response, or an error object.
I have a component that should detect whether the operation has failed or not, preferably by subscribing to the FETCH_FAILED action and displaying an error message based on the type of the error (404/401 and other status codes)
export const fetchData = () => {
return async (dispatch, getState) => {
const appState = getState();
const { uid } = appState.appReducer;
await dispatch(fetchRequested());
try {
const response = await LookupApiFactory().fetch({ uid });
dispatch(fetchSucceeded(response));
return response;
} catch (error) {
dispatch(fetchFailed());
return error;
}
}
}
I'm quite new to redux and react, so I'm a bit unsure if I'm heading in the right direction, any help would be appreciated.
To implement a proper redux call back and storage mechanism you should have a store to keep all your data,
const store = createStore(todos, ['Use Redux'])
then, you dispatch data to store,
store.dispatch({
type: 'FETCH_FAILED',
text: reposnse.status //Here you should give the failed response from api
});
Then you can get the value from the store in any of your components using a subscribe function. It will be called any time an action is dispatched, and some part of the state tree may potentially have changed.
store.subscribe(()=>{
store.getState().some.deep.property
})
This is a simple implementation of Redux. As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state using combineReducers. You can get more information from redux.js site
The most common approach is to use connect function from react-redux library. This is a HoC which subscribes to state changes. Take a look at this library, additionally it allows you to bind your action creators to dispatch, what gives you an ability to dispatch your actions from component.
You can use it like this:
import React from 'react';
import { connect } from 'react-redux';
const MyComponent = ({ data, error }) => (
<div>
{error && (
<span>Error occured: {error}</span>
)}
{!error && (
<pre>{JSON.stringify(data, null, 2)}</pre>
)}
</div>
);
const mapStateToProps = (state) => ({
data: state.appReducer.data,
error: state.appReducer.error
});
export default connect(mapStateToProps)(MyComponent);
You can use conditional rendering inside your jsx as I've shown above, or use guard clause, like this:
const MyComponent = ({ data, error }) => {
if (error) {
return (
<span>Error occured: {error}</span>
);
}
return (
<pre>
{JSON.stringify(data, null, 2)}
</pre>
);
}
Assuming reducers,
for FETCH_FAILED action,you can put some meaningful flag indicating
there are some failure.Based on that flag you can show error messages or do other action.
const testReducers =(state,actione)=>{
case 'FETCH_FAILED' : {
return {
...state,{ error_in_response : true }
}
};
default : return state;
}
In your container,you can get that flag and passed it to your component.
Assuming combineReducers used to combine reducers;
const mapStateToProps=(state)=>{
return {
error_in_response : state.testReducers.error_in_response
}
}
connect(mapStateToProps)(yourComponent)
In your component, this can be accessed using this.props.error_in_response

Use getState to access key in redux state for API call

I'm a little new to using thunk getState I have been even trying to console.log the method and get nothing. In state I see that loginReducer has they key property which I need to make API calls. status(pin): true
key(pin): "Ls1d0QUIM-r6q1Nb1UsYvSzRoaOrABDdWojgZnDaQyM"
Here I have a service:
import axios from 'axios'
import {thunk, getState} from 'redux-thunk'
import MapConfig from '../components/map/map-config'
const origin = 'https://us.k.com/'
class KService {
getNorthAmericaTimes() {
return (dispatch, getState) => {
const key = getState().key
console.log('This is time key,', key)
if (key) {
dispatch(axios.get(`${origin}k51/api/datasets/k51_northamerica?key=${key}`))
}
}
// const url = `${origin}k51/api/datasets/k51_northamerica?key=${urlKey}`
// return axios.get(url)
}
}
export default new K51Service()
However in my corresponding action I get that Uncaught TypeError: _kService2.default.getNorthAmericaTimes(...).then is not a function
This is what the action function looks like :
export function getKNorthAmericaTime(dispatch) {
KService.getNorthAmericaTimes().then((response) => {
const northAmericaTimes = response.data[0]
dispatch({
type: ActionTypes.SET_NORTH_AMERICA_TIMES,
northAmericaTimes
})
})
}
I'm assuming it probably has to do with the if block not getting executed.
You should move your axios.get() method to your action creator and pass the promise to redux thunk, then when the promise is resolved dispatch the action with the response data so it can be processed by the reducer into the app's state.
actions
import axios from "axios";
export function fetchData() {
return (dispatch, getState) => {
const key = getState().key;
const request = axios.get();// use your request code here
request.then(({ response}) => {
const northAmericaTimes = response.data[0]
dispatch({ type: ActionTypes.SET_NORTH_AMERICA_TIMES, payload: northAmericaTimes});
});
};
}
Here's a very simple example of using axios with redux-thunk:
https://codesandbox.io/s/z9P0mwny
EDIT
Sorry, I totally forgot that you need to go to the state before making the request.
As you can see go to the state in your function, get the key from it, make the request and when the promise is resolved, dispatch the action with the response data. I've updated the live sample so you can see it working.
Again sorry...

mapStateToProps does not set the data in component props

I'm just getting started with React. I successfully used axios to get data from http and use an action to push the data. I can output the data at mapStateToProps but it does not set the data as a prop in the class. Here's my code with comments about the availability of the data.
import React from 'react';
import { connect } from 'react-redux';
import { fetchCountries } from '../../actions/actions';
import _ from 'lodash';
class TheClass extends React.Component
{
constructor(props)
{
super(props);
}
componentDidMount()
{
this.props.fetchCountries();
console.log('Fetching', this.props.countries); // !! UNDEFINED !!
}
}
function mapStateToProps(state)
{
console.log('Countries:', state.countries) // -> I get the data
return { countries: state.countries }
}
export default connect(mapStateToProps, { fetchCountries })(TheClass);
actions.js
import axios from 'axios';
export const FETCH_COUNTRIES = `fetch_countries`;
const COUNTRIES_URL = `http://api.stagingapp.io/location/v1/public/country`;
export function fetchCountries()
{
const request = axios.get(COUNTRIES_URL);
console.log(request); // -> I get the data
return {
type: FETCH_COUNTRIES,
payload: request
}
}
fetchCountries is an asynchronous operation so you can't expect the result just after calling fetchCountries as you are trying to do in componentDidMount.
If you are getting the result in connect function, then you will get the result in render function after successful network call.
Put your console here:
render() {
console.log('Fetching', this.props.countries);
}
I'd imagine that state.countries gets populated by whatever response you get from your asynchronous HTTP request in fetchCountries().
Only once this request resolves, should you get the country data. When you call fetchCountries() and immediately afterwards try to print out the value of countries, the request has not yet resolved (gotten a response), which is why you wont get any data.
Your fetch countries request in Asynchronous request, so you can't expect countries to be in store just after calling the fetchCountries() function. You will get countries data when react will re render on arrival of countries data from api.
Your function getCountries return an object with payload = a Promise return by axios, so you don't have your data when you call the function.
To make Async request you should add redux-thunk middleware, after that in your component file create a function
const mapStateToProps = (dispatch) => ({
fetchCountries: bindActionsCreator(fetchCountries, dispatch)
})
and pass this function in 2nd argument to your connect function.
In your actions.js change your function getCountries like so:
export const fetchCountries = () => (dispatch) => {
dispatch({type: FETCH_START})
axios.get(COUNTRIES_URL)
.then(response => response.data)
.then(data => dispatch({type: FETCH_COUNTRIES, payload: data})
.catch(errors => dispatch({type: FETCH_ERRORS})
}
With that, in your reducer you can set a variable loading to true when request start and pass this variable to false when Promise is resolved/rejected and after that you can create a condition to your component to be sure you have your data!

redirect from component level after specific dispatch - redux thunk

I have a fairly simple use case, but having a hard to find the appropriate answer. I'm using React,Redux,React Router & redux thunk middleware.
Lets say, I have two module food-tags & food. These modules have individual create,list,edit page/component. In practical use case, food-tags have no special value. Whenever a food object is created, separated tags are inserted into the food object's tags property.
General use case is that, after any item is created successfully, react router redirects it to the list page.
whenever i'm calling the createTag action from food-tag module, I can do it in a hacky way. like just after the success dispatch, i can call
browserHistory.push('/dashboard/tags')
this leads me to a problem where i can create food-tag inline from the food create component. Codes are given below
actions.js
export function createTag(tag) {
return function (dispatch) {
axios.post(API_URL + 'api/tags', tag)
.then((response) => {
// I CAN DO REDIRECT HERE,BUT THIS CAUSES THE PROBLEM
dispatch({type: 'TAG_CREATE_RESOLVED', payload:response});
toastr.success('Tag created Successfully.......!');
})
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err});
toastr.warning(err.message);
})
}
}
component/container.js
createTag () {
//validatation & others....
this.props.createTag(tag)
}
react-redux connection
function mapDispatchToProps (dispatch) {
return bindActionCreators({
createTag: createTag
}, dispatch)
}
Almost same pattern in food/create.js
$('#food-tags').select2(select2settings).on('select2:selecting', function (event) {
let isNewTagCreated = event.params.args.data.newOption,
name = event.params.args.data.text;
if (isNewTagCreated && name !== '') {
reactDOM.props.createTag({name}); // reactDOM = this context here
}
});
What I want basically that, I want to get access in the component level which action type is dispatching so that i can redirect from component & show notifications as well instead of action thunk. May be i'm not thinking in the proper way. there could be a dead simple work around.
It's good to know that redux-thunk passed out return value from the function. So you can return the promise from the action creator and wait until it will be finished in you component code
export function createTag(tag) {
return function (dispatch) {
return axios.post(API_URL + 'api/tags', tag) // return value is important here
.then((response) => dispatch({type: 'TAG_CREATE_RESOLVED', payload:response}))
.catch((err) => {
dispatch({type: 'TAG_CREATE_REJECTED', payload: err})
throw err; // you need to throw again to make it possible add more error handlers in component
})
}
}
Then in your component code
createTag () {
this.props.createTag(tag)
.then(() => {
toastr.success('Tag created Successfully.......!');
this.props.router.push() // I assume that you have wrapped into `withRouter`
})
.catch(err => {
toastr.warning(err.message);
});
}
Now you have proper split up between action logic and user interface.

Resources