Redux Toolkit generate and return an id (return data from action) - reactjs

I'm trying to create an object and add it to a reducer, but have the action/reducer take care of generating the id.
Per this answer, it seems the accepted pattern is to generate the id in the action creator:
const todosSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
addTodo: {
reducer(state, action) {
state.push(action.payload);
},
prepare(text) {
const id = uuid();
return { payload: {text, id} };
}
}
}
})
However, suppose I want to then use / refer to the id after creating the todo, something like
dispatch(addTodo('Say hello world')) // creates a todo with a uuid
...
id = <some way to get the id>
doSomethingElseWithTodoGivenId()
Does Redux-Toolkit provide any assistance with achieving this? I looked at createAsyncThunk, but that appears to be more focussed around async data fetching status.
I know I can do this with redux-thunk (by awaiting the dispatch and having the thunk action generate the id):
const id = await dispatch(createTodoWithGeneratedId('Say hello world'))
or by having the caller generate the id. But I'm wondering if there's a better way.

Unfourtonately, dispatch is not working that way. Plain Redux dispatch call retuns a dispatched action, but redux-toolkit using thunk middleware by default that changes a dispatch returning value to promise that fullfiled with dispatched action. You can't get a result of dispatching result from dispatch call itself, it is impossible and conflicts with it's nature.
However, you can create a thunk that dispatches different actions based on promise result. Check official documentation about creating thunk with side effects. For more advanced usage check this answer in SO.
If you are using redux-toolkit, you can use createAsyncThunk, it is implements the same logic but without much more boilerplate code.

There is no accepted pattern in this case. Sometimes you have to figure out things based on your needs. That is why leetcode algorithms are such a big deal in hiring process.
If you follow your above implementation
import { nanoid } from '#reduxjs/toolkit';
prepare(text) {
const id = nanoid();
return { payload: {text, id} };
}
the only way to get the "id" is you have to get the state with the useSelector and then loop through where text===currentToDo. This is too much work for a simple task.
Instead, why not pass id with the dispatching? You arrange your payload where accepts an object. (Because you cannot pass multiple args)
const id=nanoid()
dispatch(addTodo({todo:'Say hello world',id:id}))
// now use that id todo something
in this scenario, you do not even need the prepare phase. (you did not before neither). just push the payload object to the array

Related

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.

React Js - Combine Redux and Services layers

