Navigate to other route if Redux action is finished (or failed) - reactjs

I've got more or less the same question as this one: React-Router: how to wait for an async action before route transition
Current situation:
When I'm for example on my Home page, I click a post (e.g. /news/news-article). I immidiately navigate to the route, and I'm showing a Loader untill my Redux action has Fetched the posts content, and then I load the content. (This is also what 'jmancherje' answerred in the other question)
But this means that I have to show a loader on EVERY page my user visits, and this is not what I want.
So what I want:
When I'm on the Home, and I click to another post, I want to wait with navigation to the next route, untill my action has finished (or failed) loading the content.
This seems possible with React-Router-Redux, but I can't find out how to achieve this.
Updated question:
My action looks like this:
export function fetchPage(post_type, postSlug) {
return function (dispatch) {
dispatch({type: FETCH_PAGE_START});
axios.get(`${url}/wp-json/wp/v2/${post_type}?slug=${postSlug}`)
.then(response => {
dispatch({
type: FETCH_PAGE,
payload: response.data
});
dispatch(push('/')) // do the routing here
})
.catch(function (error) {
dispatch({
type: FAILED_PAGE
});
});
}
}
My store looks more or less like this:
const appliedMiddleware = applyMiddleware( thunk, createLogger(), routerMiddleware(history));
export default createStore(combinedReducers, appliedMiddleware);
So I think i'm on the right way, but still I can't get it to work. It still navigates immediately instead of a delay.

This is pretty much covered in details here regarding react-redux async action. But in short, one of the way is you can use redux-thunk middleware as such:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
Once you have redux-thunk middleware, in your action_post.js you can do something like this:
export function fetchPost(id) {
return dispatch => {
dispatch(fetchPostRequest())
axios.get(`/api/post/${id}`)
.then(res => {
dispatch(fetchPostSuccess(res.data))
dispatch(push('/postView')) // do the routing here
})
.catch(err => {
dispatch(fetchPostFailure(err))
})
}
}
function fetchPostRequest() {
return {
type: "FETCH_POST_REQUEST"
}
}
function fetchPostSuccess(data) {
return {
type: "FETCH_POST_SUCCESS",
data
}
}
function fetchPostFailure(err) {
return {
type: "FETCH_POST_FAILURE",
err
}
}

Related

How to extract data from Axios get request?

I have two files. One is "actions.js" and the other one is "Profile.js". The first file has a function that calls an API that will get information about an user based on his/her id.
The code for "actions.js" is:
import axios from "axios";
export const getPerson = (id, history) => async dispatch => {
try {
const res = await axios.get(`http://localhost:6969/users/getUsers/${id}`);
const { email } = res.data;
console.log(email);
dispatch({
type: GET_PERSON,
payload: res.data,
});
} catch (error) {
history.push("/profile");
}
};
The code for my "Profile.js" page is:
import React, { Component } from 'react'
import { getPerson } from '../actions/personActions';
import * as PropTypes from 'prop-types'
import { connect } from "react-redux";
render() {
this.props.getPerson(id, this.props.history);
----------- Followed by render method
}
Profile.propTypes = {
getPerson: PropTypes.func.isRequired
};
const mapStateToProps = state => ({
errors: state.errors
});
export default connect(
mapStateToProps,
{ getPerson }
)(Profile);
Problem is that I cannot show those responses in my Profile page by even localstorage if I decide to put the respective value in it. Tried with variety of ways but it shows up in the action.js page if I see from console using inspect log however it shows undefined in the Profile.js page. Please tell me where did I made it wrong. Thanks.
So, I think you're issue is that you're not dispatching anything in catch here:
} catch (error) {
dispatch({
// or something like this
type: GET_PERSON,
payload: error,
});
history.push("/profile");
}
Because of this, state is not updated with new error and state.errors is probably null or undefined.
Also, If you're trying to render anything other than the error from the state, don't forget to add it to mapStateToProps.
Other than that, I would recommend against calling api function in render. That's a really bad idea, that's because you might get a lot of useless api calls or even a loop of renders. Either call it in componentDidMount or use useEffect hook.
Generally I would advise that you go through couple of more react/redux tutorials.

How to warn react component when redux saga put a success action?

