Can I create a mobx computed inside a React render function to use like useMemo()? - mobx

I'm wondering how to go about using a mobx observable inside a useMemo hook. I know I could pass all possibly dependencies to the hook, but that could get kind of messy:
const MyComponent = observer(() => {
const people = useGetPeople();
const peopleFormatted = useMemo(() => {
return people.map(person => person.fullName);
},[ ...? ]);
});
I can't easily make every person's firstName be a dependency of useMemo. I'd think I could extract the functionality to a computed ... but I feel like this won't work:
const MyComponent = observer(() => {
const people = useGetPeople();
const peopleFormatted = computed(() => {
return people.map(person => person.fullName);
});
});
I feel like it will confuse mobx to create a computed inside a reaction that the reaction must depend on.
I know I could extract the computed to each person but I don't feel like that's a solution that matches every use case.
Thanks in advance!

Assuming const people = useGetPeople(); is an observable array of some sort of people objects...
const peopleFormatted = computed(() => {
return people.map(person => person.fullName);
}).get(); //note .get()
Should work fine inside the observer function body. See https://mobx.js.org/computeds-with-args.html#2-close-over-the-arguments
What is confusing me is useGetPeople();
That typically means you are using react's state api for managing state and reactions. ie: useState, etc.
Without seeing what useGetPeople() does under the hood, it's hard to give a concrete answer.

Related

Redux Toolkit: Async Dispatch won't work in react-native

I'm trying to make some async actions with redux toolkit in react-native. The project runs on redux without any issues, beside the implementation issues for createAsyncThunk.
I used the same logic as described in the docs
Within my Slice, I'm creating the createAsyncThunk Object as follows:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(thunkAPI) => {
const state = thunkAPI.getState();
console.log("THUNK state.loggedIn: "+state.loggedIn);
if(state.loggedIn){
return apiHelper.getAddressDataAsync();
}
});
It only differs in the export tag before const tag compared to the docs. I had to make it in order to access the fetchAddressList from outside. The apiHelper.getAddressDataAsync() is an async method, that returns the result of a fetch.
Than I added the extraReducers attribute to my slice object.
export const appDataSlice = createSlice({
name: "appDataReducer",
initialState:{
//Some initial variables.
},
reducers: {
//Reducers...
},
extraReducers: (builder) => {
builder.addCase(fetchAddressList.fulfilled, (state, action) => {
console.log("FULLFILLED::: ",action.payload);
state.addressList = action.payload.addressList;
state.defaultAddressId = action.payload.defaultAddressId;
})
}
});
export const { /*REDUCER_METHOD_NAMES*/ } = appDataSlice.actions;
This slice is stored in the store using configureStore, among other slices, that are definitely working fine.
Calling the fetchAddressList() method using dispatch doesn't do anything:
dispatch(fetchAddressList());
What exactly am I doing wrong here? Would appreciate any hints.
Edit:
Are there configurations required within the configureStore()-method when creating the store object?
This is how I create the store object:
export const store = configureStore({
reducer: {
/*Other reducer objects....,*/
appDataReducer: appDataSlice.reducer
},
});
Maybe something is missing here...
It was due to wrong usage of the createAsyncThunk()-method. I'd passed the thunkAPI to be as the first (and only) parameter to the inner method, which was linked to user arguments passed through parameters into the initial dispatch method (like dispatch(fetchAddressList("ARG_PASSED_TO_FIRST_PARAMETER_OF_ASNYCTHUNK"));). However thunkAPI is being injected into the second parameter of createAsyncThunk()-method and as a result thunkAPI was undefined, since I hadn't passed any parameters by calling dispatch(fetchAddressList());
It was odd, to not have any errors / exceptions
calling a method of an undefined object though => thunkAPI.getState().
The solution is to use the second parameter for thunkAPI.
You do have two options by doing so.
1 - Either load the whole thunkAPI into the second parameter and use it as so:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args, thunkAPI) => {
console.log("TEST: ", thunkAPI.getState());
thunkAPI.dispatch(...);
});
2 - Or load exported methods by the thunkAPI:
export const fetchAddressList = createAsyncThunk('/users/fetchAddresses', async(args,{getState, dispatch}) => {
console.log("TEST: ", getState());
dispatch(...);
});
Both ways will work. Happy coding :)

How to create an rxjs Observable from TextInput (either onChange or onTextChange)

