Making too many request await Axios (ReactJS) - reactjs

On the outside it seems not an issue, but when I open the DevTools and then go to network tab. It shows that there are 500 requests made. So how can I refactor the code so this will not happens?
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
"https://raw.githubusercontent.com/XiteTV/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
console.log('input');
} catch (err) {
console.log(err);
}
};
fetchData();
}, [dispatch]);

with redux first create a function which will push your request data into redux state like that outside your react component
export const getMyData = () => {
return async (dispatch) => {
const response = await axios.get(
"https://raw.githubusercontent.com/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
}
}
create a function that will extract data from redux. state is redux current state, ownProps is your component props, like <Component customProp={1}/>
const mapStateToProps = (state: any, ownProps: any) => {
return {
...ownProps,
myProps: state.user
};
};
In your connect Function pass the function which will store your data in redux state
export default connect(mapStateToProps, { getMyData })(MyReactComponent);
that way you'll be able to access your data via your component props and also access your getMyData function and also the redux state you mapped to props
props.getMyData();

Related

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

Cannot dispatch actions initially when app loads

I have a mern app and i'm using redux to maintain state of my posts, I want to fetch all data from my api at first run of the app (when the app component loads initially) but I can't achieve it. It only works when I post something and it fetches the post, but it doesn't fetch all the posts from db initially.
After struggling for a day I decided ask here.
This is my component tree:
In my PostsBody, I want to fetch all the posts from the database whenever the app loads initially (this is not happening) and then whenever there is a change in state like create, delete it should fetch the updated posts (this is happening).
This is my PostsBody component:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from 'react-redux';
import Post from "./Post";
import { getPostsAction } from '../actions/posts';
const PostsBody = () => {
const dispatch = useDispatch();
// fetching posts
useEffect(() => {
dispatch(getPostsAction);
}, [dispatch]);
const posts = useSelector((globalState) => globalState.postsReducer);
console.log(posts); // intially empty when the app reloads/renders.
return (
// simply posts.map to display individual posts
);
}
export default PostsBody;
Action:
export const getPostsAction = () => async (dispatch) => {
try {
const { data } = await getAllPosts();
const action = {
type: 'GET_ALL',
payload: data,
}
dispatch(action);
} catch (error) {
console.log(error.message);
}
}
GET CALL:
import axios from 'axios';
const url = "http://localhost:5000/users";
export const getAllPosts = () => axios.get(url);
Reducer:
const postsReducer = (posts=[], action) => {
switch (action.type) {
case 'GET_ALL':
return action.payload;
case 'CREATE':
return [...posts, action.payload];
default: return posts;
}
}
export default postsReducer;
I repeat, the only problem is, it is not fetching all the posts from db initially when the app renders, after that when I create a new post it does fetch that post (not all from db).
Issues
It doesn't appear as though you are invoking the getPostsAction action creator correctly. Also, with only dispatch in the useEffect's dependency array the hook callback will only be invoked once when the component mounts.
Solution
Invoke the getPostsAction action.
useEffect(() => {
dispatch(getPostsAction()); // <-- invoke
}, [dispatch]);
Now this still only solves for fetching posts from the DB when the component mounts, but not when new posts are POST'd to your backend.
I've looked at your actions and state. Normally you would include another variable in the useEffect dependency array to trigger the effect callback to execute again, but I think a simpler way is possible. Instead of POST'ing the new post and dispatching the CREATE action you should POST the new "post" and immediately GET all posts and dispatch the GET_ALL action instead with that data.
export const createPostAction = (newPostData) => async (dispatch) => {
try {
await createPost(newPostData);
getAllPosts()(dispatch);
} catch (error) {
console.log(error.message);
}
}
I've basic familiarity with Thunks, but if the above doesn't work then you may need to duplicate some behavior, or factor it out into some common utility code used by both action creators.
export const createPostAction = (newPostData) => async (dispatch) => {
try {
await createPost(newPostData);
const { data } = await getAllPosts();
const action = {
type: 'GET_ALL',
payload: data,
}
dispatch(action);
} catch (error) {
console.log(error.message);
}
}

How to stop component re-render when redux store change

When I dispatch an action from a child component that triggers a change in the Redux store, the whole tree gets re-render including the child component itself. How can I stop that?
For example ...
1- I'm listening for when a user clicks on a child component to expand it.
const [expanded, setExpanded] = useState(false);
<span onClick={() => setExpanded(!expanded)}>Expand</span>
2- Then if expanded is true I dispatch an action in useEffect ...
useEffect(() => {
if (expanded) {
dispatch(asyncGetItems())
}
}, [expanded]);
3- Then inside the async dispatch it changes the redux store based on the returned data ...
export const getItems = (items: {}): AppActions => {
return {
type: GET_ITEMS,
payload: items,
};
};
export const asyncGetItems = () => {
return async (dispatch: any, getState: () => AppState) => {
try {
const { data } = await apiGetItems();
dispatch(getItems(data));
} catch (error) {
handleResponseError(error);
}
};
};
Now, when I do that and change the data in the store the component gets re-rendered because its parents get re-rendered, how to stop that?
I tried to use memo like this, but it didn't work ...
export default memo(MyComponent);
And when I don't change anything in the store and just return the data like this ...
export const asyncGetItems = () => {
return async (dispatch: any, getState: () => AppState) => {
try {
const { data } = await apiGetItems();
return data;
} catch (error) {
handleResponseError(error);
}
};
};
The component doesn't get re-rendered.

Redux ToolKit: is it possible to dispatch other actions from the same slice in one action created by createAsyncThunk

I am using redux-toolkit with createAsyncThunk to handle async requests.
I have two kinds of async operations:
get the data from the API server
update the data on the API server
export const updateData = createAsyncThunk('data/update', async (params) => {
return await sdkClient.update({ params })
})
export const getData = createAsyncThunk('data/request', async () => {
const { data } = await sdkClient.request()
return data
})
And I add them in extraReducers in one slice
const slice = createSlice({
name: 'data',
initialState,
reducers: {},
extraReducers: (builder: any) => {
builder.addCase(getData.pending, (state) => {
//...
})
builder.addCase(getData.rejected, (state) => {
//...
})
builder.addCase(
getData.fulfilled,
(state, { payload }: PayloadAction<{ data: any }>) => {
state.data = payload.data
}
)
builder.addCase(updateData.pending, (state) => {
//...
})
builder.addCase(updateData.rejected, (state) => {
//...
})
builder.addCase(updateData.fulfilled, (state) => {
//<--- here I want to dispatch `getData` action to pull the updated data
})
},
})
In my component, I have a button that triggers dispatching of the update action. However I found after clicking on the button, despite the fact that the data is getting updated on the server, the data on the page is not getting updated simultaneously.
function MyComponent() {
const dispatch = useDispatch()
const data = useSelector((state) => state.data)
useEffect(() => {
dispatch(getData())
}, [dispatch])
const handleUpdate = () => {
dispatch(updateData())
}
return (
<div>
<ul>
// data goes in here
</ul>
<button onClick={handleUpdate}>update</button>
</div>
)
}
I tried to add dispatch(getData()) in handleUpdate after updating the data. However it doesn't work because of the async thunk. I wonder if I can dispatch the getData action in the lifecycle action of updateData i.e.
builder.addCase(updateData.fulfilled, (state) => {
dispatch(getData())//<--- here I want to dispatch `getData` action to pull the updated data
})
Possibly it's not actual and the question is outdated, but there is thunkAPI as second parameter in payload creator of createAsyncThunk, so it can be used like so
export const updateData = createAsyncThunk('data/update', async (params, {dispatch}) => {
const result = await sdkClient.update({ params })
dispatch(getData())
return result
})
First of all: please note that reducers always need to be pure functions without side effects. So you can never dispatch anything there, as that would be a side effect. Even if you would somehow manage to do that, redux would warn you about it.
Now on to the problem at hand.
You could create a thunk that dispatches & awaits completion of your updateData call and then dispatches your getData call:
export const updateAndThenGet = (params) => async (dispatch) => {
await dispatch(updateData(params))
return await dispatch(getData())
}
//use it like this
dispatch(updateAndThenGet(params))
Or if both steps always get dispatched together anyways, you could just consider combining them:
export const updateDataAndGet = createAsyncThunk('data/update', async (params) => {
await sdkClient.update({ params })
const { data } = await sdkClient.request()
return data
})

How to access async function from Redux Action passed into Redux Thunk?

I am trying to refactor code from Javascript React to Typescript React.
I have an action here which performs an API call and returns a function with dispatch
My UserActions.ts file looks like this
export const login = ({username, password}) => async (dispatch: any) => {
try {
let r = await apiCall(); //performing api call here
return r.code; //returning the custom responseCode here from my server
console.log(auth)
} catch (err) {
throw new Error(err);
}
}
In my component file MyComponent.ts there is a member function for the component class
public formSubmit = (e: React.FormEvent) => {
e.preventDefault();
const { password, email } = this.state;
this.props.login({
email,
password,
}).then(responseCode => {
//I want to access responseCode here
});
}
The connection to Redux looks like this in MyComponent.ts
const mapStateToProps = (store: IAppState) => {
return {
}
}
export default connect(mapStateToProps, {
login
})(SignIn);
So, as you can see, the login actually returns a function which is passed into Redux Thunk Middleware and is then passed in this.props to MyComponent
To access return variable from function, I have to type the outer function in login action UserActions.ts.
So how do I access with proper types ? Because typescript won't allow me to put this.props.login().then() in MyComponent.ts
The best practise is to dispatch action and populate the data to reducer. Try to avoid passing data back from async functions. You can have a AuthReducer and update the data and use it directly in the component.
export const login = ({username, password}) => async (dispatch: any) => {
let r = await apiCall(); //performing api call here
dispatch({ type: 'UPDATE_AUTH', payload: auth })
}
and in your reducer
case 'UPDATE_AUTH':
return { ...state, ...action.payload }
and in your component
const mapStateToProps = ({ auth }) => ({ auth })
export default connect(mapStateToProps, {
login
})(SignIn);
I didn't know the actual Typescript way to do it, so I used the any workaround
Instead of doing
this.props.login({
email,
password,
}).then(responseCode => {
//I want to access responseCode here
});
You can just do
const responseCode:any = await this.props.login({email, password});
Typescript is checking await here and I'm casting the returned object to any and in runtime the actual responseCode is returned from Redux Thunk

Resources