I have a redux action called academyRedirect.js which is invoked whenever an user is redirected to the path /user/academy when he pushes the 'academy' button in my main page.
export const getAcademyAutoLogin = () => {
axios.get(`/user/academy`)
.then((response) => {
window.location.replace(response.data); // this is replaced by an URL coming from backend
})
.catch((error) => {
reject(error.response);
});
return null;
};
Whenever the user is not allowed to access the academy (for not having credentials) or whenever i get an error 500 or 404, i need to display a modal or something to inform the user that an error occurred while trying to log into the academy. Right now im not being able to do it, the page just stays blank and the console output is the error.response.
Any help is appreciated
Redux Store
export const messageSlice = createSlice({
name: 'message',
initialState: { isDisplayed: false, errorMessage: ''},
reducers: {
displayError(state, action) {
state.isDisplayed = true
state.errorMessage = action.message
},
resetErrorState() {
state.isDisplayed = false
state.errorMessage = ''
},
}
})
export const messageActions = messageSlice.actions;
Inside the component:-
const Login = () => {
const errorState = useSelector(globalState => globalState.message)
const onClickHandler = async => {
axios.get(`/user/academy`)
.then((response) => { window.location.replace(response.data) })
.catch((error) => {
dispatch(messageActions.displayError(error))
});
}
return (
{errorState.isDisplayed && <div>{errorState.errorMessage}</div>}
{!errorState.isDisplayed &&<button onClick={onClickHandler}>Fetch Data </button>}
)
}
Maybe this is of help to you
You can try to add interceptor to your axios.
Find a place where you create your axios instance and apply an interceptor to it like this
const instance = axios.create({
baseURL: *YOUR API URL*,
headers: { "Content-Type": "application/json" },
});
instance.interceptors.request.use(requestResolveInterceptor, requestRejectInterceptor);
And then, in your requestRejectInterceptor you can configure default behavior for the case when you get an error from your request. You can show user an error using toast for example, or call an action to add your error to redux.
For second case, when you want to put your error to redux, its better to use some tools that created to make async calls to api and work with redux, for example it can be redux-saga, redux-thunk, redux-axios-middleware etc.
With their docs you would be able to configure your app and handle all cases easily.
I'm trying to make a post request inside a function whenever i click on a button.
here is the code of the button
<Button onClick={handleClick}>Add to Cart</Button>
and here is the `handleClick funcion:
const handleClick = (event) => {
event.preventDefault();
props.postCart(itemData.product_name, itemData.product_price);
}
and here i showcase the code of mapDispatchToProps function:
const mapDispatchToProps = dispatch => {
return {
postCart: (productName, productPrice) => dispatch(postAddToCart(productName, productPrice))
}
}
finally the code of postAddToCart:
export const postAddToCart = (productName, productPrice) => {
const email = sessionStorage.getItem('email');
return (dispatch) => {
dispatch(productName, productPrice);
//REST API endpoint
axios.post('http://localhost:8000/api/auth/add-to-cart', {
email:email,
})
.then(resp => {
dispatch({
type: actionTypes.ADDED_TO_CART,
status: resp.status
});
})
.catch(resp => {
dispatch({
type: actionTypes.ADDED_TO_CART,
status: "FAILED"
});
})
}
}
But whenever i click the button Add to cart i get the following error:
Error: Actions must be plain objects. Use custom middleware for async actions.
knowing that i'm using the redux-thunk middleware.
Can you please tell me what's the problem and how can i fix it ? thank you ^^. if i missed something tell me in comments to add it ^^
Your function postAddToCart() returns a "dispatcher", i.e. a function that expects dispatch as an argument.
The error is that you are trying to dispatch this "dispatcher", instead of an "action":
// wrong: calling 'dispatch()'
postCart: (productName, productPrice) => dispatch(postAddToCart( ... ))
// correct: calling the returned dispatcher and pass 'dispatch' as argument
postCart: (productName, productPrice) => postAddToCart( ... )(dispatch)
I built an input interface in React and stored the data in a Redux state.
Then, I created a new action for a Post API call. If the "data" parameter is a constant (that I created as a test), everything works fine.
In reality, I'd like to use a key from the Redux state as the data parameter.
In the example, I'm getting an error because props are not defined.
Does it makes sense to connect this action to the state? If yes, how to do it? Thanks a lot!
import axios from 'axios';
export const show_query_action = () => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: this.props.reducer_components
})
.then(res => {
dispatch(show_query_success_action(res));
})
.catch(err => {
dispatch(show_query_failure_action(err.message));
});
};
};
const show_query_started_action = () => ({
type: 'ADD_SHOW_QUERY_STARTED'
});
const show_query_success_action = todo => ({
type: 'ADD_SHOW_QUERY_SUCCESS',
payload: {
...todo
}
});
const show_query_failure_action = error => ({
type: 'ADD_SHOW_QUERY_FAILURE',
payload: {
error
}
});
If it needs to be callable with different parameters from a React component you can put the data parameter as a parameter to your action creator:
export const show_query_action = (data) => {
return dispatch => {
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
This is also easily testable. If you only call this action with parameters from the redux store, you can use the second parameter from redux-thunk (wich i presume you are using here):
export const show_query_action = () => {
return (dispatch, getState) => {
const data = getState().data; -> you would specify in wich part of the state your data lives, this is just an example
dispatch(show_query_started_action());
axios({
method: 'post',
url:'http://127.0.0.1:5000/show_query',
data: data
})
Hope this helps.
I want to call useQuery whenever I need it,
but useQuery can not inside the function.
My trying code is:
export const TestComponent = () => {
...
const { data, loading, error } = useQuery(gql(GET_USER_LIST), {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
...
...
const onSaveInformation = async () => {
try {
await updateInformation({...})
// I want to call useQuery once again.
} catch (e) {
return e
}
}
...
How do I call useQuery multiple times?
Can I call it whenever I want?
I have looked for several sites, but I could not find a solutions.
From apollo docs
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to
events other than component rendering
I suggest useLazyQuery. In simple terms, useQuery will run when your component get's rendered, you can use skip option to skip the initial run. And there are some ways to refetch/fetch more data whenever you want. Or you can stick with useLazyQuery
E.g If you want to fetch data when only user clicks on a button or scrolls to the bottom, then you can use useLazyQuery hook.
useQuery is a declarative React Hook. It is not meant to be called in the sense of a classic function to receive data. First, make sure to understand React Hooks or simply not use them for now (90% of questions on Stackoverflow happen because people try to learn too many things at once). The Apollo documentation is very good for the official react-apollo package, which uses render props. This works just as well and once you have understood Apollo Client and Hooks you can go for a little refactor. So the answers to your questions:
How do I call useQuery multiple times?
You don't call it multiple times. The component will automatically rerender when the query result is available or gets updated.
Can I call it whenever I want?
No, hooks can only be called on the top level. Instead, the data is available in your function from the upper scope (closure).
Your updateInformation should probably be a mutation that updates the application's cache, which again triggers a rerender of the React component because it is "subscribed" to the query. In most cases, the update happens fully automatically because Apollo will identify entities by a combination of __typename and id. Here's some pseudocode that illustrates how mutations work together with mutations:
const GET_USER_LIST = gql`
query GetUserList {
users {
id
name
}
}
`;
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, update: { name: $name }) {
success
user {
id
name
}
}
}
`;
const UserListComponen = (props) => {
const { data, loading, error } = useQuery(GET_USER_LIST);
const [updateUser] = useMutation(UPDATE_USER);
const onSaveInformation = (id, name) => updateUser({ variables: { id, name });
return (
// ... use data.users and onSaveInformation in your JSX
);
}
Now if the name of a user changes via the mutation Apollo will automatically update the cache und trigger a rerender of the component. Then the component will automatically display the new data. Welcome to the power of GraphQL!
There's answering mentioning how useQuery should be used, and also suggestions to use useLazyQuery. I think the key takeaway is understanding the use cases for useQuery vs useLazyQuery, which you can read in the documentation. I'll try to explain it below from my perspective.
useQuery is "declarative" much like the rest of React, especially component rendering. This means you should expect useQuery to be called every render when state or props change. So in English, it's like, "Hey React, when things change, this is what I want you to query".
for useLazyQuery, this line in the documentation is key: "The useLazyQuery hook is perfect for executing queries in response to events other than component rendering". In more general programming speak, it's "imperative". This gives you the power to call the query however you want, whether it's in response to state/prop changes (i.e. with useEffect) or event handlers like button clicks. In English, it's like, "Hey React, this is how I want to query for the data".
You can use fetchMore() returned from useQuery, which is primarily meant for pagination.
const { loading, client, fetchMore } = useQuery(GET_USER_LIST);
const submit = async () => {
// Perform save operation
const userResp = await fetchMore({
variables: {
// Pass any args here
},
updateQuery(){
}
});
console.log(userResp.data)
};
Read more here: fetchMore
You could also use useLazyQuery, however it'll give you a function that returns void and the data is returned outside your function.
const [getUser, { loading, client, data }] = useLazyQuery(GET_USER_LIST);
const submit = async () => {
const userResp = await getUser({
variables: {
// Pass your args here
},
updateQuery() {},
});
console.log({ userResp }); // undefined
};
Read more here: useLazyQuery
You can create a reusable fetch function as shown below:
// Create query
const query = `
query GetUserList ($data: UserDataType){
getUserList(data: $data){
uid,
first_name
}
}
`;
// Component
export const TestComponent (props) {
const onSaveInformation = async () => {
// I want to call useQuery once again.
const getUsers = await fetchUserList();
}
// This is the reusable fetch function.
const fetchUserList = async () => {
// Update the URL to your Graphql Endpoint.
return await fetch('http://localhost:8080/api/graphql?', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
}).then(
response => { return response.json(); }
).catch(
error => console.log(error) // Handle the error response object
);
}
return (
<h1>Test Component</h1>
);
}
Here's an alternative that worked for me:
const { refetch } = useQuery(GET_USER_LIST, {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
}
);
const onSaveInformation = async () => {
try {
await updateInformation({...});
const res = await refetch({ variables: { ... }});
console.log(res);
} catch (e) {
return e;
}
}
And here's a similar answer for a similar question.
Please use
const { loading, data, refetch } = useQuery(Query_Data)
and call it when you need it i.e
refetch()
export function editPost(props){
const request = axios.put(`http://www.example.com/posts`, props) ;
return{
type: EDIT_POST,
payload: request
};
}
hi,, is this what a proper "Update" action should look like in Redux?
using axios to make the request
the type was created in another file and then imported
Thanks
axios is promise-based, so currently you're returning a payload that doesn't exist. Look for redux-thunk and use it in the following way:
actionCreator() {
return (dispatch) => {
return axios.put('url').then((res) => dispatch({ type: EDIT_POST, payload: res }))
}
}