Do you need action creators when working with Redux hooks? - reactjs

This is an odd question, but I haven't been able to find an explicit answer for it.
In my project I'm using Redux with new Redux hooks, useSelector and useDispatch. The setup is as standard as it gets:
Reducer:
const reducer = (state = defaultState, action) => {
switch (action.type) {
type SOME_ACTION:
return {
...state,
someVariable: action.payload
}
default:
return state
}
Synchronous actions:
export function someAction(payload) {
return {
type: SOME_ACTION
payload: payload
}
And in the components I dispatch actions using
dispatch({type: SOME_ACTION, payload: payload}).
Recently I've noticed that dispatching works just fine even if the action creator doesn't exist: as in, if the SOME_ACTION type is listed in the reducer switch operator, but there is no function someAction(payload).
The question is: is writing the action creators redundant in this case? What are the best practices when working with Redux hooks?

You can certainly use action object literals
dispatch({ type: 'MY_SUPER_AWESOME_ACTION });
But creating action creators allow you to reuse code more efficiently.
const mySuperAwesomeAction = () => ({ type: 'MY_SUPER_AWESOME_ACTION });
And now anywhere in your app where you need to dispatch this action you import the action and use
dispatch(mySuperAwesomeAction());
As apps grow and actions are used in multiple components if you were using object literals you would need to find/replace all instances of the action literal, and hope you didn't typo any of them. With the single action creator definition you need only update it in a single location.

Related

how action contacted the reducer to change the state of the object

I am sorry it might be a silly question but i want to know i have redux store first let me explain the data flow there
The data is getting store at a global level state which i have in my store.js which have been declare in my productReducer.js There i define a switch statement and there i alter the state of the product
productReducer.js
my code here
import {
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_FAIL,
CLEAR_ERROR
} from '../constants/productConst'
export const productReducer = (state ={product:[]},action)=>{
switch (action.type) {
case PRODUCT_LIST_REQUEST:
return{
laoding: true,
product:[],
}
case PRODUCT_LIST_SUCCESS:
return{
laoding: false,
product:action.payload.products,
productsCount:action.payload.productCount,
}
case PRODUCT_LIST_FAIL:
return{
laoding: false,
error:action.payload,
}
case CLEAR_ERROR:
return{
...state,
error:null
}
default:
return {
...state,
}
}
}
i have action productAction.js
import axios from 'axios'
import {
PRODUCT_LIST_SUCCESS,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_FAIL,
CLEAR_ERROR
} from '../constants/productConst'
export const getProduct = () => async (dispatch) =>{
console.log("Been executed at the action")
try {
dispatch({type:PRODUCT_LIST_REQUEST})
const {data} = await axios.get("api/v1/products")
dispatch({
type:PRODUCT_LIST_SUCCESS,
payload:data,
})
} catch (error) {
dispatch({
type:PRODUCT_LIST_FAIL,
payload:error.response.data.message,
})
}
}
export const clearError =() =>async (dispatch)=>{
dispatch({type:CLEAR_ERROR})
}
Let me Sum up my question when i neeed to update the state from the frontend i call the action but there is no way the action and the reducer connected together how the state of the product is altered in my case
To answer your question "how do actions contact the reducer to change the state of the object?":
The overall redux store setup enables this, especially how you registered your productReducer.
Let's go through the typical flow to illustrate how everything is connected:
Somewhere in a React component, a user-interaction (e.g. button click) or something automatic dispatches the async getProduct() action. This is possible because getProduct is either a prop of the component (Redux' connect API) or you're using the useDispatch hook.
The store setup knows that PRODUCT_LIST_SUCCESS is handled by your productReducer. We go through the switch statement and now state.product.product holds an array of products (careful with the naming there btw, plural vs. singular).
Any React component interested in state.product.product is now notified that the state has changed. They're getting notified because either the connect API (mapStateToProps) or useSelector connects the state with the mounted React component. The products can now be (re-)rendered, or clicked on, etc..
Action
An action is a plain JavaScript object that has a type field. You can think of an action as an event that describes something that happened in the application.
Reducer
You can think of a reducer as an event listener which handles events based on the received action (event) type.
By using dispatch() you are dispatching the event and then comes the following in the reducer logic:
Check to see if the reducer cares about this action
If so, make a copy of the state, update the copy with new values, and return it
Otherwise, return the existing state unchanged
If you are interested about more then please have a look into official redux documentation, there is really everything you will need.

How to call another autogenerated action within an autogenerated action of redux-toolkit

I'm developing a small game. And so i used the redux-toolkit for the first time. (i'm used to redux)
this createSlice method is awesome but i cannot figure out how to call actions inside actions.
so the actions are autogenerated and therefore not available before generation.
example:
const gameSlice = createSlice({
name: 'game',
initialState,
reducers: {
startNewGame: (state) => {
state.activeTeam = state.config.startTeam = random(0, 1)
state.winner = undefined
},
endRound: (state, action, dispatch) => { // last param not available
// ... code that handles the rounds inputs and counts points for each team ...
if(state.red >= 30) {
// HOW?! Here's something i tried
endGame(state, Team.Red)
dispatch({type: endGame, payload: Team.Red})
dispatch({type: gameSlice.endGame, payload: Team.Red})
}
if(state.blue >= 30) {
// HOW?! Here's something i tried
endGame(state, Team.Blue)
dispatch({type: endGame, payload: Team.Blue})
dispatch({type: gameSlice.endGame, payload: Team.Blue})
}
},
endGame: (state, action) => {
state.winner = action.payload
}
}
})
This is just a small example. There is nothing async and no backend-call. I just want to nest action-calls. Simple as hell is suppose... but i have no idea how to do it in a KISS-way.
I read something with extraReducers and createAsyncThunk but i won't do this for every action as i have no async stuff in here and because then there is no benefit in using redux-toolkit anymore. It's just a hell of more code for nothing.
I think i'm blind or stupid or just confused... but this drives me crazy, right now.
Without redux-toolkit this was easy.
Any help is appreciated.
You can never dispatch more actions inside of a Redux reducer, ever.
However, you can write additional helper functions to do common update tasks in reducers, and call those functions as needed. This can be especially helpful when using Immer-powered reducers in RTK's createSlice.
Additionally, you can call other case reducers as needed, by referring to the slice.caseReducers object like:
reducers: {
endRound(state, action) {
if (someCondition) {
// "Mutate" state using the `endGame` case reducer
mySlice.caseReducers.endGame(state)
}
},
endGame(state, action) {}
}

Component isn't reloading after deleting an object from the state, I'm using React-redux

I'm working on an online streaming app where the user can create, edit, delete, and host streams. The problem is when I try deleting the stream, it deletes it in the database but my main component where the streams are being displayed doesn't reload, although it reloads itself when the user creates or edits the stream.
Here's my action creator:
export const deleteStream = (id) => async dispatch => {
await streams.delete(`/streams/${id}`);
dispatch({ type:'DELETE_STREAMS', payload: id });
history.push('/');
}
And here's the reducer:
const streamReducer = (state = {}, action) => {
switch(action.type){
case 'DELETE_STREAM':
return {...state, [action.payload]: undefined};
//I also used lodash to delete it alternatively as- return _.omit(state, action.payload);
default:
return state;
}
}
Also, not to forget that the objects are key interpolated in my server i.e., instead of having an array of objects, I have an object of objects.
Plural problem! DELETE_STREAMS vs DELETE_STREAM. You dispatch the former and reduce on the latter.
This is why it's always a good idea to have your actions defined somewhere even of its just export const X = "X". Then always reference them instead of using string literals.
It wasn't an error updating your React rendering, but that redux was never updated. A great tool to debug this is redux devtools https://github.com/reduxjs/redux-devtools, you can see the state of redux and every dispatched action and it's impact.

How to chain redux actions using returned result of the previous action?

I'm building an app in React Native, and using Redux with redux-persist to act as on device database.
The crux of the issue is, how do I return the result of a redux action, to then dispatch another action with this data? Read on for more specifics.
The user can create custom habit types. When doing so, I dispatch an action to create a habit type in the store (e.g. "running"). This action generates a new unique UUID for the habit type. I then want to add this newly created habit type to a routine (e.g. "morning routine"), and so I need to receive back the UUID of the habit type and call another dispatch to add it to the routine.
I'm using immer to make manipulating the state in my reducers simpler, and have this code (simplified example):
import produce from "immer";
const userReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_CUSTOM_HABIT_TYPE: {
return produce(state, draftState => {
const newHabitType = {
id: generateUuid(),
name,
};
draftState.customHabitTypes.push(newHabitType);
return draftState;
});
}
}
};
I'm then dispatching it in my component, like so (simplified):
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
name: "running",
});
How can I then say, after creating this new habit type, to dispatch another action and add it to my routine?
I've looked at redux-thunk and redux-saga, and spent hours reading about these and trying to get redux-thunk to work, but to no avail. I'm sure this must be simple, but I'm coming up blank, and so maybe others are too, hence this post.
A very simple solution would be to generate the unique id before dispatching the action.
Example
const newHabitType = {
id: generateUuid(),
name,
};
dispatch({
type: ADD_CUSTOM_HABIT_TYPE,
habit: newHabitType,
});
dispatch({
type: ADD_CUSTOM_HABIT_TO_ROUTINE,
habit: newHabitType.id,
});
Pros
You no longer need to chain actions per se, you just need to dispatch them in order.
This preserves one of the most important Redux guidelines: your reducer should not have any side effects (in your case, generating a random id). reference
Cons
If you create the new habits in multiple places, you will have to generate the unique ids in every place where you dispatch the action. This might lead to repeated code. The solution to this would be to encapsulate the whole logic for creating the habits to a single component and then reuse this component everywhere.
Actions do not return data per se, the are simply objects which mutate the store based on the rules defined in the reducer. Two possible solutions:
Option A, create a composite action.
const compositeAction = args => {
return dispatch => {
return someAsyncCall(args).then(response => {
dispatch(addCustomHabitat(response))
dispatch(followUpAction())
}
}
}
const addCustomHabitat = response => {
return {
type: "ADD_CUSTOM_HABIT_TYPE",
data: response
}
}
const followUpAction = () => {
...another action...
}
Option B, connect the results of the first action to the dispatching component through react-redux and pass them to the second action.
import {connect} from 'react-redux';
const MyReactComponent = props => {
dispatch(addCustomHabitatTypeAction());
if(props.customHabitatType !== undefined)
dispatch(followUpAction(props.customHabitatType());
return (
...JSX here...
);
}
const mapStateToProps = state => {
return {
customHabitatType: state.userReducer.customHabitatType
}
}
connect(mapStateToProps)(MyReactComponent);
I hope this helps! Please excuse my abbreviated code and let me know if you have any questions.

How to make API calls using React, Redux in TypeScript

I'm using typescript-fsa and typescript-fsa-reducers packages to simply create actions and reducers in TypeScript React application.
const actionCreator = actionCreatorFactory();
export function signInHandler(state: UserState, action: Action): UserState {
// ????
return { ...state };
}
export const signIn = actionCreator.async<SignInRequest, RequestResponse<SignInResponse>>("USER_SIGNIN");
export const UserReducer = reducerWithInitialState({ signedIn: false } as UserState)
.casesWithAction([signIn.started, signIn.done], signInHandler)
.build();
Usage in component:
export default connect<StateProps, DispatchProps>(
(state: RootState) => ({} as StateProps),
(dispatch: Dispatch<RootState>) => {
return {
signIn: (userName: string, password: string) => dispatch(signIn.started(new SignInRequest(userName, password)))
};
}
)(SignIn);
And now I'm stuck. I don't know how to make HTTP calls to my API so I can send request when component dispatches action on dispatch next action when response from API arrives. I would like to use promises.
How to solve that?
In React without the typescript-fsa abstraction, you'd make async API callsat the action creator level, since actions are just dispatched POJOs and reducers are supposed to not have any side effects.
There are two projects that make it easy to do this, redux-thunk and redux-saga. I prefer redux-thunk because it is easier to wrap your head around. Basically your action creators get passed the dispatch function, and then they can be responsible for dispatching more than one thing... like so:
function asyncActionCreator(dispatch) {
dispatch(startAsyncAction());
doSomethingAsync()
.then(result => dispatch(completeAsyncAction(result))
.catch(err => dispatch(errorAsyncAction(err));
}
In your typescript-fsa world, there are some companion packages for both of these: typescript-fsa-redux-thunk and typescript-fsa-redux-saga.
It appears that typescript-fsa-redux-thunk takes a similar approach to the above example, using the concept of an "action worker", which coordinates the dispatching of actions via typescript-fsa. There is a really good example of doing this on the typescript-fsa-redux-thunk repo.

Resources