When I delete the order, sometimes the state is updated and sometimes not. It did not work 5 minutes and then started working correctly and I am wondering whether I've messed up something in my code.
action
export const deleteOrder = (token, id) => async dispatch => {
const headers = {
headers: {
Authorization: `Bearer ${token}`,
}
}
const response = await api.delete(`/orders/detail/${id}`, headers)
.catch(error => {
dispatch({ type: constants.PUSH_ERROR, payload: error })
})
if (response && (response.status === 204 || response.status === 301)) {
dispatch({ type: constants.DELETE_ORDER, payload: id });
}
};
reducer
case DELETE_ORDER:
return {
...state,
orders: state.orders.filter(o => o.id !== action.payload)
}
You can ask for more code.
Related
to everyone, i have a problem about a request to the server. I get error 400, but i don't understand where is the problem, i checked my code thousand times and it looks like all good.
Basically, with this request i get the "order number".
This is the reducer:
const initialState = {
status: "STALE",
};
export const orderObjectReducer = (state = initialState, action) => {
if (action.type === "SEND_ORDER_FAILED") {
return { status: "FAILED" };
}
if (action.type === "SEND_ORDER_IS_LOADING") {
return {
status: "LOADING",
};
}
if (action.type === "SEND_ORDER_STALE") {
return {
status: "STALE",
};
}
if (action.type === "SEND_ORDER_SUCCESS") {
return {
status: "SUCCESS",
data: action.payload,
};
}
return state;
};
export const makeOrder = (ingredientsIds) => async (dispatch) => {
dispatch({
type: "SEND_ORDER_IS_LOADING",
});
const result = await fetch("https://norma.nomoreparties.space/api/orders", {
method: "POST",
body: JSON.stringify({ ingredients: ingredientsIds }),
})
.then((res) => res.json())
.catch(() => null);
if (!result || !result.success) {
dispatch({
type: "SEND_ORDER_FAILED",
});
return result;
}
dispatch({
type: "SEND_ORDER_SUCCESS",
payload: result,
});
};
The button that trigger the request:
<Button
disabled={selectedIngredientsIds.length === 0}
htmlType="button"
onClick={() => dispatch(makeOrder(selectedIngredientsIds))}
type="primary"
size="large"
extraClass="mr-4" >
Оформить заказ
</Button>
This is the component that get the result of the request:
export const OrderDetails = () => {
const { status, data } = useAppSelector((store) => store.orderObject);
const dispatch = useAppDispatch();
if (status === "STALE" || status === "LOADING") {
return null;
}
return (
<Modal onClose={() => dispatch({ type: "SEND_ORDER_STALE" })}>
{status === "FAILED" ? ("Ошибка") : (
<>
<p className={`${orderDetailsStyle.orderDetails__orderNumber} text text_type_digits-large mt-30 mb-8`}>{data?.order.number}</p>
<p className="text text_type_main-medium mb-15">идентификатор заказа</p>
<img src={done} alt="заказано" className={orderDetailsStyle.orderDetails__image}/>
<p className="text text_type_main-default mb-2">Ваш заказ начали готовить</p>
<p className="text text_type_main-default text_color_inactive mb-30">Дождитесь готовности на орбитальной станции</p>
</>
)}
</Modal>
);
};
How can i solve the problem?
A 400 Bad Request simply means the request was bad. The server is saying something is wrong with what you sent it.
To test this outside of your app, in Firefox I use a free extension called RESTED Client to simply test a POST to your URL (https://norma.nomoreparties.space/api/orders) with the a pseudo body of { ingredients: ["1", "2"]}. It returns the following:
{
"success": false,
"message": "Ingredient ids must be provided"
}
The problem is with the data you are sending. The reason I know this is because I repeated exactly what your app is doing here:
const result = await fetch("https://norma.nomoreparties.space/api/orders", {
method: "POST",
body: JSON.stringify({ ingredients: ingredientsIds }),
})
the code is right, i needed just to add to my request:
headers: {
"Content-Type": "application/json",
},
I am using a json-server where I store my database in JSON objects. At some point, I have to update all the values in one of those objects, which is an array. The problem is that when I apply that function that updates the values, the server shuts down. I know that this is happening because I am interacting with the server in a for loop, and I suppose that this is not the best way to do that communication with the server. Here's the code:
The reducer:
import * as ActionTypes from './ActionTypes';
export const Inventario = (state = { errMess: null, inventario: []}, action) => {
switch (action.type) {
case ActionTypes.EDIT_INGREDIENTE:
return {...state, inventario: state.inventario.map((item) => item.id === action.payload.id ?
action.payload : item)};
default:
return state;
}
}
Action Creator:
export const editIngrediente = (ingrediente) => ({
type: ActionTypes.EDIT_INGREDIENTE,
payload: ingrediente
});
export const putIngrediente = (ingredienteId, nombreIngrediente, costo, disponible, conversiones) => (dispatch) => {
const newIngrediente = {
id: ingredienteId,
ingrediente: nombreIngrediente,
costo: costo,
disponible: disponible,
conversiones: conversiones
};
return fetch(baseUrl + 'inventario/' + ingredienteId, {
method: "PUT",
body: JSON.stringify(newIngrediente),
headers: {
"Content-Type": "application/json"
},
credentials: "same-origin"
})
.then(response => {
if (response.ok) {
return response;
} else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
throw error;
})
.then(response => response.json())
.then(response => {dispatch(editIngrediente(response)); console.log('editado con exito')})
.catch(error => { console.log('put ingrediente', error.message); alert('Your ingredient could not be posted\nError: '+error.message); });
};
And finally where I applied the code:
function restarIngrediente(ingrediente){
for (var elem of ingrediente){
var restar = elem.gramos;
var enInventario = inventario.filter((inven) => inven.ingrediente === elem.ingrediente)[0];
var restante = enInventario.disponible - restar;
putIngrediente(enInventario.id, enInventario.ingrediente, enInventario.costo, restante, enInventario.conversiones);
}
}
Another doubt that I have is that when I want to update the object, I have to pass to the fetch all the attributes of the object, and not only the one that I actually want to change.
If someone can help me, I'd really appreciate it.
I am recreating the issue as I am not sure if that's a bug and a previous issue created for this just disappeared =/
Description
During an upgrade from react-admin v2 to v3.3.0, I encounter issue to get redirected to /login after rewriting the authProvider
react-admin documentation | github
My authProvider.js contains the following methods:
export default function createAuthProvider(urls, client, options) {
return {
login: (params) => {
return fetchLoginBatch(params, urls, client, options);
},
logout: (params) => params.jwt ? fetchLogout(makeRequestLogout(urls.logout, params.jwt), options) : Promise.resolve(),
checkError: (error) => {
const { status } = error;
if (status === 401 || status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
checkAuth: (params) => params.jwt ? Promise.resolve() : Promise.reject(),
getPermissions: (params) => params.jwt ? Promise.resolve(params.jwt.authorities) : Promise.resolve(),
refreshJwt: (params) => params.jwt ? fetchRefreshJwt(makeRefreshJwtRequest(client.accessTokenUri, {
grant_type: 'refresh_token',
refresh_token: params.jwt.refresh_token,
}, client), options) : Promise.resolve()
};
}
It is as described in documentation and example
Expected
I expect to be redirected to /login.
Result
Instead, I stay on the page, checkAuth is well called and the jwt is null,
Possible fix
It is offered to change the redirect using an argument to rejected promise:
checkAuth: () => localStorage.getItem('token')
? Promise.resolve()
: Promise.reject({ redirectTo: '/no-access' }),
But adding Promise.reject({ redirectTo: '/login' }) doesn't help even if the code run.
I have tried to add some logging in ra-core/lib/sideEffect/auth.js:
console.log('auth');
exports.handleCheck = function (authProvider) {
console.log('handleCheck', authProvider);
return function (action) {
console.log('action', action);
var payload, meta, error_1, redirectTo, errorMessage;
console.log('redirectTo', redirectTo, 'meta', meta);
handleCheck is called but never action, it seems that the saga is ignored somehow.
Question
How should client side handle permissions and checkAuth rejection params ?
What should I check next to see why the redirection is failing?
How does the ra-core code follow?
Could you kindly scrutinize this line?
// authProvider.js
// checking if request has a JWT (this might be a typo!)
- checkAuth: (params) => params.jwt ? Promise.resolve() : Promise.resolve(),
// If not, we should reject thus triggering a redirect.
+ checkAuth: (params) => params.jwt ? Promise.resolve() : Promise.reject(),
Let me know how it works out.
Here is an example of authProvider, it works with both React-Admin 2 and React-Admin 3.3.1
Using:
<Admin authProvider={authProvider(loginUrl, logoutUrl)} ...>
import storage from './storage'
import {
AUTH_LOGIN,
AUTH_LOGOUT,
AUTH_ERROR,
AUTH_CHECK,
AUTH_GET_PERMISSIONS
} from 'react-admin'
import {
LB_TOKEN,
getTokenId
} from './token'
export const authProvider = (loginApiUrl, logoutApiUrl, noAccessPage = '/login') => {
return (type, params) => {
if (type === AUTH_LOGIN) {
const request = new Request(loginApiUrl, {
method: 'POST',
body: JSON.stringify(params),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText)
}
return response.json()
})
.then(({ ttl, ...data }) => {
storage.save(LB_TOKEN, data, ttl)
return Promise.resolve(data)
})
}
if (type === AUTH_LOGOUT) {
const token = getTokenId()
if (token) {
storage.remove(LB_TOKEN)
if (logoutApiUrl) {
const request = new Request(`${logoutApiUrl}?access_token=${token}`, {
method: 'POST',
headers: new Headers({'Content-Type': 'application/json'}),
})
return fetch(request)
.then(response => {
if (response.status !== 204) {
console.error('authProvider - Logout, status:', response)
}
return Promise.resolve()
})
}
}
return Promise.resolve()
}
if (type === AUTH_ERROR) {
const { status } = params
if (status === 401 || status === 403) {
storage.remove(LB_TOKEN)
return Promise.reject()
}
return Promise.resolve()
}
if (type === AUTH_CHECK) {
const token = storage.load(LB_TOKEN)
if (token && token.id) {
return Promise.resolve()
} else {
storage.remove(LB_TOKEN)
return Promise.reject({ redirectTo: noAccessPage })
}
}
if (type === AUTH_GET_PERMISSIONS) {
const token = storage.load(LB_TOKEN)
if (token && token.user && token.user.roleId) {
return Promise.resolve(token.user.roleId)
} else {
console.warn('Unknown user rights:', token)
storage.remove(LB_TOKEN)
return Promise.reject({ redirectTo: noAccessPage })
}
}
return Promise.reject(`authProvider - Unknown method, type: ${type}, params: ${params}`)
}
}
I am a noob, using vue.js and a node auth api, the api works fine and provides the jwt token in the response, my question is how can i use the token in all the requests that follows (using axios), and any best practices for handling the token in the front end is also appreciated.
Thanks
You can use something like that for Your scenario in your vuejs app.
import axios from 'axios'
const API_URL = 'http://localhost:3000'
const securedAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
const plainAxiosInstance = axios.create({
baseURL: API_URL,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
})
securedAxiosInstance.interceptors.request.use(config => {
const method = config.method.toUpperCase()
if (method !== 'OPTIONS' && method !== 'GET') {
config.headers = {
...config.headers,
'X-CSRF-TOKEN': localStorage.csrf
}
}
return config
})
securedAxiosInstance.interceptors.response.use(null, error => {
if (
error.response &&
error.response.config &&
error.response.status === 401
) {
return plainAxiosInstance
.post('/refresh', {}, { headers: { 'X-CSRF-TOKEN': localStorage.csrf } })
.then(response => {
localStorage.csrf = response.data.csrf
localStorage.signedIn = true
let retryConfig = error.response.config
retryConfig.headers['X-CSRF-TOKEN'] = localStorage.csrf
return plainAxiosInstance.request(retryConfig)
})
.catch(error => {
delete localStorage.csrf
delete localStorage.signedIn
location.replace('/')
return Promise.reject(error)
})
} else {
return Promise.reject(error)
}
})
export { securedAxiosInstance, plainAxiosInstance }
And in your component you use this to process your request with api
Products.vue
export default {
name: 'products',
data () {
return {
products: [],
newProduct: [],
error: '',
editedProduct: ''
}
},
created () {
if (!localStorage.signedIn) {
this.$router.replace('/')
} else {
this.$http.secured.get('/api/v1/products')
.then(response => { this.products = response.data })
.catch(error => this.setError(error, 'Something went wrong'))
}
},
methods: {
setError (error, text) {
this.error = (error.response && error.response.data && error.response.data.error) || text
},
addProduct () {
const value = this.newProduct
if (!value) {
return
}
this.$http.secured.post('/api/v1/products/', { product: { name: this.newProduct.name } })
.then(response => {
this.products.push(response.data)
this.newProduct = ''
})
.catch(error => this.setError(error, 'Cannot create product'))
},
removeProduct (product) {
this.$http.secured.delete(`/api/v1/products/${product.id}`)
.then(response => {
this.products.splice(this.products.indexOf(product), 1)
})
.catch(error => this.setError(error, 'Cannot delete product'))
},
editProduct (product) {
this.editedproduct = product
},
updateProduct (product) {
this.editedProduct = ''
this.$http.secured.patch(`/api/v1/products/${product.id}`, { product: { title: product.name } })
.catch(error => this.setError(error, 'Cannot update product'))
}
}
}
You can find here a lot of good patterns which I personally use on my projects and how also JWT token handling.
For saving token in a brower, you can use cookie, sessionStorage or localStorate, last one is the most popular now (short explination here).
In a few words, you can create an axion instance and add a token before request sent.
const http = axios.create({
baseURL: process.env.VUE_APP_SERVER_API,
// here you can specify other params
})
http.interceptors.request.use(request => {
// Do something before request is sent
request.headers['Authorization'] = `JWT ${TOKEN_HERE}`
// some logic what to do if toke invalid, etc ...
return request
}, function (error) {
// Do something with request error
return Promise.reject(error)
})
i am having exsiting service to make api call through axios in my react app,which i think is limited to one api request at a time,i wanted to make multiple request using axios.all,but i am not able to find way to modify the service,see below is the code
As in Action.js you can see that i combine two request which is wrong i guess so,please help me how to combine two request using axios.all,and please suggest api service implementation is correct or what can i do to improve it
APIService.js
import axios from 'axios';
import apiConfig from './apiConfig';
import UserSession from './userSession';
import history from '../utils/history/history';
const session = sessionStorage;
var axiosConfig = axios.create({
baseURL: apiConfig.baseUrl,
headers: {
Authorization: sessionStorage.getItem('token') != null ?
`Bearer ${sessionStorage.getItem('token')}` : null,
Accept: 'application/json',
'Content-Type': 'application/json'
},
timeout: 20000,
responseType: 'json'
});
axiosConfig.interceptors.request.use((config) => {
config.headers.Authorization =
sessionStorage.getItem('token') != null ? `Bearer
${sessionStorage.getItem('token')}` : null;
return config;
},(error) => Promise.reject(error));
const apiService = function(options) {
const onSuccess = function(response) {
if (response.status === 201) {
return Promise.resolve(
Object.assign(
{},
{
message: response.statusText
}
)
);
} else if (response.status === 200) {
if ((response.data && response.data !== null) || response.data !==
undefined || response.data !== '') {
return response.data;
} else {
return Promise.resolve(
Object.assign(
{},
{
message: response.statusText
}
)
);
}
} else if (response.data.length < 1) {
return Promise.reject(
Object.assign(
{},
{
message: 'No Data'
}
)
);
} else {
return response.data;
}
};
const onError = function(error) {
if (error.response) {
if (error.response.status === 401) {
sessionStorage.removeItem('token');
window.location = '/login';
return Promise.reject(error.response);
} else if (error.response.status === 404) {
return Promise.reject(
Object.assign(
{},
{
message: error.response.statusText
}
)
);
} else if (error.response.status === 500) {
return Promise.reject(
Object.assign(
{},
{
message: error.response.statusText
}
)
);
} else {
return Promise.reject(error.response.data);
}
} else if (error.request) {
// The request was made but no response was received
return Promise.reject(
Object.assign(
{},
{
message: error.message
}
)
);
//return Promise.reject(error.message);
} else {
// Something else happened while setting up the request
// triggered the error
return Promise.reject(
Object.assign(
{},
{
message: error.message
}
)
);
}
};
return axiosConfig(options).then(onSuccess).catch(onError);
};
export default apiService;
Request.js
import apiService from '../apiService';
export const FirstRequest = () => {
return apiService({
url: 'FirstURL',
method: 'get',
});
};
export const SecondRequest = () => {
return apiService({
url: 'SecondURL',
method: 'get',
});
};
Action.js
export const SomeHandler = () => (dispatch) => {
dispatch({
type: API_REQUEST
});
FirstRequest()
.then((res) => {
dispatch({
type: API_SUCCESS
});
SecondRequest().then((res) => {
dispatch({
type: API_SUCCESS
});
dispatch({ type: VIEW1, payload: res });
dispatch({ type: VIEW2, payload: res });
}).catch((err) => {
dispatch({
type: API_FAILURE,
payload: err
});
});
})
.catch((err) => {
dispatch({
type: API_FAILURE,
payload: err
});
});
};
This is not related to axios at all. You can combine two async functions together in an action method, using async library:
async.parallel([
getUsers,
getComments
],
function(err, results) {
// the results array will equal to [[], {'x': 'y'}] even though
// the second function had a shorter timeout.
// dispatch here
});
function getUsers(callback) {
callback(null, [])
}
function getComments(callback) {
callback(null, {'x': 'y'})
}
First off, not sure you want to do this in your componentWillMount, because your component will not render until all this is done, it's better to have it in componentDidMount and have some default states that will update once done with these requests. Second, you want to limit the number of setStates you write because they might cause additional re-renders, here is a solution using async/await:
async componentDidMount() {
const firstRequest = await axios.get(URL1);
const secondRequest = await axios.get(URL2);
const thirdRequest = await axios.get(URL3);
this.setState({
p1Location: firstRequest.data,
p2Location: SecondRequest.data,
p3Location: thirdRequest.data,
});
}
i'm working this way. you can use this
const token_config = {
headers: {
'Authorization': `Bearer ${process.env.JWD_TOKEN}`
}
}
const [ res1, res2 ] = await Axios.all([
Axios.get(`https://api-1`, token_config),
Axios.get(`https://api-2`, token_config)
]);
res.json({
info: {
"res_1": res1,
"res_2": res2
}
});