should i use action or create reusable function for CRUD in React Hook - reactjs

I am trying to understand React hook (useReducer, useEffect), what is the best way to create crud to hit on API with React hook?
should I create the function for fetch, delete, add, update in action ? then call it on reducer ? or create another reusable function and call it on the action ? or create reusable function crud and use it on components?
let say that I want to add a task
async function addTask(payload){
try {
const { data } = await axios.post(endppoint, payload)
return data
} catch(e){
console.log(e)
}
}
async function getTask() {
try {
const { data } = await axios.get(endppoint)
return data
} catch(e){
console.log(e)
}
}
where i should call those functions if I use React Hook?
i have read some articles but i just want to make sure those, for example, to hit to API

Related

What is the best practice for useAxios hook with Redux's slice?

I've created a useAxios hook to use for Axios calls and also reduxjs/toolkit for handling React data. Is it the correct practice to use them together?
For example, in slice.js
export const getTodo = (data) => async (dispatch) => {
try {
const response = await axios.get(`${URL}/${data}`);
dispatch(getTodo(response.data));
} catch (err) {
throw new Error(err);
}
};
When I replace await axios.get(`${URL}/${data}`); to useAxios()hook, I got the error: hooks can only be called inside of the body of a function component.
What is the best practice for Redux to use the Axios hook? or it's not possible? Any good example? Thanks!
*The only place you can ever call a React hook is inside of a function component, or another hook. You can never call a hook anywhere else.

Is it possible to use i18n localization for redux Actions?

I was trying to implement i18n localization for Redux Action functions, but i constantly recieve different hook errors. When i implemented i18n this way i recieve error.
Line 428:17: React Hook "useTranslation" is called in function "sendDataRequest" that is neither a React function component nor a custom React Hook function.
import { useTranslation } from "react-i18next";
export const sendDataRequest = (requestId) => {
const { t } = useTranslation();
return async (dispatch) => {
try {
dispatch(sendDataRequest());
await dataAPI.sendDataRequest({ requestId });
notification.success({
message: t('infoPage.requestSentSuccessfully'),
});
dispatch(sendDataSuccess());
} catch (error) {
dispatch(sendDataFailure());
console.error(error);
}
}
}
Then i moved const { t } = useTranslation(); inside of return statement.
But then i recieved another error
It looks like I obviously using it wrong here.
But i cannot find any examples on i18n being used in Actions.
Does anyone knows is it even possible to use i18b in Actions?
Check out https://github.com/i18next/react-i18next/issues/909
You need to gain access to your i18next instance. The one you create somewhere in your codebase. With this instance you can simply call i18n.t("my.translation.key") to translate. This is completely independent from react.

React Redux: Updating Component on Multiple Dependent Props

