Set context and fire a method in an xState Action - reactjs

I have a simple state machine that handles an input form
export const chatMachine = Machine({
id: 'chat',
initial: 'idle',
states: {
idle: {
on: {
SET_MESSAGE: { actions: ['handleMessageChange'] },
COMMENT_SUBMITTED: {
actions: ['submitComment']
}
}
}
}
});
I would like the submitComment action to fire off a function and then reset a field in context like this:
submitComment: (ctx, e) => {
e.payload(ctx.message);
assign({
message: ''
});
}
This doesn't work.
It fires the method I'm passing in but it doesn't make it to the assign bit.
Can I do two thing sin one action or should I be creating two seperate actions?

You should be creating two separate actions because those are two separate actions.
I'm not sure what e.payload(ctx.message) does, but events should be purely data - you should not put functions in events.
Also, assign(...) is not imperative. It is a pure function that returns an action that looks something like { type: 'xstate.assign', ...}. None of XState's actions are imperative.
Try this:
// ...
COMMENT_SUBMITTED: {
actions: ['submitComment', 'assignComment']
},
// ...
actions: {
submitComment: (ctx, e) => { ... },
assignComment: assign({ message: '' })
}

Related

How do I invalidate queries and fetch latest data in TRPC?

I am simply trying to get latest data from the server after performing a mutation. My code looks something like this:
const utils = trpc.useContext()
const markAsUnreadMutation = trpc.useMutation(['update-mark-as-unread'], {
onSuccess() {
utils.invalidateQueries() //THIS IS NOT WORKING!
},
onError(data) {
toast({
type: 'error',
message: data.message,
})
},
})
function markAsUnread(isUnread: boolean) {
markAsUnreadMutation.mutate({
id: parseInt(channel.id),
markAsUnread: isUnread,
})
}
Invalidating a single query
You can invalidate a query relating to a single procedure and even filter based on the input passed to it to prevent unnecessary calls to the back end.
import { trpc } from '../utils/trpc';
function MyComponent() {
const utils = trpc.useContext();
const mutation = trpc.post.edit.useMutation({
onSuccess(input) {
utils.post.all.invalidate();
utils.post.byId.invalidate({ id: input.id }); // Will not invalidate queries for other id's 👍
},
});
// [...]
}
If you are using TRPC v10, you can do something like this:
utils.your.route.invalidate()

Update deeply nested state object in redux without spread operator

