How can I pass state to action to fetch API? - javascript

so im new in redux and I need bit of help with my homework. I have a drop down with couple of choices and the choice that user select needs to be passed to state (already have this working and state is updating when user select something new) and then to action that can fetch data with '/stats/${userChoice}'. But i have no idea how to do this at all.
actions/index.js:
export const fetchAuthorsStats = () => async dispatch => {
const response = await myAPI.get(`/stats/${userChoice}`);
dispatch({ type: 'FETCH_AUTHORS_STATS', payload: response.data })
};
components/Dropdown.js:
onAuthorSelect = (e) => {
this.setState({selectAuthor: e.target.value})
};
.
.
.
const mapStateToProps = state => {
return {
authors: state.authors,
selectAuthor: state.selectAuthor,
authorsStats: state.authorsStats
}
};
export default connect(mapStateToProps, { fetchAuthors, selectAuthor, fetchAuthorsStats })(Dropdown)
under "selectAuthor" I have my state that I need to pass to this action API

You already map dispatch to fetchAuthorsStats thunk in your component so that means you can just use it in onAuthorSelect (or anywhere else you need - like on form submit) and pass it a parameter with the selectedAuthor.
// Added a userChoice param here:
export const fetchAuthorsStats = (userChoice) => async dispatch => {
const response = await myAPI.get(`/stats/${userChoice}`);
dispatch({ type: 'FETCH_AUTHORS_STATS', payload: response.data })
};
onAuthorSelect = (e) => {
this.setState({selectAuthor: e.target.value})
this.props.fetchAuthorsStats(e.target.value);
};

You can achieve this by calling the API directly with the event target value :
/// first you update your API call to receive the selected author
export const fetchAuthorsStats = (userChoice) => async dispatch => {
const response = await myAPI.get(`/stats/${userChoice}`);
dispatch({ type: 'FETCH_AUTHORS_STATS', payload: response.data })
};
//then you update your handler function
onAuthorSelect = (e) =>{
this.props.fetchAuthorsStats(e.target.value)
}
if you wish to still save it on the react state you can do the setState first and then the API call with (this.state.selectedAuthor) instead of (e.target.value)

Related

How to use custom react query hook twice in the same component?