I have a react component, let's call it Documents. In this component I need to load multiple pieces of dependent data using fetch. I am using redux thunk for these async redux actions that perform data fetching.
So the component looks like this:
interface Props {
comments: Entity[];
documents: Entity[];
users: Entity[];
getComments: Function;
getDocuments: Function;
getUsers: Function;
}
export class Documents<Props> {
public async componentDidMount(props: Props){
// is this the right lifecycle method for this since
// it does not really need to change state
const { documents, users, comments, getDocuments, getUsers, getComments} = props;
if(!documents || !documents.length){
await getDocuments();
} else {
await getUsers(documents.map(d => d.username));
}
getComments(documents, users); // dependent upon both users and documents
}
public render() {
// render documents, users, and comments
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Documents);
Here I need to look at the documents before I can load users. And Both users and documents before I can load comments. What's the best approach for handling this in react. My render function will load small sub-components responsible for rendering these data but I don't want them to be connected components.
I think I was able to accomplish it with a functional component/useEffect hooks:
https://codesandbox.io/s/festive-varahamihira-kpssd?file=/src/Documents.js
useEffect(() => {
if (!documents || !documents.length) {
getDocuments();
} else {
getUsers(documents);
}
}, [documents]);
useEffect(() => {
if (users) {
getComments(documents, users);
}
}, [users]);

How do I replace route in react redux after thunk error response?

I am using react with redux. In the redux layer I am using thunk. So component calls action, redux action calls a new layer -> service. The service handles http calls. When response is ok - I use the dispatch that thunk provides and return to the reducer. when the response is error I want to redirect the user and I couldn't find a way to do so. I am missing the router and its replace method.
You could catch inside the component which triggered the action creator
function doAction() {
return (dispatch) => {
return fetch(...).then(...)
}
}
class Component extends React.Component {
onClickButton = () => {
this.props.doAction().catch(() => {
this.props.router.replace(...)
})
}
}
I'm assuming you've connected properly and router is being passed in as a prop (or a context - doesn't really matter so long as it's available in the component)
one downside to this is that you have to replicate this functionality whenever you use that action creator. You could provide the router as an argument to the action creator, but I'm not sure if that's against standard practice/an anti-pattern.
Another option is to hard-redirect using window.location
Ok, I think I've got it. I created a general redux action called ResponseError. I will dispatch this action in all my services, every time an HTTP call returns with error. Then in the reducer I'll just turn on a boolean flag on the store and in the main application page I'll listen to this flag. If it is on -> do whatever you want.
If you are using axios for the service that handles http calls, you can use its interceptor. This way, you do not need to have a handler on every action/reducer-action.
import axios from 'axios';
axios.interceptors.response.use(
res => interceptResponse(res),
err => interceptError(err)
);
const interceptResponse = response => {
// nothing to do here, return back response
return response;
};
const interceptError = error => {
if (error.response.status === 404 || error.response.status === 403) {
// redirect to another react router page
history.push('/error404');
} else if (error.response.status === 401) {
return Promise.reject(error);
}
};
Referenced from my sample project on GitHub: https://github.com/mosufy/serverless-react/blob/develop/client/site/src/lib/sdk.js#L90

Display loading state and change route when API call is successfull

While working on a side project, I faced an issue with react-router-dom.
What I want to implement is: When I submit a Form, I need to save the data on my server. While the request is pending, I need to display a loading indicator. Once the server says everything is ok, I need to redirect the user on a new page
action.js
export const addNotification = value => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
} catch(e) {
dispatch(addNotificationFailure())
}
}
component.js
class CreateNotificationForm extends Component {
onSubmit = (values) => {
this.props.addNotification(parameters, history)
}
render() {
const { isCreating } = this.props
const submitBtnText = isCreating ? 'Creating...' : 'Submit'
return (
<Form>
// content omitted
<Submit value={submitBtnText} />
</Form>
)
}
}
const mapStateToProps = (state) => ({
isCreating: getIsFetching(state)
})
const mapDispatchToProps = (dispatch) => ({ // omitted })
connect(mapStateToProps, mapDispatchToProps)(CreateNotificationForm)
So far so good: When I submit my form, the form's submit button shows a Creating... text.
However, how do I tell react-router to load a new path once the request is successful?
Right now, I've done that by using withRouter and using this.props.history as a second argument for this.props.addNotification.
It works great, but it seems really wrong
I've seen solutions using react-router-redux, but I don't really want to add a new middleware to my store.
Should I make the API call inside my component and use a Promise?
Any help?
Update:
After working a little on my own React project, and thinking about similar situations where I handle route changes there, I decided I want to change my original answer. I think the callback solution is OK, but the solution that you already mentioned of making the API call inside your component and using a promise is better. I realized that I've actually been doing this in my own app for a while now.
I use redux-form in my app, and it provides onSubmitSuccess/onSubmitFail functions that you can use to handle the submit result, and each of those rely on you returning a promise (usually from your action creator).
I think the fact that one of the most popular packages for form submission in React/Redux supports this pattern is an indication that it's probably a good pattern to use. Also, since react-router passes history into your component, it seems logical that they expect most people to do a lot of their programmatic route changes inside the component.
Here's an example of what the promise solution would look like with your code:
action.js
export const addNotification = value => dispatch => {
return new Promise(async (resolve, reject) => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
resolve(response)
} catch(e) {
dispatch(addNotificationFailure())
reject(e)
}
})
}
component.js
onSubmit = async () => {
try {
await this.props.addNotification(parameters)
this.props.history.push('/new/route')
} catch(e) {
// could use a try/catch block here to display
// an error to the user here if addNotification fails,
// or go to a different route
}
}
Old Answer:
A simple solution would be to allow addNotification() to accept a callback function as an optional second argument.
export const addNotification = (value, callback=null) => async dispatch => {
dispatch(addNotificationPending())
try {
const response = await client.createNotification(values)
dispatch(addNotificationSuccess(response))
(typeof callback === 'function') && callback()
} catch(e) {
dispatch(addNotificationFailure())
}
}
Then inside your component use the router to go to the new route.
onSubmit = (values) => {
this.props.addNotification(parameters, () => {
this.props.history.push('/new/route')
})
}
You should not write your asynchronous calls in reducers or actions as the documentation clearly suggests them to be pure functions. You will have to introduce a redux-middleware like redux-thunk or redux-saga (I personally prefer sagas)
All your async calls will happen inside the middleware, and when it succeeds, you can use react-routers history .replace() or .push() methods to update your route. Let me know if it makes sense
You can use one popular package axios
See Here https://www.npmjs.com/package/axios
and you can implement your login like
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
You can write your loader login while calling api
and then you can hide your loader in .then

Resources