I've been breaking my head for a week or something with this !!
My redux state looks similar to this
{
data: {
chunk_1: {
deep: {
message: "Hi"
}
},
chunk_2: {
something: {
something_else: {...}
}
},
... + more
},
meta: {
session: {...},
loading: true (or false)
}
}
I have an array of keys like ["path", "to", "node"] and some data which the last node of my deeply nested state object should be replaced with, in my action.payload.
Clearly I can't use spread operator as shown in the docs (coz, my keys array is dynamic and can change both in values and in length).
I already tried using Immutable.js but in vain.. Here's my code
// Importing modules ------
import {fromJS} from "immutable";
// Initializing State ---------
const InitialState = fromJS({ // Immutable.Map() doesn't work either
data: { ... },
meta: {
session: {
user: {},
},
loading: false,
error: "",
},
});
// Redux Root Reducer ------------
function StoreReducer(state = InitialState, action) {
switch (action.type) {
case START_LOADING:
return state.setIn(["meta"], (x) => {
return { ...x, loading: true };
});
case ADD_DATA: {
const keys = action.payload.keys; // This is a valid array of keys
return state.updateIn(keys, () => action.payload); // setIn doesn't work either
}
}
Error I get..
Uncaught TypeError: state.setIn (or state.updateIn) is not a function
at StoreReducer (reducers.js:33:1)
at k (<anonymous>:2235:16)
at D (<anonymous>:2251:13)
at <anonymous>:2464:20
at Object.dispatch (redux.js:288:1)
at e (<anonymous>:2494:20)
at serializableStateInvariantMiddleware.ts:172:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:258:1)
at Object.dispatch (<anonymous>:3665:80)
What I want ?
The correct way to update my redux state (deeply nested object) with a array containing the keys.
Please note that you are using an incredibly outdated style of Redux. We are not recommending hand-written switch..case reducers or the immutable library since 2019. Instead, you should be using the official Redux Toolkit with createSlice, which allows you to just write mutating logic in your case reducers (and thus also just using any helper library if you want to use one).
Please read Why Redux Toolkit is how to use Redux today.
you could use something like that:
import { merge, set } from 'lodash';
export default createReducer(initialState, {
...
[updateSettingsByPath]: (state, action) => {
const {
payload: { path, value },
} = action;
const newState = merge({}, state);
set(newState, path, value);
return newState; },
...}

How to create reusable redux-module?

For example, my app contains the two list: colors & my favorite colors. How to create the re-usable filter-module for this two lists?
The problem is the actions in redux commiting into global scope, so filter-reducer for colors and filter-reducer for favorite colors reacting to the same actions.
I try something like high-order functions that receive the module-name and returned new function (reducer) where classic switch contain module + action.type.
But how make scoped actions or scoped selectors? Best-practise?
Maybe Redux-Toolkit can solve this problem?
High-order reducer
High-order action
High-order selector
Well described here
Name your function like createFilter and add a parameter like domain or scope
after this in switch check it e.g
export const createFilters = (domain, initState) => {
return (state = initState, { type, payload }) => {
switch (type) {
case: `${domain}/${FILTER_ACTIONS.ADD_FILTER}`:
...
and for actions create something like this
const createFilterActions = (domain: string) => {
return {
addFilter: (keys: string[]) => {
return {
type: `${domain}/${FILTER_ACTIONS.ADD_FILTER}`,
payload: keys
}
},
updateFilter: (key: string, state: any) => {
return {
type: `${domain}/${FILTER_ACTIONS.FILTER_UPDATED}`,
payload: { key, state }
}
},
}
}

How to define an action in a mobx store using mobx-react?

Have been following a few tutorials on youtube and have pretty much never seen anyone explicitly define an action that mutates the state they just throw in into the store. I have been doing the same and while it works a 100% it throws a warning on react native. Just wondering how you could define that something is an action and maybe if someone has a good way to separate the actions into a different file. Here is my store.
export function createCurrencyStore() {
return {
currencies: [
'AED',
'ARS',
'AUD',
],
selectedCurrencyFrom: 'USD',
selectedCurrencyTo: 'EUR',
loading: false,
error: null,
exchangeRate: null,
amount: 1,
fromFilterString: '',
fromFilteredCurrencies: [],
toFilterString: '',
toFilteredCurrencies: [],
setSelectedCurrencyFrom(currency) {
this.selectedCurrencyFrom = currency
},
setSelectedCurrencyTo(currency) {
this.selectedCurrencyTo = currency
},
async getExchangeRate() {
const conn = await fetch(
`https://api.exchangerate-api.com/v4/latest/${this.selectedCurrencyFrom}`
)
const res = await conn.json()
console.log(res)
this.exchangeRate = res.rates[this.selectedCurrencyTo]
},
setFromFilters(string) {
this.fromFilterString = string
if (this.fromFilterString !== '') {
this.fromFilteredCurrencies = this.currencies.filter((currency) =>
currency.toLowerCase().includes(string.toLowerCase())
)
} else {
this.fromFilteredCurrencies = []
}
},
setToFilters(string) {
this.toFilterString = string
if (this.toFilterString !== '') {
this.toFilteredCurrencies = this.currencies.filter((currency) =>
currency.toLowerCase().includes(string.toLowerCase())
)
} else {
this.toFilteredCurrencies = []
}
},
}
}
have pretty much never seen anyone explicitly define an action
Well, this is weird because it is a very common thing to only mutate state through actions to avoid unexpected mutations. In MobX6 actions are enforced by default, but you can disable warnings with configure method:
import { configure } from "mobx"
configure({
enforceActions: "never",
})
a good way to separate the actions into a different file
You don't really need to do it, unless it's a very specific case and you need to somehow reuse actions or something like that. Usually you keep actions and the state they modify together.
I am not quite sure what you are doing with result of createCurrencyStore, are you passing it to observable? Anyway, the best way to create stores in MobX6 is to use makeAutoObservable (or makeObservable if you need some fine tuning). So if you are not using classes then it will look like that:
import { makeAutoObservable } from "mobx"
function createDoubler(value) {
return makeAutoObservable({
value,
get double() {
return this.value * 2
},
increment() {
this.value++
}
})
}
That way every getter will become computed, every method will become action and all other values will be observables basically.
More info in the docs: https://mobx.js.org/observable-state.html
UPDATE:
Since your getExchangeRate function is async then you need to use runInAction inside, or handle result in separate action, or use some other way of handling async actions:
import { runInAction} from "mobx"
async getExchangeRate() {
const conn = await fetch(
`https://api.exchangerate-api.com/v4/latest/${this.selectedCurrencyFrom}`
)
const res = await conn.json()
runInAction(() => {
this.exchangeRate = res.rates[this.selectedCurrencyTo]
})
// or do it in separate function
this.handleExchangeRate(res.rates[this.selectedCurrencyTo])
},
More about async actions: https://mobx.js.org/actions.html#asynchronous-actions

Redux - Removing entities from related state

I have a redux state of the following form, which is managed in slices using combineReducers:
interface AppState {
foos: Foo[];
bars: Bar[];
bazs: Baz[];
}
These are related in the following way:
One Foo has many Bar. One Bar has many Baz. Their structures are as follows:
interface Foo {
id: string;
name: string;
}
interface Bar {
id: string;
name: string;
fooId: string;
}
interface Baz {
id: string;
name: string;
barId: string;
}
I have the regular thunks/actions setup for each part of the state, i.e DELETE_FOO_REQUEST, DELETE_FOO_FAILURE DELETE_FOO_SUCCESS and other CRUD options for each of the entities.
My delete foo thunk looks like this:
function deleteFoo(fooId) {
return async (dispatch, getState) => {
dispatch(deleteFooRequest());
await api.deleteFoo(fooId);
dispatch(deleteFooSuccess(fooId);
// omitted error handling for brevity
}
}
The thing is: when I delete a Foo on my api/backend it also deletes all related Bars and Bazs. Now how do I handle this while using redux-thunk conventions?
Do I create more actions of the form DELETE_BARS_FOR_FOO and dispatch those in the same thunk? Or do I reuse DELETE_BAR_SUCCESS and use it in a loop?
Option A
function deleteFoo(fooId) {
return async (dispatch, getState) => {
dispatch(deleteFooRequest());
await api.deleteFoo(fooId);
const barIds: string[] = selectBarsForFoo(fooId);
dispatch(deleteFooSuccess(fooId);
dispatch(deleteBarsForFoo(fooId);
for (const barId of barIds) {
dispatch(deleteBazForBar(barId));
}
// omitted error handling for brevity
}
}
Option B
function deleteFoo(fooId) {
return async (dispatch, getState) => {
dispatch(deleteFooRequest());
await api.deleteFoo(fooId);
const barIds: string[] = selectBarsForFoo(fooId);
dispatch(deleteFooSuccess(fooId);
for (const barId of barIds) {
dispatch(deleteBarSuccess(barId));
}
// followed by a similar loop for the bazs of each bar
// omitted error handling for brevity
}
}
In option B case I am reusing an action meant for something else technically. In both actions I'm dispatching in a loop which would impact performance as well. I am using react-redux however and can use the batch() api so no worries there.
Are these my only two options while using redux-thunk or is there a superior/conventional way of going about this?
Instead of complicating your actions is it an option to just deal with this in your reducer (as suggested in comments). The following way you can still use combineReducers but have a combined reducer that gets {foo,bar,bas} as state:
const fooBarBaz = combineReducers({
foo:fooReducer,
bar: barReducer,
baz:bazReducer,
});
const combined = (state,action)=>{
//handle foo remove success action, state is {foo,bar,baz}
}
export default function reducer(state,action){
return combined(fooBarBaz(state,action),action)
}

Resources