I am using the redux action pattern (REQUEST, SUCCESS, FAILURE) along with redux saga. I made a watcher and worker saga just like that:
import axios from 'axios';
import { put, call, takeEvery } from 'redux-saga/effects';
import * as actionTypes from 'constants/actionTypes';
import * as actions from 'actions/candidates';
const { REQUEST } = actionTypes;
// each entity defines 3 creators { request, success, failure }
const { fetchCandidatesActionCreators, addCandidateActionCreators } = actions;
const getList = () => axios.get('/api/v1/candidate/');
// Watcher saga that spawns new tasks
function* watchRequestCandidates() {
yield takeEvery(actionTypes.CANDIDATES[REQUEST], fetchCandidatesAsync);
}
// Worker saga that performs the task
function* fetchCandidatesAsync() {
try {
const { data } = yield call(getList);
yield put(fetchCandidatesActionCreators.success(data.data));
} catch (error) {
yield put(fetchCandidatesActionCreators.failure(error));
}
}
const postCandidate = params => axios.post('/api/v1/candidate/', params).then(response => response.data).catch(error => { throw error.response || error.request || error; });
// Watcher saga that spawns new tasks
function* watchAddCandidate() {
yield takeEvery(actionTypes.ADD_CANDIDATE[REQUEST], AddCandidateAsync);
}
// Worker saga that performs the task
function* AddCandidateAsync({ payload }) {
try {
const result = yield call(postCandidate, payload);
yield put(addCandidateActionCreators.success(result.data));
} catch (error) {
yield put(addCandidateActionCreators.failure(error));
}
}
export default {
watchRequestCandidates,
fetchCandidatesAsync,
watchAddCandidate,
AddCandidateAsync,
};
My reducer has two flags: isLoading and success. Both flags change based on the request, success and failure actions.
The problem is that I want my component to render different things when the success action is put on the redux state. I want to warn the component every time a _success action happens!
The flags that I have work well on the first time, but then I want them to reset when the component mounts or a user clicks a button because my component is a form, and I want the user to post many forms to the server.
What is the best practice for that?
The only thing I could think of was to create a _RESET action that would be called when the user clicks the button to fill up other form and when the component mounts, but I don't know if this is a good practice.
You need to assign a higher order component, also called a Container, that connects the store with your component. When usgin a selector, your component will automatically update if that part of the state changes and passes that part of the state as a prop to your component. (as defined in dspatchstateToProps)
Down below i have a Exmaple component that select status from the redux state, and passes it as prop for Exmaple.
in example i can render different div elements with text based on the status shown in my store.
Good luck!
import { connect } from 'react-redux'
const ExampleComponent = ({ status }) => {
return (
<div>
{status === 'SUCCESS' ? (<div>yaay</div>) : (<div>oh no...</div>)}
</div>
)
}
const mapStateToProps = state => {
return {
status: state.status
}
}
const mapDispatchToProps = dispatch => {
return {}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ExampleComponent)

Best Way to load model into redux store on route load

I have a React app that uses React-Router/React-Router-dom for page navigation and redux to store some global state info (jwt token for django rest framework for example). The state also stores info about the currently viewed page, such as the serialized django model.
But what is the best way to load the django model into the redux store when the route changes? I'm having trouble wrapping my head around where logic should be going.
If you view the repo below you can see where I'm having trouble figuring it out.
In this example when someone navigates to /spells/:id, it should load the spell django model into the redux store so information about it is globally accessible.
But how do I go about doing that? Where do I call the actions and reducers to properly handle the state?
Any guidance would be appreciated.
You can view the full project here. The component in question here is LayoutSpellView (/frontend/src/components/LayoutSpellView). That's where the model information is stored, displayed, etc.
Edit: Adding relevant code
Called in componentDidMount:
axios
.get("http://localhost:3000/api/spells/" + spellId)
.then(response => {
let spell = Object.assign({}, spellView.state.spell);
spell.id = response.data.id;
spell.owner = response.data.owner;
...blahblah other fields
this.setState({
spell
});
})
.then(response => {
this.props.dispatch({
type: 'FETCH_SPELL_SUCCESS',
payload: this.state.spell,
});
})
.catch(function(error) {
console.error('[API]\t', error);
});
In LayoutSpellView (same component as above)
import {loadSpell} from "../src/reducers";
const mapStateToProps = (state) => ({
spell: loadSpell(state.spell.id),
});
const mapDispatchToProps = (dispatch) => ({
getSpell: (state.spell.id) => {
dispatch(loadSpell(state.spell.id))
}
});
Actions spell.js:
export const FETCH_SPELL = '##spell/FETCH_SPELL';
export const FETCH_SPELL_SUCCESS = '##spell/FETCH_SPELL_SUCCESS';
export const FETCH_SPELL_FAILURE = '##spell/FETCH_SPELL_FAILURE';
export const loadSpell = (spellId) => ({
[RSAA]: {
endpoint: '/api/spell/${spellId}',
method: 'GET',
types: [
FETCH_SPELL, FETCH_SPELL_SUCCESS, FETCH_SPELL_FAILURE
]
}
});
Reducers spell.js:
const initialState = {
spell: {
id: 0,
owner: 0,
Name: 'Name',
School: 'unknown',
Subschool: 'unknown',
}
};
export default (state=initialState, action) => {
switch(action.type) {
case spell_action.FETCH_SPELL_SUCCESS:
return {
spell: {
id: action.payload.spell.id,
owner: action.payload.spell.owner,
Name: action.payload.spell.Name,
School: action.payload.spell.School,
Subschool: action.payload.spell.Subschool,
}
};
default:
return state;
}
}
export function loadSpell(state) {
if (state) {
return state.spell
}
}
Let's look at the question in a different way. Instead of asking "How do I dispatch an action when routes change", let's ask "What is the actual source of truth: Redux or URL?"
If we go with redux being the Single Source of Truth, then that would mean that we need to dispatch some action that would cause some side-effect ( maybe redux-saga or redux-observable or even redux-thunk? ) that changed the url:
Comp -> dispatch(action) -> store updates -> URL changes
If we go with the URL being the Single Source of Truth, we change the flow to:
URL changes -> dispatch(action) -> store updates
If we go this route, which is what it sounds like you are wanting, you will need to probably hook up middleware, which are functions of the following signature:
store => next => action => next(action)
Depending on the router that you are using, you can either hook into their actions or you can hook into window.onpopstate and check the next url. Either way, the overall middleware function would look something like
const middleware = store => {
return next => action => {
if (actionWillCauseSpellToBeNeeded(action)) {
makeAPICall()
.then(transformAPIToAction)
.catch(transformError)
.then(store.dispatch)
}
return next(action)
}
}