After some researches, I found some questions on stackoverflow about what I am trying to achieve, however, I don't feel that these questions and their answers gives me the "answers" or the "directions" i am looking for..
Note: I am pretty new to react even if I already made 2 projects and implemented redux into one of them. However, I ain't new at all in C# or in Go, even less in C. Based on my experience, I am just used to some architectures and I would like to reproduce one of them.
Here is a pretyy good schema from a similar question of mine:
Situation:
So let say I have pages that contains Components. I want these pages/compoments to display some stuff. One of my functionnality is to discover a map and for that, when the client moves, he gets new parts from my API. However, I don't wanna ask the server to give me the new parts and the ones I discovered already.
My idea about it would be to use a service MapService.js. This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones (concat).
However, I have to be logged for this, so I would like an ApiService.js that would store my authentication data and automatically put them in each of my requests.
Based on what I said, we would have something as:
Page -> Component -> Service -> API
From this, the API response would be gotten by my service, handled, then returned to the component. Handled means (data added to the previous then all returned)
I saw on internet one question that was referring "MVCS" (Model View Controller Service) pattern and I think I am looking for something as but I am not sure about how to implement it in ReactJs.
Redux seems to be something that you put all around and everywhere in your solution. What I would like is to use it as a "repository" let say, to be able to manage it from a service and not from the component itself. However, a service should be a single instance shared across the app and I don't know if something such as dependency injection could be the solution in ReactJS
Feel free to ask any edit if you need more details :)
Thanks for your help !
Here is a minimal example of Redux middleware usage. Usually, redux devs are using libraries (that give you a middleware) to have access to more appropriate APIs.
Redux middleware are chained, so each middleware can call the next middleware. The first middleware of the chain is called every time dispatch function (you can have it from react-redux connect) is called. In a middleware, if there is no next middleware it is the reducers that will be called. The next middleware can be call asynchronously after receiving an action. (Redux docs will still be better than my explainations).
In my example there is a catService that provide function that call rest API. Your services can be anything (a Class instance or a singleton for example). Usually in React/Redux stack, devs don't use object oriented development.
If a component dispatch getCat(123), the catMiddleware will be called (synchronously). Then requestGetCat will be called with the id 123. When the promise returned by requestGetCat will be resolved a setCat action will be send through the reducers to update the redux state. Once the redux state is done, the component listening for cats items object will be update too (triggering a rerender).
That can look very complexe, but in fact, it is very scalable and convenient.
// catService.js
// return a promise that return a cat object
const requestGetCat = id =>
fetch(`www.catcat.com/api/cat/${id}`)
.then(response => response.json())
// catTypes.js
export const GET_CAT = 'GET_CAT'
export const SET_CAT = 'SET_CAT'
// catActions.js
export const getCat = id => ({
type: GET_CAT,
id
})
export const setCat = (cat, id) => ({
type: SET_CAT,
id,
cat
})
// catReducer.js
const initialState = {
items: {}
}
const catReducer = (state = initialState, action) => {
if (action.type === SET_CAT) {
return {
items: {
...state.items,
[action.id]: action.cat
}
}
}
}
// catMiddleware.js
const handleGetCat = (next, action) => {
requestGetCat(action.id)
.then(cat => next(setCat(cat, action.id)))
// after retrieving the cat send an action to the reducers (or next middleware if it exist)
}
const actionHandlers = {
[GET_CAT]: handleGetCat
}
// receive every actions passing by redux (if not blocked)
// store: { dispatch, getState }
// next: next middleware or reducers (that set redux state)
// action: a redux action (dispatched) with at least type property
const catMiddleware = store => next => action => {
const handler = actionHandlers[action.type]
if (handler) {
handler(next, action)
} else {
// passing the action to the next middleware (or reducer - when there is no next middleware)
next(action)
}
}
// you have to apply your middleware
// and your reducer (see redux doc)
This one would just store the discovered pieces of the map discovered and ask the server automatically about the new ones, and of course, store the new ones
This is something I've wanted to do in the past, but never implemented a solution for.
The issue is that you essentially want to "cross the streams"..
In Redux there are two separate streams, ie dispatch an action to update the store, and read data from the store. Each of these are executed separately from a component. Combined, they can be used in a cycle by calling an action to load data into the store which triggers an update of the component which then reads from the store.
Basically you can't have non-component code that reads from the store, and if the data is missing, fires an action to load the data, then returns the data.
Thinking about it now, I'm wondering if the way to do this without adding logic to your view component is to wrap it in a component (HOC) that provides the logic.
The HOC will check the state for the location specified in the props. If it doesn't find it, it will dispatch an action to fetch it and render a loading display. When the state is updated with the new location it will update and render the wrapped component.
You could optionally always render the wrapped component and have it cope with the missing location until it is updated with the location set..
untested brain-dump below
loader HOC:
import React, { useEffect } from "react";
import actions from "./actions";
function withLocationLoader(Component) {
const Wrapper = function ({ location, locations, loadLocation, ...props }) {
useEffect(() => {
if (!locations[location]) {
loadLocation(location);
}
}, [locations]);
if (locations[location]) {
return <Component locations={locations} {...props} />;
}
return <div>Loading...</div>;
}
const mapStateToProps = (state, ownProps) => {
return { locations: state.locations };
};
const mapActionsToProps = {
loadLocation: actions.loadLocation,
};
return connect(
mapStateToProps,
mapActionsToProps
)(Wrapper);
}
export { withLoader };
component:
function MyBareComponent({ locations }) {
return <div>{JSON.stringify(locations)}</div>;
}
const MyComponent = withLocationLoader(MyBareComponent);
export { MyComponent };
actions: (utilising redux-thunk middleware)
function setLocation(location, data) {
return { type: "SET_LOCATION", payload: { location, data } };
}
export function loadLocation(location) {
return dispatch =>
Promise.resolve({ geoData: "" }) // mock api request
.then(data => dispatch(setLocation(location, data)));
}