I have a custom hook like so for getting data using useQuery. The hook works fine, no problem there.
const getData = async (url) => {
try{
return await axios(url)
} catch(error){
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData', () => getData(url), {onSuccess})
}
However, if I call this hook twice in my component it will only fetch data from the first call even with a different URL. (Ignore the comments typo, that's intentional)
The call in my component:
const { data: commentss, isLoading: commentsIsLoading } = useGetData(`/comments/${params.id}`)
const { data: forumPost, isLoading: forumPostIsLoading } = useGetData(`/forum_posts/${params.id}`)
When I console.log forumPost in this case, it is the array of comments and not the forum post even though I am passing in a different endpoint.
How can I use this hook twice to get different data? Is it possible? I know I can just call parallel queries but I would like to use my hook if possible.
Since useQuery caches based on the queryKey, use the URL in that name
const getData = async(url) => {
try {
return await axios(url)
} catch (error) {
console.log(error.message)
}
}
export const useGetData = (url, onSuccess) => {
return useQuery('getData' + url, () => getData(url), {
onSuccess
})
}
//........
const {
data: commentss,
isLoading: commentsIsLoading
} = useGetData(`/comments/${params.id}`)
const {
data: forumPost,
isLoading: forumPostIsLoading
} = useGetData(`/forum_posts/${params.id}`)

React and Redux: Pulling Data From Payload of Action Type

Did not get the help I was looking for in my previous question. I will try to ask it differently. DELETE_ITEM action type comes with a payload and if I console.log it, it displays the id number of the item being deleted. Now, only upon DELETE_ITEM_SUCCESS I want to delete it from the state. DELETE_ITEM_SUCCESS does not come with a payload. How can I get the payload from GET_ITEM which is the id number and use it in DELETE_ITEM_SUCCESS? Any help will be greatly appreciated. Thank you.
//Action types
export const getItems = () => ({
type: GET_ITEMS
});
export const updateItems = (data) => ({
type : UPDATE_ITEMS,
payload: data
});
export const deleteItem = (itemId) => ({
type : DELETE_ITEM,
payload: itemId
});
//Delete Api
export const apiDeleteRequest = (url, onSuccess, onError) => ({
type: DELETE_API_REQUEST,
meta: {url, onSuccess, onError}
});
//Middleware
export const removeItemFlow = ({dispatch, getState}) => next => action => {
next(action);
if(action.type === DELETE_ITEM){
console.log(action.payload)
dispatch(apiDeleteRequest(`https://jsonplaceholder.typicode.com/todos/${action.payload}`, DELETE_ITEM_SUCCESS, DELETE_ITEM_ERROR));
}
if(action.type === DELETE_ITEM_SUCCESS){
console.log(action.payload)
dispatch(updateItems(getState().items.itemsList.filter(item => item.id !== action.payload)))
}
};
This is the enhancement that could be done to the middleware, which I mentioned in the comment beneath question. My proposition is to handle the API call right inside the middleware, instead of further dispatching the action and delegate the handling to some other code.
export const removeItemFlow = ({ dispatch, getState }) => next => action => {
next(action);
if (action.type === DELETE_ITEM) {
const itemIdToDelete = action.payload
const url = `https://jsonplaceholder.typicode.com/todos/${action.payload}`
const onSuccessCallback = () => {
// do whatever you would do when DELETE_ITEM_SUCCESS
console.log('You get access to "itemIdToDelete" right here', itemIdToDelete)
dispatch(updateItems(getState().items.itemsList.filter(item => item.id !== itemIdToDelete)))
}
const onErrorCallback = () => {
// do whatever you would do when DELETE_ITEM_ERROR
console.log('You get access to "itemIdToDelete" right here', itemIdToDelete)
}
axios.delete(url).then(onSuccessCallback, onErrorCallback)
};

How to create custom middleware for async request and redux?

Help to understand what the problem is. When I write mapDispatchToProps like this:
const mapDispatchToProps = (dispatch: any) => {
return {
getPostByIdAction: (post: any) => dispatch ({type: GET_ID, payload: post})
}
};
everything is working fine. But when I try to dispatch the function in this way:
const mapDispatchToProps = (dispatch: any) => {
return {
getPostByIdAction: (post: any) => dispatch (getPostById (post))
}
};
I get an error: Actions must be plain objects. Use custom middleware for async actions.
What could have gone wrong?
my actions:
export const getPostById = async (id: any) => {
const myResponse = await fetch (`https://jsonplaceholder.typicode.com/posts/$ {id}`);
const myJson = await myResponse.json ();
const post = myJson.body
}
my reducer:
import {combineReducers} from 'redux'
import {pageReducer} from './page'
export const rootReducer = combineReducers ({
page: pageReducer
})
import {GET_ID} from '../actions/PageActions'
const initialState = {
post: "Click on article to read it"
}
export function pageReducer (state = initialState, action: any) {
switch (action.type) {
case GET_ID:
return {... state, post: action.payload};
default:
return state
}
}
export const getPostById = async (id: any) => {
const myResponse = await fetch (`https://jsonplaceholder.typicode.com/posts/$ {id}`);
const myJson = await myResponse.json ();
const post = myJson.body
}
you are not returning anything. you should return an object here to pass it to dispatch. dispatch needs an object and that object has to have "action" property. you can add more properties but "action" property is a must.
The main problem with redux is that.., it is synchronous.
To handle async operations, we use middlewares. There are many libraries available to handle async operations.
But, if you want to create one, lets create a custom middleware for async operations
I've defined some types for type safety (since we're doing it in typescript).
Here we added another field 'api' for our convenience to differentiate the async operations with non-async ones
import { Middleware, Dispatch, MiddlewareAPI } from 'redux'
export interface DispatchType {
type: string
payload?: any
meta?: DispatchMeta
}
export interface DispatchMeta {
async: boolean
api: {
url: string
onComplete: string
// extra information you want for the request can be passes here:
// eg
// params, method, data
}
}
The object type that we dispatch looks something like this:
dispatch({type: 'TYPE_OF_DISPATCH', payload: 'any type of payload'})
Now lets create the Middleware to intercept those actions(object) that has meta field with async set to 'true'.
// Middleware to intercept those actions(object) that has meta field with async set to true;
export const asyncMiddleware: Middleware = ({ getState }: MiddlewareAPI) => (
next: Dispatch
) => async (action: DispatchType) => {
// Call the next dispatch method in the middleware chain.
next(action);
if (action.meta && action.meta.async && action.meta.api) {
const res = await fetch(action.meta.api.url);
const json = await res.json();
const post = json.body;
next({
type: action.meta.api.onComplete,
data: post
})
}
}
Here our middleware intercepts the action with async flag 'true' and then once completed, its dispatches the 'onComplete' action sent to the meta.
To use this middleware, Your dispatch action should look something like this
dispatch({
type: 'FETCH',
meta: {
async: true,
api: {
url: 'https://jsonplaceholder.typicode.com/posts/1',
onComplete: 'FETCHED_POST'
}
}
})
Here you may create a wrapper function which dispatches the above action
const getpost = (id: string) =>
dispatch({
type: 'FETCH',
meta: {
async: true,
api: {
url: `https://jsonplaceholder.typicode.com/posts/${id}`,
onComplete: 'FETCHED_POST'
}
}
})
Lastly, don't forget to apply the middleware to your redux store.
const store = createStore(
RootReducer,
applyMiddleware(asyncMiddleware)
)

How can I make an action be reusable? ReactJS with Redux

I need to do a dynamic action. In other words, it can be reused for differents actions.
I tried to create a function that loads type and payload, but an error appears.
I'm trying make this function works:
export function getData(url, type) {
const request = Server.get(url)
return (dispatch) =>
request.then((response) => {
dispatch({
type: type,
payload: response.data
})
}).catch(function (error) {
console.log(error)
});
}
But I got an error when I call this function this way:
export function getClientes() {
Actions.getData('ClientesEFornecedores', GET_CLIENTES)
}
It's showing:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
I'm Calling the getClientes() function this way:
function ClientesTable(props)
{
const dispatch = useDispatch();
const clientes = useSelector(({erpCliente}) => erpCliente.clientes.data);
useEffect(() => {
dispatch(Actions.getClientes());
}, [dispatch]);
How can I make an action be reusable?
Try something like this
export const getData=(url, type) =>async dispatch=>{
try{
const response = await Server.get(url);
dispatch({ type: type,payload: response.data })
} catch(err){
console.log(err)
}
}
getClientes function
export const getClientes=() => dbActions.getData('ClientesEFornecedores', GET_CLIENTES);
In fact I had almost succeeded.
All that remained was to return the function call.
This is the way that works:
export function getClientes() {
return dbActions.getData('ClientesEFornecedores', GET_CLIENTES)
}

React Redux dispatch action after another action

I have an async action, which fetch data from REST API:
export const list = (top, skip) => dispatch => {
dispatch({ type: 'LIST.REQUEST' });
$.get(API_URL, { top: top, skip: skip })
.done((data, testStatus, jqXHR) => {
dispatch({ type: 'LIST.SUCCESS', data: data });
});
};
A sync action, which changes skip state:
export const setSkip = (skip) => {
return {
type: 'LIST.SET_SKIP',
skip: skip
};
};
Initial state for top = 10, skip = 0. In component:
class List extends Component {
componentDidMount() {
this.list();
}
nextPage() {
let top = this.props.list.top;
let skip = this.props.list.skip;
// After this
this.props.onSetSkip(skip + top);
// Here skip has previous value of 0.
this.list();
// Here skip has new value of 10.
}
list() {
this.props.List(this.props.list.top, this.props.list.skip);
}
render () {
return (
<div>
<table> ... </table>
<button onClick={this.nextPage.bind(this)}>Next</button>
</div>
);
}
}
When button Next at first time clicked, value of skip which uses async action not changed.
How I can to dispatch action after sync action?
If you are using redux thunk, you can easily combine them.
It's a middleware that lets action creators return a function instead of an action.
Your solution might have worked for you now if you don't need to chain the action creators and only need to run both of them.
this.props.onList(top, newSkip);
this.props.onSetSkip(newSkip);
If you need chaining(calling them in a synchronous manner) or waiting from the first dispatched action's data, this is what I'd recommend.
export function onList(data) {
return (dispatch) => {
dispatch(ONLIST_REQUEST());
return (AsyncAPICall)
.then((response) => {
dispatch(ONLIST_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function setSkip(data) {
return (dispatch) => {
dispatch(SETSKIP_REQUEST());
return (AsyncAPICall(data))
.then((response) => {
dispatch(SETSKIP_SUCCESS(response.data));
})
.catch((err) => {
console.log(err);
});
};
}
export function onListAndSetSkip(dataForOnList) {
return (dispatch) => {
dispatch(onList(dataForOnList)).then((dataAfterOnList) => {
dispatch(setSkip(dataAfterOnList));
});
};
}
Instead of dispatching an action after a sync action, can you just call the function from the reducer?
So it follows this flow:
Sync action call --> Reducer call ---> case function (reducer) ---> case function (reducer)
Instead of the usual flow which is probably this for you:
Sync action call --> Reducer call
Follow this guide to split the reducers up to see what case reducers are.
If the action you want to dispatch has side affects though then the correct way is to use Thunks and then you can dispatch an action after an action.
Example for Thunks:
export const setSkip = (skip) => {
return (dispatch, getState) => {
dispatch(someFunc());
//Do someFunc first then this action, use getState() for currentState if you want
return {
type: 'LIST.SET_SKIP',
skip: skip
};
}
};
also check this out redux-sequence-action
Thanks for the replies, but I made it this way:
let top = this.props.list.top;
let skip = this.props.list.skip;
let newSkip = skip + top;
this.props.onList(top, newSkip);
this.props.onSetSkip(newSkip);
First I calculate new skip and dispatch an async action with this new value. Then I dispatch a syns action, which updates skip in state.
dispatch({ type: 'LIST.SUCCESS', data: data, skip: The value you want after sync action });

Categories