Dispatcher function never gets called in React thunk - reactjs

I'm working on a project in React, but there's an issue I can't resolve with using Redux. My thunk is set up as follows
export const getData = async () => {
return async(dispatch) => {
const sendRequest = async () => {
const url = '...';
const response = await fetch(url);
const data = await response.json();
return data;
}
try {
const myData = await sendRequest();
console.log(myData)
dispatch(dataActions.setData(myData))
}
catch (error) {
console.log(error);
}
}
}
On the other hand, my slice looks like this
const initialState = {
my_data: []
};
const dataSlice = createSlice({
name: 'data',
initialState,
reducers: {
setData(state, action){
state.my_data = action.payload.myData;
}
}
})
export const dataActions = dataSlice.actions;
export default dataSlice.reducer;
In my App.js, I call it with the following code
const data = useSelector((state)=>{return state.data.my_data})
const dispatch = useDispatch();
useEffect(()=>{
dispatch(getData);
}, [dispatch])
I used a number of console.log()s in order to find out where the issue might be and I found that it does, in fact, enter the getData() function, but it never touches what's being returned. I even tried making a dispatcher() function which contained the code that's being returned, but it would only get called if I called it inside of getData() and it would give me the following error - 'TypeError: dispatch is not a function at ...'

You need to invoke getData while dispatching in order for the thunk action creator to be called.
useEffect(()=>{
dispatch(getData());
}, [dispatch])

Related

Expected 0 arguments, but got 1 while passing arguments to the async func in redux toolkit store

I am creating a project using Redux Toolkit + Typescript + React.
I have the function the fetches movies in my moviesSlice (as part of redux toolkit store) that has movieId as an argument. When I try to dispatch this function in my component and pass this id, TypeScript shows an error: Expected 0 arguments, but got 1.
How can I fix it?
Thank you
// moviesSlice.tsx
export const fetchMovie = createAsyncThunk(
"people/fetchPerson",
async (movieId) => {
try {
const response = await axios(`${BASE_URL}/films/${movieId}`);
return response.data;
} catch (error) {
console.log(error);
}
}
);
// store.tsx
export const store = configureStore({
reducer: {
people: peopleReducer,
movies: moviesReducer,
planets: planetsReducer,
},
});
// Movie.tsx
useEffect(() => {
dispatch(fetchMovie(movieId));
}, []);
This is TypeScript. Give your arguments a type - otherwise the created thunk action creator will not know what to do:
export const fetchMovie = createAsyncThunk(
"people/fetchPerson",
async (movieId: string) => {
try {
const response = await axios(`${BASE_URL}/films/${movieId}`);
return response.data;
} catch (error) {
console.log(error);
}
}
);

React/Redux/Typescript - useDispatch with .then().catch()

I'm beginner with React/Redux.
I want to authenticate a User and display a notification on my app when error occurs.
This is my login function:
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const handleSignIn = (values: SignInOpts, setSubmitting: any) => {
setLoading(true);
dispatch(authenticationActions.signInAndFetchUser(values))
.then(res => {
console.log("SignIn Success");
setLoading(false);
})
.catch(err => {
console.log("SignIn Failure");
setLoading(false);
showErrorNotification(
"Error notification"
);
});
};
My action:
export const signInAndFetchUser = (credentials: SignInOpts) => {
return (dispatch, getState) => {
return dispatch(signIn(credentials)).then(res => {
const token = getState().authentication.token;
return dispatch(getMe(token));
});
};
};
The error I have :
How can I perform this ?
Most of your work should happen in the thunk (the action). dispatch does not return a promise. So you have to handle your promise inside your thunk and dispatch the corresponding action, it will then be send to the reducer. The new state will reflects the changes.
Here is a thunk which should give you an idea of how it works :
export const signInAndFetchUser = (credentials: SignInOpts) => {
return (dispatch, getState) => {
dispatch(action.startLoading);
signIn(credentials)
.then((res) => {
// do something with res : maybe recover token, and store it to the store ?
// if the token is in the store already why do you need to signIn ?
dispatch(action.signedInSuccess(res.token));
getMe(res.token)
.then((user) => {
dispatch(action.getMeSucceded(user));
})
.catch((err) => dispatch(action.getMeFailed(err)));
})
.catch((err) => dispatch(action.signInFailed(err)));
};
};
Or using async/await :
export const signInAndFetchUser = (credentials: SignInOpts) => {
return async (dispatch, getState) => {
dispatch(action.startLoading);
try {
const res = await signIn(credentials);
dispatch(action.signedInSuccess(res.token));
try {
const user = await getMe(res.token);
dispatch(action.getMeSucceded(user));
} catch (err) {
dispatch(action.getMeFailed(err));
}
} catch {
dispatch(action.signInFailed(err));
}
};
};
Generally for thunks, dispatch(myThunk()) will return whatever the thunk returns and can be a Promise, as also the case in your signInAndFetchUser method.
The problem is that the normal useDispatch hook typings do not know which middlewares you are using and thus have no overload for that behaviour.
That is why the Redux TypeScript Quickstart Tutorial recommends you define your own, correctly-typed hooks:
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
If you are using the official Redux Toolkit (especially with TypeScript, you definitely should as it cuts out almost all type annotations), you can just get
export type AppDispatch = typeof store.dispatch
If you are using old-style vanilla Redux, just use ThunkDispatch as AppDispatch.
dispatch do not return promise so you cannot pipe .then. From Action the pipeline flows to reducer, and reducer returns the state back to your component via useSelector for functional component and connect for class based component. So you need to hook those to receive login success object via state

Get the state variable after dispatch is finished in react redux and saga?