Redux change 'state' used in other reducers when using combined reducers

I'm using redux and redux-saga. I have several reducers such as:
userReducer, itemReducer, etc. Each one serves a specific purpose for encapsulated related data.
The situation I have is that for every API request being made a token is returned. This token needs to update in the state foruserReducer.
Here's an example:
// Action
export function getItems () {
return {
type: 'GET_ITEMS'
}
}
//Saga
const getItems = function * getItems (action) {
const response = yield call(api.getItems)
yield put({ type: 'GET_ITEMS_SUCCESS' })
}
//itemReducer
switch(action.type) {
...
case 'GET_ITEMS_SUCCESS':
return {
...state,
action.data
}
...
}
This is just a basic example, now in this scenario, I'm getting items and updating the state when successful, pretty straight forward. What I also want to do is update the userReducer state with a token that would be available in action.token.
The obvious way is there would be duplicate cases, which really isn't reasonable.
The second option is in my userReducer, I can do a check before the switch for if the action contains a token and if it does, update it's state (seems ok, I guess).
The last option I can think, is to somehow modify my saga so that I can issue a new action 'UPDATE_TOKEN' or something similar, when each saga is run.
Would anyone have any suggestions on best practice on how to handle this?
EDIT:
Ok, so the best way I can see to do this now is by adding another yield in saga, such as:
yield put({ type: 'UPDATE_TOKEN', token: response.token }) and just use UPDATE_TOKEN in my userReducer. This seems pretty straight forward, just need to add it wherever I want to actually perform this action.

Redux actions depending on/coupled to other actions

I am building a Redux application (my first) and am a little unclear about how much coupling is appropriate between actions.
My application has several forms whose values are serialized in the url.
For example, there is an input field for a particular search, and upon key-up a url parameter is updated. There are several other input fields that follow this pattern.
In my top-level index.js I have several blocks of code that look like this:
// Within the declaration of a high-level component
onForm1Change={(key, value) => {
// Listened to by "formValues" state cross-section reducer
store.dispatch({
type: actions.FORM1_CHANGE,
key: key,
value: value
});
// Listened to by "url" state cross-section reducer, leads to a url param update.
// Has it's own logic that is based upon the formValues state.
// Must run after FORM1_CHANGE finishes
store.dispatch({
type: actions.UPDATE_URL,
formValues: store.getState().formValues
});
}
}
Something about actions like UPDATE_URL doesn't feel right. This action doesn't stand on its own...it relies on other actions to be dispatched first.
Is this sort of coupling between actions a code smell? Are there any common techniques to de-couple/refactor these actions?
I think that's totally OK way of chaining synchronous actions. You can also use middleware like redux-thunk for this purpose to make it simpler to read (as you will store your actions dispatcher function as an action creater). Here is some article on this topic.
This is how i did it,
Defined a redux store middleware that will detect if any dispatched action has a queryString property, and update url with it.
import createHistory from "history/createBrowserHistory";
function queryStringMiddleware(history) {
return store => next => action => {
const { payload } = action;
if (payload.queryString) {
history.push({
search: queryString
});
}
next(action);
};
}
const history = createHistory();
const middlewares = [queryStringMiddleware(history)];
const store = configureStore({}, middlewares);
Then in your action:
const onForm1Change = (key, value) => {
store.dispatch({
type: actions.FORM1_CHANGE,
key: key,
value: value,
queryString: "?the=query"
});
};

chaining multiple async dispatch in Redux