I want to create an observable from a change event that gets fired on a React Native TextInput component. TextInput comes with 2 change props that I'm aware of (onChangeText and onChange). From what I gather, you need to use onChange if you want access to the native event you need to use onChange.
I don't know much about the native event object. I am trying to create an rxjs observable using fromEvent.
First I created a ref in my functional component like this:
const sqftRef = useRef().current
Then I attached this ref to the TextInput component like this:
<TextInput
ref={sqftRef} // attach a ref
label='Sqft'
mode='flat'
textContentType='none'
autoCapitalize='none'
keyboardType='numeric'
autoCorrect={false}
value={String(formValues.sqft)}
dense
underlineColor={colors.colorOffWhite}
onChangeText={(text) => setText(text)}
onChange={e => {
// somehow create an observable from this event ???
}}
style={styles.inputStyles}
theme={inputTheme}
/>
I tried to create an Observable using fromEvent like this but it doesn't work. I get undefined is not an object (evaluating target.addEventListener):
fromEvent(sqftRef, 'onChange').subscribe(value => console.log(value))
I know my approach is all wrong. Hoping someone can point me in the correct direction.
I would emit events you need into a subject, then subscribe to the subject in other parts of your code.
Here's a simple React example that should get you started
function App() {
const textChange = new Subject<string>();
useEffect(() => {
// subscribe to
const subscription = textChange.asObservable().subscribe(console.log)
return () => subscription.unsubscribe()
}, [])
// Emit events with a subject
return <textarea onChange={(e) => {
textChange.next(e.target.value)
}}>
</textarea>
}
render(<App />, document.getElementById('root'));
Check out the example here: https://stackblitz.com/edit/react-ts-akoyfv
I think the problem is with assigning the current directly to the sqftRef. Try to define it without current, but use current when creating the Observable, like the following:
const sqftRef = useRef();
Then create the Observable within useEffect to make sure that the DOM is ready:
useEffect(() => {
fromEvent(sqftRef.current, 'onChange').subscribe((value) =>
console.log(value)
);
});
OK, I was able to figure it out with the help of Amer Yousuf and Alex Fallenstedt.
I did something similar to what Alex suggested, modifying his solution for React Native. One reason his solution wasn't working for me is that it is important to use the useRef hook to prevent the Observable from being re-created on each render. If the observable is recreated (on a re-render) and useEffect doesn't run again, then we won't have an active subscription to the newly (re-created) observable (useEffect never runs again). That's why my call to sqft$.next was originally only being called once (the first time until we re-render).
My solution looks like this:
let sqft$ = useRef(new BehaviorSubject(0)).current
useEffect(() => {
const sub = sqft$.subscribe({
next: (val) => {
// just testing stuff out here
updateForm('sqft', val)
updateForm('lot', val * 2)
}
})
// this is only relevant to my use case
if (activeReport) sqft$.next(activeReport.sqft)
return () => sub.unsubscribe()
}, [activeReport])
and of course I call this in onChangeText:
onChangeText={(text) => {
sqft$.next(text)
}}
So this is working right now. I still feel like there may be a better way using onChange(e => ...stuff). I will leave this question open for a little bit in case anyone can break down how to do this using nativeEvent or explain to me how I can access an event off the TextInput component.

How do I access rootGetters from a different namespaced module in Vuex?

