Show 404 after Redux ajax request - reactjs

When users navigate to /places/:slug in my React/Redux app, an ajax call is triggered to get the relavant place data from the database.
While this all works as expected I'm not sure how to show a 404 if no place was found. I have a 404 route setup for when users navigate to a non-route but how can I trigger a 404 using Redux?
In my container component I have:
this.props.dispatch(fetchPlace(this.props.match.params.slug))
and my action:
import axios from "axios";
export function fetchPlace(slug) {
return function(dispatch) {
dispatch({type: "FETCH_PLACE"});
axios.get("/server/places/" + slug)
.then((response) => {
dispatch({type: "FETCH_PLACE_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACE_REJECTED", payload: err})
})
}
}
and my reducer:
const reducer = (state={
place: {},
isFetching: false,
error: null
}, action) => {
switch(action.type) {
case "FETCH_PLACE" : {
return { ...state, isFetching: true }
}
case "FETCH_PLACE_REJECTED" : {
return { ...state, isFetching: false, error: action.payload }
}
case "FETCH_PLACE_FULFILLED" : {
return { ...state, isFetching: false, place: action.payload }
}
default:
return state
}
}
export default reducer
Ideas
I could use another state property in my reducer called notFound and initialize it to false. Then read the response data payload and detect whether a job has been returned. If not then set notFound to true. But how do I listen for notFound to be true and trigger a 404?

I would suggest returning a promise from fetchPlace action and catch it in container.
Action code which returns promise
export function fetchPlace(slug) {
return function(dispatch) {
dispatch({type: "FETCH_PLACE"});
return axios.get("/server/places/" + slug)
.then((response) => {
dispatch({type: "FETCH_PLACE_FULFILLED", payload: response.data})
})
.catch((err) => {
dispatch({type: "FETCH_PLACE_REJECTED", payload: err})
})
}
}
Container code (You must have access to react-router context or history object)
this.props.dispatch(fetchPlace(this.props.match.params.slug))
.then(() => {})
.catch(() => {
//Redirect to 404 page using history object from props
this.props.history.replace(`404`)
//Redirect to 404 page using context
this.context.router.history.replace(`404`)
})
Another approach would be checking notFound value in componentWillReceiveProps(nextProps) method.

Related

How to get data from an API using redux?