right way to POST data to a server and handle response with redux

I'm very new to react and redux.
Now I want to rewrite my post request with a redux process.
my current request looks like this:
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
this.setState({
shortenInfos: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
now I created a store:
export default function url(state = 0, action) {
switch (action.type) {
case 'CREATE_URL':
// maybe axios request?!
return `${action.url}/test`
case 'CREATED_URL':
return `${action.url}/created`
default:
return state
}
}
so where I must use my store.dispatch()? Should I make my _handleSubmit something like this?
_handleSubmit(event) {
axios
.post('/createUrl', {
url: this.state.url
})
.then((response) => {
store.dispatch({
type: 'CREATED_URL',
url: response.data
})
})
.catch((error) => {
console.log(error);
});
event.preventDefault()
}
I think this is wrong? And where I must use mapStateToProps method? Or should I do the axios-request in my CREATE_URL in my reducer?
Introduction
Using React with Redux gives you high freedom on how you can do things. The downside of this is that it can be hard to find out how things should be done properly, mainly because there is no standard or comprehensive guide to the use of the many dependency you need for a properly implemented project. This answer will guide you through the basics with links to references that will help you to find out wheres next and how to deeper your knowledge.
Reducer
Reducers should be pure, meaning that they have no side effects (like making axios requests) and they should always return a new object/array/value instead of changing the previous state. It is also a good practice to use action types as constants. You can place action types wherever you want, but for simplicity I will put them into the reducer's file, but there are better ways to organize them like using ducks.
export const CREATED_URL = 'CREATE_URL';
export default const url = (state = '', action) => {
switch (action.type) {
case CREATED_URL:
return action.url;
default:
return state;
}
};
Asynchronous actions
Everything that causes a side effect should be an action, so XHR should happen there. Because XHR should be asynchronous it is recommended to use a middleware: redux-thunk and redux-saga are two popular solutions. I will go with thunk so install it first.
First (because const has temporal dead zone) you need an action that will "load" the result of the XHR to the store:
import { CREATED_URL } from './reducer';
const createdUrl = url => ({
type: CREATED_URL,
url, // ES6 trailing comma for prettier git diffs
});
Then you can create the action that will fire the XHR, wait for the response then load it to the store using the action created previously. We need to return a function that will receive dispatch as the parameter. This technique is used in functional programming and is called currying.
export const createUrl = url => dispatch => { // with only 1 parameter the parentheses can be omited
axios
.post('/createUrl', { url }) // ES6 Shorthand property name in { url }
.then(response => {
dispatch(createdUrl({
url: response.data,
})
})
.catch(error => {
// #TODO dispatch an action that will show a message
// notifying the user that the request failed
console.log(error);
});
}
Usage in the React component.
Preparation
For ease of use, you need to connect your React component with Redux. react-redux comes to the rescue. Read the API documentation and add the <Provider> component to the root of your React component tree.
Now, in the top of your React component's file, import all the necessary stuff:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { createUrl } from './reducer';
mapStateToProps and mapDispatchToProps
Then create the two helper functions for connect:
const mapStateToProps = store => ({ url: store.url })
const mapDispatchToProps = dispatch => bindActionCreators({ createUrl }, dispatch)
With the help of mapStateToProps you can subscribe to store updates and inject the important parts of the Redux store to your components props. mapStateToProps should return an object that will be merged to the component's props. Usually we just do something like store => store.url but because our example is so simple that the reducer returns a plain string instead of something more complex in an object, we need to wrap that string into an object over here.
mapDispatchToProps with the help of bindActionCreators will inject the passed actions to the component's props so we can call and pass them down to subcomponents with ease: this.props.createUrl().
The component itself
Now we can create the component itself. I will use an ES6 class to show an example with componentDidMount, but if you don't need that and you have a stateless component, you can go with a function too.
class Example extends React.Component {
componentDidMount() {
// If you need to load data from a remote endpoint place the action call here, like so:
// this.props.createUrl('your-url');
}
render() {
return (
<div>
<div>URL injected from the store, automatically refreshed on change: {this.props.url}</div>
<div onClick={event => {this.props.createUrl('your-url');}}>Click me to fetch URL</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Example)

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...

Resources