I'm working on an app using Next.js with redux by following this example and here is some part of store.js
// REDUCERS
const authReducer = (state = null, action) => {
switch (action.type){
case actionTypes.FETCH_USER:
return action.payload || false;
default:
return state;
}
}
export const rootReducer = combineReducers({
user: authReducer,
form: reduxForm,
});
// ACTIONS
export const fetchUser = () => {
return (dispatch) => {
axios.get('/api/current_user')
.then(res => dispatch({
type: actionTypes.FETCH_USER,
payload: res.data
}));
};
};
export const submitLogin = (values) => async dispacth => {
const res = await axios.post('/api/login', values);
// Router.push('/');
// console.log(res)
dispacth({ type: actionTypes.SUBMIT_LOGIN, payload: res.data });
};
and the client side such as header
function mapStateToProps (state) {
const { user } = state
return { user }
}
export default connect(mapStateToProps)(Header)
and when I console.log('############=>', this.props.user); the props & I'm not loggesd in then it's showing null but showing some extra data such as below screenshot
but when I logged in & console.log('############=>', this.props.user); it's showing proper data without extra data such as below image
what I'm doing wrong? please help me. Thanks
The problem is probably not on your React / Redux code but on your Next.js routes.
You’re trying to call an API with axios.get('/api/current_user') but Next.js is giving you an HTML response, that you indeed store in authReducer extracting it as action.payload.
You probably want to see this section about Custom Server and Routing.
dispacth({ type: actionTypes.SUBMIT_LOGIN, payload: res.data });
Should be:
dispatch({ type: actionTypes.SUBMIT_LOGIN, payload: res.data });
#MunimMunna is spot on. Your server is either redirecting you to an HTML login page, or returning an HTML error page for failed creds. In either case, Axios is seeing a 200 status code, so it thinks the response is valid. Your action creator blindly fires off the action with the HTML payload attached.
Consider making these changes:
Client:
Add a catch block to your axios promise that logs failed response.
Pass an Accept header of application/json to tell the server you don't want HTML responses. If you are lucky, this might be enough to get NextJS to behave the way you want.
Server: If needed, change the server to detect whether the request is an XHR request, or if application/json is the only response type the client wants. Don't redirect if those conditions are true. Return return a 401 status code instead. You can optionally return a JSON body with some extra error information.
Related
So I'm making a simple CRUD app trying to familiarize myself with redux. Basically I'm transferring data from a form and sending a post request to the server to create a new blog post (more or less). Here's the code for the onSubmit:
const handleSubmit = (e) => {
e.preventDefault()
dispatch(createPost(postData));
}
Here's the action being dispatched and where the error is coming from (src/actions/posts.js):
export const createPost = (post) => async (dispatch) => {
try
{
const { data } = await api.createPost(post);
dispatch({ type: "CREATE", payload: data});
}
catch (error)
{
console.log(error.message);
}
}
This function isn't able to destructure data from the api's response, because the response is undefined. Here's the code that sends the request from src/api/index.js:
export const createPost = (newPost) => {
axios.post(url, newPost);
}
When I console.log the response from this function using .then(), it returns a 201 along with exactly what one would expect from a successful request. So the response returns 201 and the entry gets added to the DB but my action function cannot destructure the data from the response because it's undefined. What is my stupid mistake?
You aren't returning a value from the function. Try
export const createPost = (newPost) => {
return axios.post(url, newPost);
}
I'm having troubled sending an authenticated request to my API immediately after signing in to my Nextjs app using NextAuth. The request that is sent after signing in returns data for and unauthenticated user.
I believe the issue is that React Query is using a previous version of the query function with an undefined jwt (which means its unauthenticated). It makes sense because the query key is not changing so React Query does not think it's a new query, but, I was under the impression that signing in would cause loading to be set to true temporarily then back to false, which would cause React Query to send a fresh request.
I've tried invalidating all the queries in the app using queryClient, but that did not work. I've also used React Query Devtools to invalidate this specific query after signing in but it still returns the unauthenticated request. Only after refreshing the page does it actually send the authenticated request.
// useGetHome.js
const useGetHome = () => {
const [session, loading] = useSession();
console.log(`session?.jwt: ${session?.jwt}`);
return useQuery(
'home',
() => fetcher(`/home`, session?.jwt),
{
enabled: !loading,
},
);
}
// fetcher
const fetcher = (url, token) => {
console.log(`token: ${token}`);
let opts = {};
if (token) {
opts = {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${url}`, opts);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Home.js
const Home = () => {
const { data: home_data, isLoading, error } = useGetHome();
...
return(
...
)
}
Attached is the console immediately after signing in. You can see the the session object contains the jwt after signing in, but in the fetcher function it is undefined.
console after signing in
Any help here is appreciated. Is there a better way to handle authenticated requests using React Query and NextAuth? Thank you!
I have tried a similar situation here and struggled the same thing but the enabled property worked fine for me and it is good to go right now.
https://github.com/maxtsh/music
Just check my repo to see how it works, that might help.
So I use redux-hooks for state management, so in my application there is a dropdown, the value will be used to call api dynamically, in the action creator I made it like this
export const getFetchCasesSelectedCountry = (country) => (dispatch) => {
return (
axios.get(`https://covid19.mathdro.id/api/countries/${country}`).then((result) => {
let confirmed = result.data.confirmed.value;
let recovered = result.data.recovered.value;
let death = result.data.deaths.value;
dispatch({
type: GET_SELECTED_COUNTRY,
payload: {
countryConfirmed: confirmed,
countryRecovered: recovered,
countryDeath: death,
}
})
})
)
}
but i got this
error
how to get the value from the dropdown so that it can be entered into the action creator? is it possible? sorry if my question is hard to understand.
There can be various reasons for 404 issue:
it can be networking issue - I mean that requested URL is not accessible from your environment. I see from the code that you doing GET request so to test networking you can just open your browser with the URL that is being used in the action. https://covid19.mathdro.id/api/countries/USA for example.
Code that calls getFetchCasesSelectedCountry provides some weird country value that can result in a 404 error
Nevertheless the code that you've posted, does not handle errors that can arise from axios call (404 for example) so your store won't be aware of errors that can happen, hence component that is connected to the store also won't be aware of such problems.
From my experience, usual approach to handle such things with redux is to introduce more states, that will store error info:
// this is purely an example
// let say that you have such state
const state = {
loading: false,
hasError: false,
errorMessage: '',
data: null,
}
// reducer
function stateReducer(state, action) {
switch(action.type) {
case GET_SELECTED_COUNTRY_LOAD:
// set here 'loading' to true - started request execution
// return new state
case GET_SELECTED_COUNTRY_LOADED:
// set here 'loading' to false - got response
// set here data that came with action payload
// return new state
case GET_SELECTED_COUNTRY_FAILED:
// set here 'loading' to false - got error response or failed
// sethere 'errorMessage' that came with action payload
// return new state
}
}
// you'll have to create 2 new action creators to handle GET_SELECTED_COUNTRY_LOAD // and GET_SELECTED_COUNTRY_FAILED
// now your code should look like this
const getFetchCasesSelectedCountry = (country) => (dispatch) => {
return (
dispatch({ type: GET_SELECTED_COUNTRY_LOAD });
axios.get(`https://covid19.mathdro.id/api/countries/${country}`)
.then((result) => {
// do some stuff with result
dispatch({
type: GET_SELECTED_COUNTRY_LOADED,
payload: { /* useful data here */ }
});
).catch(err => {
dispatch({
type: GET_SELECTED_COUNTRY_FAILED,
payload: { /* useful error data here */ }
});
})
}
So whenever error happens component that is connected to store will be able to handle it (at least show errorMessage that is can get from the store)
The HTTP error 404, or more commonly called "404 error", means that the api you are trying to use could not be found on the server. This is a client-side incident which means either the endpoint has been deleted or moved, and the URL has not been modified accordingly, or that you have misspelled the URL.
read this for more information
Hello I'am completly new with React/Redux so there is a possibility that I violated some principles with the below code , so bare with me.
I'm building a React App which will consume my Express API. Everything is working perfectly but when I was building the Action Creators I couldnt think of a good way to handle any errors coming from the API without wrapping every single axios request with try/catch blocks.
Both in PHP world where I come from and Express you can create a global Error handler.
For any async requests in my Express APP I wrap them with the below function so I can catch them the same way as the synchronous.
module.exports = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch((err) => next(err));
};
};
From what I've learned through googling is that, there is an ErrorBoundary HOC for handling errors inside Components and for axios calls I should use axios interceptors. So I created this:
AxiosFactory Class
import axios from "axios";
import { setError } from "../actions/utilActions";
import store from "../store";
class AxiosFactory {
constructor(baseURL) {
this.instance = axios.create({
baseURL,
});
this.instance.interceptors.response.use(
function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
// Getting the errors from Express API
const {
response: {
data: { errors },
},
} = error;
store.dispatch(setError(errors));
return Promise.reject(error);
}
);
}
getInstance() {
return this.instance;
}
}
export default AxiosFactory;
User API Caller
import AxiosFactory from './AxiosFactory';
const axios = new AxiosFactory('/api/v1/users/').getInstance();
export default axios;
User ActionCreator
import { SUCCESS_LOGIN } from "./types/userTypes";
import userApi from "../apis/user";
// Tries to login the user
export const signInUser = () => {
return async (dispatch) => {
// Test
const {data:{data:{user} = await userApi.post("login", {
email: "test#test.com",
password: "test12345!",
});
dispatch({
type: SUCCESS_LOGIN,
payload: user,
});
}
Error ActionCreator
import { HAS_ERROR } from "./types/utilTypes";
export const setError = (errors) => {
return async (dispatch) => {
dispatch({
type: HAS_ERROR,
payload: errors,
});
};
};
The interceptor dispatches succesfuly the setError and the error state is getting updated like a charm, which means I dont need to manual dispatch on each call. Although I still need to catch the Promise rejection from Interceptor.
My 2 questions are:
Is there a way to lets say "stop the dispatch from executing" inside my User ActionCreator without try/catching the Promise ?
Does this whole thing I setup makes sense ? Or there is a better way to do it?
I am following a blog post, attempting to get a user login implemented in my app
https://auth0.com/blog/secure-your-react-and-redux-app-with-jwt-authentication/
It recommends doing the following in my action
// actions.js
export const LOGIN_REQUEST = 'LOGIN_REQUEST'
function requestLogin(creds) {
return {
type: LOGIN_REQUEST,
isFetching: true,
isAuthenticated: false,
creds
}
}
// .....
export function loginUser(creds) {
let config = {
method: 'POST',
headers: { 'Content-Type':'application/x-www-form-urlencoded' },
body: `username=${creds.username}&password=${creds.password}`
}
console.log(creds) // <-- this works
return dispatch => {
console.log(creds) // <-- this doesnt work and everything after it doesnt work
// We dispatch requestLogin to kickoff the call to the API
dispatch(requestLogin(creds))
return fetch('http://localhost:3001/sessions/create', config)
.then(response =>
response.json().then(user => ({ user, response }))
).then(({ user, response }) => {
if (!response.ok) {
// If there was a problem, we want to
// dispatch the error condition
dispatch(loginError(user.message))
return Promise.reject(user)
} else {
// If login was successful, set the token in local storage
localStorage.setItem('id_token', user.id_token)
localStorage.setItem('id_token', user.access_token)
// Dispatch the success action
dispatch(receiveLogin(user))
}
}).catch(err => console.log("Error: ", err))
}
}
However, as soon as the function hits the part return dispatch => { an error gets raised
--- Edit ---
the answers below fixed the console errors, but a new error was introduced
my redux chrome plugin no longer sees my redux store
It seems that you did not set up Redux Thunk which is a Redux library that allows you to have asynchronous actions.
In the tutorial you linked, it is explained how to set it up:
import thunkMiddleware from 'redux-thunk'
let createStoreWithMiddleware = applyMiddleware(thunkMiddleware, api)(createStore)
You should read more about Redux Thunk here.
What you are trying to do here is to utilise a thunk, which is a function that returns another function.
In redux that is used to cause side effects during action creation such as asynchronous calls as part of an action, and producing secondary actions, like dispatching the response from this asynchronous request.
If you look in the blog post package.json they have dependency on redux-thunk middleware package (https://www.npmjs.com/package/redux-thunk) you will need this too, and to attach it to your store...
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);