I am requesting an API using redux and saga
I the request is working fine I am able to get API returned data in the reducer, but I am not able to get that data in the home.js I am new to redux please help me.
in my home.js this is how I am making a call
const fetchSessionData = () => {
dispatch(
fetchSession({
token: token
})
)
}
action.js
export const fetchSession = data => {
return {
type: Action.SESSION_DATA,
payload: {data},
};
};
this is reducer file
export const SessionData = (state = sessionState, action) => {
console.log('inside session reducer', JSON.stringify(action));
switch (action.type) {
case Action.SESSION_FETCH_SUCCESS:
return {
...state,
isLoading: false,
};
case Action.SESSION_FETCH_ERROR:
return {
...state,
sagaerror: action.error,
isLoading: false,
};
case Action.SESSION_DATA:
return {
...state,
isLoading: true,
};
default:
return state;
}
};
and this is the api
export function fetchSessionData(payload) {
return fetch(`${url}/session/`, {
method: 'GET',
headers: {
Authorization: `Bearer ${payload.token}`,
Accept: 'application/json',
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(res => {
return res;
})
.catch(error => {
throw error;
});
}
how can I get the returning data from api in home.js?
Looks like you are not storing back the response from saga.
Please have an action for storing the response into your reducer.you may use
yield put(storeactionname(payload received from api reponse);
in your saga method and in reducer your action.payload should be stored into a state variable
and in your component you can use useSelector like below
const { yourvariableNameInStateInReducer } =
useSelector((state) => state.yourreducername);
As you say you will able to get data to reducer. It's then fine in that section. In the component you need to select stored data from the store. If you are using functional component you can use useSelector hook in react-redux. If you are using class component then you need to use connect function, you need to pass mapStateToProps and mapDispatchToProps arguments to the connect function. Refer https://react-redux.js.org/api/connect

Getting error in Axois in catch and after dispatch value didn't getting in props

I'm new in Redux React and creating web app where app interact with Lumen API framework. When a request go to server and return with error code 400, 404, 500 any status code (error) except 200 it shows console error and processed after that in React.
I tried pass value when get error at axois.catch({dispatch}) but value update in state by viewing redux-dev-tool but didn't get value at props.
As from API I passed as like:
if ($validator->fails()) {
return response()->json(['type'=> 'error','message'=> $validator->errors()->all()],400);
}
And in my action file as like:
export const loginRequest = formValues => async dispatch => {
await user.post('/user/create', { ...formValues, device_id: 12345}).then(response => {
dispatch ({type: LOGIN, payload: response.data});
}).catch(error => {
if(error.response) {
dispatch ({ type: ERRORS, payload: error.response.data.message });
}
});
}
and in reducer:
const INTIAL_STATE = {
isSignedIn: null,
accessToken: null,
loginMessage: null,
errorMessage: null
};
export default (state = INTIAL_STATE, action) => {
switch (action.type){
case ERRORS:
return { ...state, isSignedIn: false, accessToken:null , errorMessage: action.payload };
default:
return state;
}
};
as last in my component file:
const mapStateToProps = state => {
return {error: state.auth.errorMessage};
};
and console log in render method:
console.log(this.props);
I'm getting POST http://localhost:8000/user/create 500 (Internal Server Error) in my console but the errorMessage value updated in state as looked in redux-dev-tool and after error no code run.
Use the redux-saga-routines for dispatching actions, make your work easy with this module.
Here its documentation link https://github.com/afitiskin/redux-saga-routines

Redux Action not returning JSON?

I'm struggling to figure out why my redux action is not returning the JSON from the GET request, even though when I submit the GET request in Postman, I can access the information?
The error I have returning is: Profile Not Found. Yet, like I said when I do the Postman request, it's working fine.
This Redux Action doesn't work:
// Get profile by id for admins
export const getUserProfile = (id) => dispatch => {
dispatch(setProfileLoading());
axios.get(`/admin/profile/${id}`)
.then(res =>
dispatch({
type: GET_PROFILE,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Here is the Admin route which works in Postman and is returning the JSON?
router.get('/admin/profile/:id', passport.authenticate('jwt', {
session: false
}), (req, res) => {
const errors = {};
User.findOne({
user: req.user.id
})
.then(user => {
if (req.user.role === 'admin') {
Profile.findById(req.params.id)
.then(profile => {
res.json(profile);
})
.catch(err => res.status(404).json({
profilenotfound: 'Profile not found'
}));
} else {
res.json({unauthorized: 'User is unauthorized to view this area'})
}
})
.catch(err => res.status(404).json(err));
});
Here is the reducer:
import { GET_PROFILE, PROFILE_LOADING, CLEAR_CURRENT_PROFILE, GET_PROFILES }
from '../actions/types';
const initialState = {
profile: null,
profiles: null,
loading: false
}
export default function(state = initialState, action) {
switch(action.type) {
case PROFILE_LOADING:
return {
...state,
loading: true
}
case GET_PROFILE:
return {
...state,
profile: action.payload,
loading: false
}
case GET_PROFILES:
return {
...state,
profiles: action.payload,
loading: false
}
case CLEAR_CURRENT_PROFILE:
return {
...state,
profile: null
}
default:
return state;
}
}
As mentionned in the comments the problem is that you are not passing the id, you need to pass the id when you call your Redux action in your component for example if you call your getUserProfile method it should be something like that:
componentDidMount() {
const {getUserProfile} = this.props; // This is destructuring for better readability
// Here you need to pass your id for example 1234 or get it from params or from wherever you want...
getUserProfile(1234);
}

How to fix random http request (404 response) from Axios

The HTTP request works as expected but I see an additional request to /%3Canonymous%3E that returns 404. This is causing the following error in Redux:
Unhandled Rejection (TypeError): Cannot read property 'data' of
undefined
I don't see the 404 in requests to other routes in other components, for example, /api/users from the user component. I have changed the get requests and routes to match that of the user's but the problem still persists. I have tried the request in postman and it responds with the expected result. The additional request to /%3Canonymous%3E only happens when making get requests to the order resource in the browser (from the app).
GET request:
export const getOrders = () => dispatch => {
axios
.get("api/orders/")
.then(res =>
dispatch({
type: GET_ORDERS,
payload: res.data
})
)
.catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
};
Order route:
router.get(
"/",
(req, res) => {
Order.find()
.then(orders => res.json(orders))
.catch(err => {
res.json(err);
});
}
);
getOrder Reducer:
case GET_ORDERS:
return {
...state,
allOrders:
action.payload
};
Entire Order Reducer:
import {
GET_ORDERS,
ADD_ORDER,
EDIT_ORDER,
SET_EDITING_ORDER
} from "../actions/types";
const initialState = {
editingOrder: {},
allOrders: [],
editedOrder: {}
};
export default function(state = initialState, action) {
switch (action.type) {
case SET_EDITING_ORDER:
return {
...state,
editingOrder: action.payload
};
case EDIT_ORDER:
return {
...state,
editedOrder: action.payload
};
case GET_ORDERS:
return {
...state,
allOrders: action.payload
};
case ADD_ORDER:
// state.allOrders.push(action.payload);
return {
...state,
allOrders: [...state.allOrders, action.payload]
// newOrder: action.payload [don't need this
};
default:
return state;
}
}
The data is returned and populated in the redux state but that additional, random request is causing the problem.
it happened to me recently, the problem was a wrong structured response from the same request
This happened to me and I wasn't able to find a fix until now. Might be a more specific issue depending on what you're working on but for me I was loading a component that I had made a ProtectedRoute Component that was checking my isAuthenticated state for permission to render. It would load when I clicked a link to the component because the current isAuthenticated state was true. However, when I refreshed the page it would throw an error and I noticed in Redux DevTools that my isAuthenticated state refreshed and went from false to true.
My Solution: I used a ternary statement to render inside my component. it looked like this:
return !isAuthenticated ? (<p>Loading</p>) : (<Component />)
This recently happend to me and the solution was to use unique action type.
Try to replace the action type variable name along with it value.
In your above code -
Replace -
GET_ORDERS
ADD_ORDER
EDIT_ORDER
SET_EDITING_ORDER
With some some name which is only using in this api request only.
It will fix the issue.

Dispatch multiple actions using Redux Thunk and the await/async syntax to track loading

I am currently editing some reducers to be able to track the loading state of axios operations. Most of my async syntax is written in async/await fashion and would like to keep it that way to keep my code organized.
I am not sure how to dispatch two action creators one after the other: the first one to fire off the FETCHING_USER action type and keep track of the reduced isFetching state, while the other one to fire off the actual axios GET request. The code currently looks like this to get the API request:
export const fetchUser = () => async dispatch => {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: FETCH_USER, payload: res.data });
};
I am not sure how to dispatch the FETCHING_USER and then fire off the FETCH_USER action.
First you need to modify your reducer to have isFetching statement and requesting and receiving data cases:
const INITIAL_STATE = { isFetching: false, data: [] };
export default(state = INITIAL_STATE, action) => {
switch(action.type) {
case REQUEST_USER: {
return {...state, isFetching: true};
}
case RECEIVE_USER: {
return {...state, isFetching: false, data: action.payload};
}
default: return state;
}
}
Then modify your action to use try/catch statements:
export const fetchUser = () => async dispatch => {
dispatch({ type: REQUEST_USER });
try {
const res = await axios.get(`${API_URL}/api/current_user`, {
headers: { authorization: localStorage.getItem("token") }
});
dispatch({ type: RECEIVE_USER, payload: res.data });
}
catch(e){
//dispatch your error actions types, (notifications, etc...)
}
};
Then in component you can use condition like: isFetching ? //show loader : //show content (data[])

Resources