I have a Vuex module named 'forms'. I have a different (also namespaced) module named 'users'.
I'm using Vuexfire (for the first time, which I think is what's tripping me up). And have an action that works like this:
const actions = {
loadPendingHoursRequests: firestoreAction((context) => {
context.bindFirestoreRef('pendingHoursRequests', db.collection('hours')
.where('submittedToUID', '==', "iTd865JKWXRmhz2D2mtW7KIpL7a2"))
}),
This works as expected and creates a real-time connection between Firestore and Vuex. The problem is I want "iTd865JKWXRmhz2D2mtW7KIpL7a2" to be a dynamic value drawn from the 'users' module.
I'm just completely lost. If I refactor like this:
loadPendingHoursRequests ({ dispatch, commit, getters, rootGetters }) {
let uid = rootGetters['users/currentUserUID'];
console.log(uid)
firestoreAction((context) => {
context.bindFirestoreRef('pendingHoursRequests', db.collection('hours').where('submittedToUID', '==', uid))
})
}
The console.log above returns 'undefined'. And even if I remove the .where('submittedToUID', '==', uid), the firestoreAction doesn't work anyway.
Thanks in advance. I'd love to know what I'm not understanding here.
Untested (I don't use VuexFire) but assuming the bindFirestoreRef needs the context object, you can access rootGetters as a property of it as well. Putting the two snippets together ilke this:
const actions = {
loadPendingHoursRequests: firestoreAction((context) => {
const uid = context.rootGetters['users/currentUserUID'];
context.bindFirestoreRef('pendingHoursRequests', db.collection('hours')
.where('submittedToUID', '==', uid))
})
}

React Hooks and useEffect – best practices and server issues

I am using React Native with functional components. componentDidMount() etc. are not available in functional components, instead I use Hooks. But Hooks don't act like lifecycle methods. I am wondering what the best practices are.
Assumed that we have a function like this one:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
// some code inside this function which is called on every component update
}, [])
server.asyncCall().then(data => {
setSomeHook(data)
})
return (<View>
{someHook ? (<Text> `someHook` was assigned </Text>) : (<Text> `someHook` was not assigned, display some ActivityIndicator instead</Text>)}
</View>)
}
Where to place server.asyncCall()? Inside or outside of useEffect?
I think you have a misunderstanding here. The convention is that all the fetching data is going to be placed inside the componentDidMount lifecycle method. React useEffect hook can replace this easily by placing an empty array of dependencies, which means you can place that call inside the useEffect you already have.
Unlike you mention in your code comment, this hook won't be triggered on each component update. It will be only be triggered once the component is being mounted. So, you should be able to do it as follows:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
server.asyncCall().then(setSomeHook)
}, [])//only triggered when component is mounted.
In the future, you might want to check the rules of the hooks.

How to access dispatch function from epic in redux-observables

I'd like to know if there's anyway to access redux's dispatch function from an epic in redux-observables (1.2).
export const epicDownloadProfile = (action$, { dispatch }) =>
action$.pipe(
ofType(DOWNLOAD_INIT.getType()),
switchMap(() =>
from(downloadStart(dispatch)).pipe(
map(() => DOWNLOAD_INIT()),
catchError(err => of(DOWNLOAD_ERROR.asError(err.message)))
)
)
)
I know this is not ideal, but I have a very complex function that makes a lot of things while downloading, so I'd need to pass dispatch to downloadStart().
Redux-observables provides me with a StateObservable object as the second parameter of the epic, it does contain the state, but it does not contain the dispatch function... In the example { dispatch } comes undefined. Is there any other way I can access it?
You did mention this isn't ideal, but for others who might not read your question I must add a warning that doing this is suggestive that what you might be doing is an anti-pattern--but not always! Certainly if you're using some sort of third party library that you have no control over, and you need to pass it to it, that's an understandable workaround. Just don't be too tempted to called store.dispatch() around your Epics all the time, as it is a usually a sign you're fighting redux-observable. Of course, at the end of the day, this is just advice hehe :)
OK. So here's how you can do it:
redux-observable provides a way to inject dependencies into every epic. So when you create your epicMiddleware, you can pass a reference to the store, dispatch, or anything else.
https://redux-observable.js.org/docs/recipes/InjectingDependenciesIntoEpics.html
/* Where ever you create your store/middleware
*****************************************/
const middlewares = [];
const epicMiddleware = createEpicMiddleware({
dependencies: {
get store() { // or getStore() if you want
return store;
}
}
});
middlewares.push(applyMiddleware(epicMiddleware));
const store = createStore(
rootReducer,
initialState,
composeEnhancers(...middlewares)
);
epicMiddleware.run(rootEpic);
/* Where ever this epic is
*************************/
const epicDownloadProfile = (action$, state$, { store }) =>
action$.pipe( dependencies ----^
ofType(DOWNLOAD_INIT.getType()),
switchMap(() =>
from(downloadStart(store.dispatch)).pipe(
map(() => DOWNLOAD_INIT()),
catchError((err) => of(DOWNLOAD_ERROR.asError(err.message)))
)
)
);
There are other approaches too, such as exporting your store from the module, importing it inside your epic modules. But that might not be good if you need to don't want your store to be a singleton, doing SSR, etc.
Here's another approach, if you prefer it, since you should always start the root epic after the store has been created anyway.
// Manually inject it yourself by wrapping the "root epic"
// with another function, which is basically an epic which
// defers to your root epic.
epicMiddleware.run((action$, state$) => {
return rootEpic(action$, state$, { store });
});