How to work with API call action in redux? - reactjs

I am new to redux and I am trying to make it work with my application, but I have problems with understanding how to work with async actions in it. I have action that is api call. This action should be called as soon as my other state is not empty. I do not get any mistakes but do not think that my action is called since the data is empty. Can anybody help to understand what I am doing wrong?
Here is my actions.js. The wordsFetchData is the action I need to call:
export function wordsFetchDataSuccess(items){
return{
type: 'WORDS_FETCH_DATA_SUCCESS',
items
};
}
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
}
export function wordsHasErrored(bool) {
return {
type: 'WORDS_HAS_ERRORED',
hasErrored: bool
};
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('URL', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
}
Here are my reducers:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function wordsAreFetching(state = false, action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return action.areFetching;
default:
return state;
}
}
export function wordsFetchHasErrored(state = false, action) {
switch (action.type) {
case 'WORDS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
This is my componentDidMount function:
componentDidMount = (state) => {
this.props.fetchData(state);
};
This is the function after terminating which the action should be called:
parseInput = async () => {
console.log(this.state.textInput);
let tempArray = this.state.textInput.split(" "); // `convert
string into array`
let newArray = tempArray.filter(word => word.endsWith("*"));
let filterArray = newArray.map(word => word.replace('*', ''));
await this.setState({filterArray: filterArray});
await this.props.updateData(this.state.filterArray);
if (this.state.projectID === "" && this.state.entity === "")
this.dialog.current.handleClickOpen();
else
if (this.state.filterArray.length !== 0)
this.componentDidMount(this.state.filterArray);
};
These are the mapStateToProps and mapDispatchToProps functions.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: wordsFetchData
};
};

You only need one action for executing fetching (i.e WORDS_ARE_FETCHING), the rest of the cases (i.e WORDS_HAS_ERRORED & WORDS_FETCH_DATA_SUCCESS) can be handled inside your reducer.
Your action:
export function wordsAreFetching(){
return{
type: 'WORDS_ARE_FETCHING',
}
}
Your new reducer:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return {...state, error: false, areFetching: true};
case 'WORDS_FETCH_DATA_SUCCESS':
return {...state, items: action.payload , areFetching: false};
case 'WORDS_HAS_ERRORED':
return {...state, error: true, areFetching: false};
default:
return state;
}
Then you can trigger WORDS_FETCH_DATA_SUCCESS after you get the data from here:
export function wordsFetchData() {
try {
const response = await axios.get(YOUR_URL);
return dispatch({ type: WORDS_FETCH_DATA_SUCCESS, payload: response.data });
} catch (err) {
return dispatch({ type: WORDS_HAS_ERRORED });
}
}
Take a look at this example, it uses axios that can help you with async calls.

A couple of things:
No need to pass state into your componentDidMount, your mapDispatchToProps is not using it.
Here is a suggestion to structure those functions. They are a bit more concise and readable.
const mapStateToProps = ({items, wordsAreFetching, wordsFetchHasError}) => ({
items,
hasErrored: wordsFetchHasErrored,
areFetching: wordsAreFetching,
});
const mapDispatchToProps = () => ({
fetchData: wordsFetchData(),
});
Other notes and helpful things:
If you're using thunk, you'll have access to your entire redux store in here as a second argument. For example:
return (dispatch, getState) => {
dispatch(wordsAreFetching(true));
console.log('getState', getState());
const { words } = getState().items;
// This is a great place to do some checks to see if you _need_ to fetch any data!
// Maybe you already have it in your state?
if (!words.length) {
fetch('URL', {
method: "POST",
headers: {
......
}
})
I hope this helps, if you need anything else feel free to ask.

Related

How to useDispatch async multiple times?

I am using useDispatch two times to change my todos and then change my login status. Both work seperately but when put after each other the second dispatch overwrites my list of todos to an empty object [].
How would I make this work?
Axios Post
axios
.post("http://localhost:3333/user/login", newUser)
.then((response) => {
//do stuff
dispatch(changeTodos(stuff));
dispatch(login());
});
Actions
export const login = (data) => {
return {
type: "LOGIN",
data: data,
};
};
export const changeTodos = (data) => {
return {
type: "CHANGETODOS",
data: data,
};
};
Reducer
const loggedReducer = (state = false, action) => {
switch (action.type) {
case "LOGIN":
return true;
case "LOGOUT":
return false;
default:
return false;
}
};
export default loggedReducer;
const todosReducer = (state = [], action) => {
switch (action.type) {
case "CHANGETODOS":
return action.data;
default:
return [];
}
};
export default todosReducer;
For your default you need to return state otherwise it's gonna hit the default when it doesn't match the action type. For example, LOGIN is will make your TodosReducer return [] which is why it's being cleared out.
default:
return state;

redux to dispatch response and a return message

In my action i am dispatching the type and the payload but what if i also want the res.status and a return JSON message to be included into my props. How would i do so in my action and reducer?
action
export const fetchUserPosts = () => (dispatch) => {
fetch(`${currentPort}/user/recipes`,
{
withCredentials: true,
credentials: 'include',
})
.then((res) => {
if (res.status !== 401) return res.json().then((data) => data);
return { message: { msgBody: 'UnAuthorized' }, msgError: true };
})
.then((posts) => dispatch({
type: FETCH_USER_POSTS,
payload: posts,
}));
};
reducer
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_USER_POSTS:
return {
...state,
fetchUsersPosts: action.payload,
};
default:
return state;
}
}
You can combine multiple items into a single payload. I would create different actions for success and error. Using the same action complicates the reducers logic. It's also easier to work with async/await then with nested promises.
This is a working example that uses SpaceX open API:
const FETCH_USER_POSTS_SUCCESS = 'FETCH_USER_POSTS_SUCCESS'
const FETCH_USER_POSTS_FAILED = 'FETCH_USER_POSTS_FAILURE'
const fetchPostSuccessAction = (payload) => ({
type: 'FETCH_USER_POSTS_SUCCESS',
payload,
})
const fetchPostFailureAction = (payload) => ({
type: 'FETCH_USER_POSTS_FAILURE',
payload,
})
const fetchUserPosts = () => async dispatch => {
const res = await fetch('https://api.spacexdata.com/v3/launches/latest');
if (res.status !== 401) {
const { ships: posts } = await res.json();
dispatch(fetchPostSuccessAction({
posts,
status: res.status,
}))
} else {
dispatch(fetchPostFailureAction({
message: { msgBody: 'UnAuthorized' },
}))
}
};
fetchUserPosts()(console.log)
The reducer can handle the object by destructuring it, and the properties to the new state in any way you need. You can also change other properties, for example changing errMsg to true or false according to the action's type:
export default function (state = initialState, { type, payload }) {
switch (type) {
case FETCH_USER_POSTS_SUCCESS: {
const { posts, status } = payload;
return {
...state,
status,
fetchUsersPosts: posts,
msgError: false,
message: null
};
}
case FETCH_USER_POSTS_FAILURE: {
const { message } = payload;
return {
...state,
status: 401,
fetchUsersPosts: null,
msgError: true,
message
};
}
default:
return state;
}
}
If I am following correctly you are using this action inside of a component to send a fetch. You don't have access to the components props with the reducer. You can send the http request in the component and use that to store the response in the state. Or use connect from 'react-redux' package to map the redux store to access the fetch result.
import { connect } from 'react-redux'
const component = props => {
//to access redux state in component use props.myprop
return <div>{props.myprops.title}</div>
}
const mapStateToProps = state => {
return{
myprop: state.fetchUsersPosts
}
}
export default connect(mapStateToProps)(component)
If this was what you were looking for you can learn more at https://react-redux.js.org/api/connect

Redux Thunk - Get Updated Store in Promise

I'm having an issue trying to use Redux Thunk for implementing JWT Authentication in my app: when I perform login and retrieve the promise from the action, I do not get the updated store (I still have the previous value).
Here is my code:
const Login = ({ doLogin, token }) => {
const submitForm = (e) => {
doLogin(email, password).then(function () {
console.log(token);
});
}
};
return (
// Some JSX that calls submitForm()
);
};
Login.propTypes = {
token: PropTypes.string.isRequired,
doLogin: PropTypes.func.isRequired,
};
function mapStateToProps(state) {
console.log(state);
return {
token: state.auth.access_token,
};
}
const mapDispatchToProps = { doLogin };
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Here is my action creator:
export function doLogin(email, password) {
return (dispatch) => {
dispatch(start());
return axios
.post(config.api.url + "/login", {
email,
password,
})
.then((success_rsp) => {
if (success_rsp.data.success) {
dispatch(success(success_rsp.data.access_token));
} else {
dispatch(error());
}
})
.catch((error_rsp) => dispatch(error()));
};
function start() {
return { type: types.AUTH_LOGIN };
}
function success(access_token) {
return {
type: types.AUTH_LOGIN_SUCCESS,
is_authenticated: true,
access_token: access_token
};
}
function error() {
return {
type: types.AUTH_LOGIN_ERROR,
};
}
}
And here is my reducer:
const authReducer = (state = initialState.auth, action) => {
switch (action.type) {
case types.AUTH_LOGIN:
return {
...state,
is_loading: true,
};
case types.AUTH_LOGIN_SUCCESS:
return {
...state,
is_loading: false,
is_authenticated: action.is_authenticated,
access_token: action.access_token
};
case types.AUTH_LOGIN_ERROR:
return {
...state,
is_loading: false,
is_authenticated: false,
access_token: "",
};
default:
return state;
}
};
The problem is that when I get in the Promise of my Login component, it logs the previous value of the token, and not the one received by the API.
However, when I log the state in the mapStateToProps function, I see that the function is called 3 times in total (one when arriving on the page, one when starting the doLogin, and one on the success), and it logs the right value for the token.
Do you know why the token I get is not updated? And how I could get an updated one?
Thank you!

Call multiple actions jointly in React Redux

I am learning React Redux. My Action is like below
import Axios from 'axios';
export const getAddress = valueModal => dispatch => {
return Axios.get('/api/address')
.then(response => {
var addressData = response.data;
dispatch({
type: 'getAddresses',
payload: { addressData, valueModal }
});
})
.catch(function(error) {
console.log(error);
});
};
export const uploadImage = (formData, id, config) => dispatch => {
return Axios.post('/api/address/upload', formData, config)
.then(response => {
dispatch({
type: 'uploadImage',
payload: response.data
});
})
.catch(function(error) {
console.log(error);
});
};
export default { getAddress, addAddress, uploadImage };
My Reducer is like below
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
I would like to call getAddresses and uploadImage jointly. How can I do that ?
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
...state,
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
...state,
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
You need to spread the object state out otherwise there is never a reference to the state before the update.
The object spread syntax lets you use the spread ... operator to copy enumerable properties from one object to another in a more succinct way.

Add multiple parameters to URL API fetch in Redux?

I am trying to add multiple parameters to a API fetch using Redux. I am able to get one to work (maxDist you see below in the API call), but I want to add the minStars and maxRes parameters as well, but am unsure how I would add more than one. I tried adding more parameters to the search() function, but it will only use one argument. Here is what I have so far:
API Call:
export default {
search(maxDist) {
const url = `${API_URL}&maxDistance=${maxDist}&minStars=${minStars}&maxResults=${maxRes}`;
console.log(url);
return fetch(url)
.then(response => response.json())
.then(result => {
return result.items;
});
}
};
Redux Side:
const MAX_DIST_CHANGED = "MAX_DIST_CHANGED";
const initialState = {
items: [],
maxDist: "",
minStars: "",
maxRes: ""
};
export const actions = {
maxDistChanged(maxDist) {
return {
type: MAX_DIST_CHANGED,
maxDist
};
getItems(maxDist) {
return {
type: "ITEMS",
payload: API.search(maxDist)
};
}
};
export function reducer(state = initialState, action) {
switch (action.type) {
case MAX_DIST_CHANGED: {
return {
...state,
maxDist: action.maxDist
};
}
case "ITEMS_FULFILLED": {
return {
...state,
loading: false,
items: action.payload
};
}
default:
return state;
}
}
App.js:
handleSubmit = e => {
e.preventDefault();
this.props.onGetItems(
this.props.maxDist
);
};
handleChange = event => this.props.onMaxDistChanged(event.target);
function mapDisatchToProps(dispatch) {
return {
onMaxDistChanged(maxDist) {
dispatch(actions.maxDistChanged(maxDist));
}
onGetItems(maxDist) {
dispatch(actions.getItems(maxDist));
}
};
}
How do I add the minStars and maxRes in the api call? Only one parameter seems to works. Thank you!

Resources