I am trying to chain multiple actions together in the following fashion:
A. post user data to database
B. use posted data to query Elasticsearch for results
(I do A and B in parallel)
B1. with results from ES, query original database for results from two tables
B2. navigate to new page and update UI
I am using thunks right now to reason about my code, but I also found this async pattern to be extremely verbose:
export function fetchRecipes(request) {
return function(dispatch) {
dispatch(requestRecipes(request))
return fetch(url)
.then(response => response.json())
.then(json => dispatch(receiveRecipes(request, json))
)
}
}
this, along with "requestRecipes" and "receiveRecipes" as other action creators seems like quite a bit just to make one async call. (a request, a receive, and a fetch function)
summary: when you're chaining 2-3 async actions whose outputs depend on each other (I need to promisify when possible), is there a more efficient means of doing so without writing 3 functions for each async call?
I figure there had to be a way. I'm pattern matching off of the Redux docs and soon became very overwhelmed with the functions I was creating
thanks a lot for the feedback!
You can use redux-saga instead of redux-thunk to achieve this more easily. redux-saga lets you describe your work using generators and is easier to reason about.
The first step is to describe how you pass your data to redux without worrying about services or async stuff.
Actions
// actions.js
function createRequestTypes(base) {
return {
REQUEST: base + "_REQUEST",
SUCCESS: base + "_SUCCESS",
FAILURE: base + "_FAILURE",
}
}
// Create lifecycle types on `RECIPES`
export const RECIPES = createRequestTypes("RECIPES")
// Create related actions
export const recipes = {
// Notify the intent to fetch recipes
request: request => ({type: RECIPES.REQUEST, request})
// Send the response
success: response => ({type: RECIPES.SUCCESS, response})
// Send the error
error: error => ({type: RECIPES.FAILURE, error})
}
Reducer
// reducer.js
import * as actions from "./actions"
// This reducer handles all recipes
export default (state = [], action) => {
switch (action.type) {
case actions.RECIPES.SUCCESS:
// Replace current state
return [...action.response]
case actions.RECIPES.FAILURE:
// Clear state on error
return []
default:
return state
}
}
Services
We also need the recipes API. When using redux-saga the simplest way to declare a service is to creating a (pure) function which reads the request as argument and returns a Promise.
// api.js
const url = "https://YOUR_ENPOINT";
export function fetchRecipes(request) {
return fetch(url).then(response => response.json())
}
Now we need to wire actions and services. This is where redux-saga come in play.
// saga.js
import {call, fork, put, take} from "redux-saga/effects"
import * as actions from "./actions"
import * as api from "./api"
function* watchFetchRecipes() {
while (true) {
// Wait for `RECIPES.REQUEST` actions and extract the `request` payload
const {request} = yield take(actions.RECIPES.REQUEST)
try {
// Fetch the recipes
const recipes = yield call(api.fetchRecipes(request))
// Send a new action to notify the UI
yield put(actions.fetchRecipes.success(recipes))
} catch (e) {
// Notify the UI that something went wrong
yield put(actions.fetchRecipes.error(e))
}
}
}
function* rootSaga() {
yield [
fork(watchFetchRecipes)
]
}
And that's it! Whenever a component will send a RECIPES.REQUEST action, the saga will hook up and handle the async workflow.
dispatch(recipes.request(req))
What's awesome with redux-saga is that you can easily chain async effects and dispatch actions during the workflow.
Based on your description, the only time you actually update your UI is right at the end of all these asynchronous operations (B1).
If you don't use the results from the preceding async calls to change your application state / update your UI, what is the benefit of having these fine-grained actions?
Of course there are things like "loading / request started" and "finished loading / request stopped", but it seems to me, that in your case, you could just do the chained async calls outside of redux (in some kind of API-layer) and only use one action.
This action dispatches a "REQUEST_STARTED", then calls the API-layer, which does the DB-calls and elasticsearch request etc., and then dispatches either "REQUEST_SUCCESS" or "REQUEST_FAILURE", based on the result of the promise, which will give you the data you need to update your UI.
This way, the state in redux only concerns itself with ONE side-effect, instead of the implementation details of your chained calls. Also, your action gets a lot simpler, because it just handles the results of one async call.

Resources