Hello I am fairly new to React, Redux and Saga. So I have a scenario where I have a .jsx file which is the view file then an action file used for dispatch and I am also using saga which updates the data in the reducers. Following are the file structurs:
Action file:
export const getAction = (requestor) => ({
type: GET_ACTION,
data: {
requestor,
},
});
Reducer file
export const Reducer = (currentState = {}, action) => {
const newState = { ...currentState };
switch (action.type) {
case GET_ACTION:
newState.data = action.data;
return newState;
}
};
Saga file
function* getData(action) {
const { requestor } = action.data;
try {
const data = yield call(callService);
if(success) {
yield put( {type: GET_ACTION, data} );
}
} catch (e)
{
}
}
function* getDataSaga() {
yield takeLatest(GET_ACTION, getData);
}
export {
getData,
};
export default [
getDataSaga,
];
jsx file
const [dummy, setDummy] = useState([]);
const data = useSelector(state => state.data, shallowEqual) || {};
There is a function in which dispatch function is called.
dispatch(getAction(requestor));
Now I need to access the updated state of data after dispatch has finished updating the data because after the data is updated I have to setDummy to set the dummy variable mentioned. Any way which I can be approaching to achieve that. I have tried to use dispatch.then but on UI it is saying .then is not a function for dispatch.
after the data is updated I have to setDummy
useEffect lets you do something upon a given prop changing
const [dummy, setDummy] = useState([]);
const data = useSelector(state => state.data, shallowEqual) || {};
// setDummy when `data` changes
useEffect(() => {
setDummy(data);
}, [data])

Axios call in useReducer returning promise and not data

I am using useReducer in my context provider. The idea is that I will be a central place for my state and dispatch functions to live.
I am making an axios call to a datapase to fetch projects. However, when I return in the dispatch function, it is returning a promise. How can I return the data from the axios call so that it stores the data from the call in state?
const initState = []
const projectsReducer = async (state, action) => {
switch(action.type) {
case 'FETCH_PROJECTS':
const req = await axios.get('/api/fetch_projects')
const { data } = req
return {...state, data}
default:
return state
}
}
useEffect(() => {
const initFetch = () => {
projectsDispatch({type: 'FETCH_PROJECTS'})
}
initFetch()
}, [])
const [projects, projectsDispatch] = useReducer(projectsReducer, initState)
Do your fetch within the effect, then pass the data into the reducer.
A reducer is a pure function that should do no side effects. Plus whatever data is returned from the reducer is set as the next state. So, an async function always returns a promise - which means that you are setting the state of projects to be a promise for the data.
If you refactor your code as follows, it should work.
const initState = {data: []};
const projectsReducer = (state, action) => {
switch(action.type) {
case 'FETCH_PROJECTS':
const { data } = action.payload;
return {...state, data}
default:
return state
}
}
const [projects, projectsDispatch] = useReducer(projectsReducer, initState)
useEffect(() => {
const initFetch = async () => {
const req = await axios.get('/api/fetch_projects')
projectsDispatch({type: 'FETCH_PROJECTS', payload: {data: req.data}})
}
initFetch()
}, [])
// data is in projects.data;
Though since it's more simple, you don't really need a reducer:
const [projects, setProjects] = useState([]);
useEffect(() => {
const initFetch = async () => {
const req = await axios.get('/api/fetch_projects')
setProjects(req.data);
}
initFetch()
}, [])
// data is in projects

How to use async await in React component

I have a component like below
import Axios from 'axios';
export const getCountry = async () => dispatch => {
return await Axios.get('')
.then(response => {
//some code
})
.catch(error => {
//some code
});
};
export default { getCountry };
I am getting error Parsing error: Can not use keyword 'await' outside an async function.
Try this:
import Axios from 'axios';
export const getCountry = async (dispatch) => await Axios.get('...');
No need to re-export the same constant.
Your code is pretty much equivalent to:
const theFunction = dispatch => {
return await Axios.get('')
.then(response => {
//some code
})
.catch(error => {
//some code
});
};
export const getCountry = async () => theFunction;
I.e. you have an async function that returns a promise of a non-async function. There are several problems here:
You want getCountry to return a country, presumably, not another function;
You don't need to have a function returning a function (with no closure) be async);
You use await uselessly; your code is not more readable, as you still use the promise pattern (not critical);
You do need theFunction, a function containing await, to be async (the immediate source of your error).
The fix is rather easy: get rid of the useless wrapper, and make the correct function async:
export const getCountry = async dispatch => {
try {
const response = await Axios.get('')
//some code
} catch (error) {
//some code
}
};
EDIT: If your question is in context of redux-thunk, then my point #1 does not apply; the function would probably look like this, instead (NB: I am not familiar with redux-thunk, so this is a guess; you should tag correctly in order to attract the relevant crowd):
export const getCountry = () => async dispatch => {
try {
const response = await Axios.get('')
//some code
} catch (error) {
//some code
}
};
This is an example on how to use Async Await with react Hooks
function useAsyncAwaitHook(searchBook) {
const [result, setResult] = React.useState([]);
const [loading, setLoading] = React.useState("false");
React.useEffect(() => {
async function fetchBookList() {
try {
setLoading("true");
const response = await fetch(
`https://www.googleapis.com/books/v1/volumes?q=${searchBook}`
);
const jsonData = await response.jsonData();
// console.log(jsonData);
setResult(
jsonData.items.map(item => {
console.log(item.volumeInfo.title);
return item.volumeInfo.title;
})
);
} catch (error) {
setLoading("null");
}
}
if (searchBook !== "") {
fetchBookList();
}
}, [searchBook]);
return [result, loading];
}

Resources