Is there a reason to use prevState as follows:
sort=()=>{
this.setState(prevState =>({contacts: [...prevState.contacts].sort(compareNames)}))
}
or is it ok to just use:
sort=()=>{
this.setState({contacts: [...this.state.contacts].sort(compareNames)})
}
From the react documentation for State and Lifecycle
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
Related
My desired behaviour:
Run HTTP request
Immediately look up data in async cache
If cache has the value before HTTP emits - use cache value.
Use HTTP value after it's finally here.
If HTTP responds faster than cache - ignore cache.
So basically I would like to kick off two async processes, one of which is supposed to provide a value quickly but if it doesn't - I want to only use the value from a slower observable which takes precedence anyway.
To expand from my comments: the question is to trigger two observables in parallel and utilize the first emission even if the other observable hasn't emitted yet.
Normally you could use the merge function for it.
However you have a condition ("If HTTP responds faster than cache - ignore cache.") that is not natively fulfilled by the merge function nor by any standard RxJS operators.
But it is easy to write custom operators in RxJS from existing operators. For your case you could customize the filter operator to suit your needs. See here for a brief intro on how to write a custom operator.
export const filterLateCache = () => {
let serverEmitted = false;
return <T>(source: Observable<T>) => {
return source.pipe(
filter((data: any) => {
if (!!data.server) {
serverEmitted = true;
return true;
} else if (!!data.cache) {
if (serverEmitted) {
return false;
} else {
return true;
}
} else {
return false;
}
})
);
};
};
As you can see the boolean flags server and cache in the incoming notification are checked to decide whether the value must be emitted. So you'd need to append the values from the observables with these flags using the map operator.
merge(
server$.pipe(
map((value) => ({
server: true,
value: value,
}))
),
cache$.pipe(
map((value) => ({
cache: true,
value: value,
}))
)
)
.pipe(filterLateCache())
.subscribe({
next: ({ value }) => { // <-- utilize destructuring to ignore boolean flags
// handle response here
},
error: (error: any) => {
// handle errors
}
});
Working example: Stackblitz
Maybe it is worth looking at the raceWith: https://rxjs-dev.firebaseapp.com/api/operators/raceWith
Basically it would look like:
server$.pipe(raceWith(cache$)).subscribe(/*side effect that must be done*/);
The thing missing is that it does not fulfill requirement 4.
I am trying to go a simple step beyond the nest doc example in implementing #Sse() in a controller but I never used rxjs untill now so Im a bit confused.
The flow is :
client send a POST request with a file payload
server (hopefully) sends back the newly created project with a prop status:UPLOADED
client subscribe to sse route described below passing as param the projectId it just received from server
in the meantime server is doingSomeStuff that could take from 10sec to a min. When doingSomeStuff is done, project status is updated in db from UPLOADED to PARSED
My need is for the #Sse decorated function to execute at x interval of time a "status-check" and return project.status (that may or may not have been updated at the time)
My present code :
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable<any> {
const projId$ = from(this.projectService.find(projectId)).pipe(
map((p) => ({
data: {
status: p.status,
},
})),
);
return interval(1000).pipe(switchMap(() => projId$));
}
I don't put code of the service here as it a simple mongooseModel.findById wrapper.
My problem is the status returned remains UPLOADED and is never updated. It doesnt seem the promise is reexecuted at every tick. If I console.log inside my service I can see my log being printed only once with the initial project value while I expect to see a new log at each tick.
This is a two-step process.
We create an observable out of the promise generated by this.service.findById() using the from operator in rxjs. We also use the map operator to set the format of the object we need when someone subscribes to this observable.
We want to return this observable every x seconds. interval(x) creates an observable that emits a value after every x milliseconds. Hence, we use this and then switchMap to the projId$ whenever the interval emits a value. The switchMap operator switches to the inner observable whenever the outer observable emits a value.
Please note: Since your server may take 10 sec, to min for doing the operation, you should set the intervalValue accordingly. In the code snippet below, I've set it to 10,000 milli seconds which is 10 seconds.
const intervalValue = 10000;
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable < any > {
return interval(intervalValue).pipe(
switchMap(() => this.projectService.find(projectId)),
map((p) => ({
data: {
status: p.status,
}
})));
}
// OR
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable < any > {
const projId$ = defer(() => this.service.findById(projectId)).pipe(
map(() => ({
data: {
_: projectId
}
}))
);
return interval(intervalValue).pipe(switchMap(() => projId$));
}
#softmarshmallow
You can watch model changes and use observable stream to send it.
Something like this
import { Controller, Param, Sse } from '#nestjs/common'
import { filter, map, Observable, Subject } from 'rxjs'
#Controller('project')
export class ProjectStatusController {
private project$ = new Subject()
// watch model event
// this method should be called when project is changed
onProjectChange(project) {
this.project$.next(project)
}
#Sse('sse/:projectId')
sse(#Param('projectId') projectId: string): Observable<any> {
return this.project$.pipe(
filter((project) => project.projectId === projectId),
map((project) => ({
data: {
status: project.status,
},
})),
)
}
}
I have two actions TEST and TEST_DONE which both increment an id property in my redux state. I am using redux-saga to dispatch the second action TEST_DONE automatically whenever I dispatch the first action TEST from my component.
I expect the order of execution to go like this:
component renders with initial value of testState.id = 0
component dispatches TEST action
component re-renders with testState.id = 1
saga dispatches the TEST_DONE action
component re-renders with testState.id = 2
Instead my component only re-renders when testState.id is updated to 2. I can't see the 1 value in the getSnapshotBeforeUpdate function. It shows 0 as the previous prop.
Why does the prop jump from 0 to 2 without receiving 1 in between?
saga.js:
export function* TestSagaFunc() {
yield put({
type: actions.TEST_DONE
});
};
export default function* rootSaga() {
yield all([
yield takeEvery(actions.TEST, TestSagaFunc),
]);
};
action.js:
const actions = {
TEST: 'TEST',
TEST_DONE: 'TEST_DONE',
callTest: (id) => ({
type: actions.TEST,
payload: {
id
}
}),
};
export default actions;
reducer.js:
const initState = {
testState: {
id: 0
}
};
export default function TestReducers ( state=initState, { type, ...action}) {
switch(type) {
default:
return state;
case actions.TEST: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
};
case actions.TEST_DONE: {
const { id } = state.testState;
const nextId = id + 1;
return {
...state,
testState: {
...state.testState,
id: nextId
}
};
}
};
};
console output from component getSnapshotBeforeUpdate
Summarizing my comments from the question:
The redux state is indeed being updated as you've seen, but a component is not guaranteed to render every intermediate state change based on the way react batches state changes. To test this you can try importing delay from redux-saga/effects and adding yield delay(1000); before calling yield put in TestSagaFunc so the two state updates don't get batched together.
This is just a trick to illustrate the effects of batching and almost certainly not what you want to do. If you need the intermediate state to be rendered you could dispatch TEST_DONE from the component being rendered with a useEffect (or componentDidUpdate) to ensure that the component went through one render cycle with the intermediate state. But there is no way to force your component to render intermediate reducer states that are batched together.
after running mutation using the graphql, if I quickly goback to Previous page,
occur error : Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and
asynchronous tasks in %s.%s, a useEffect cleanup function,
I think it's because I quickly go to another page during the mutation.
If this is not the case, there is no error.
(Even if an error occurs, update succeeds. but I'm worried about errors)
Even if move to another page during mutating, I want to proceed with the update as it is
How can I proceed with the update?
if If there is no way, is there method that How to create a delay during mutating
im so sorry. my english is not good.
const CalendarTodo = ({
month,
day,
data,`enter code here`
isImportWhether,
setIsImportWhether
}) => {
const [value, setValue] = useState("");
const monthDay = `${month + 1}월 ${day}일`;
const [createToDoMutation] = useMutation(CREATE_TODO, {
variables: {
toDoId:
data &&
data.toDos &&
data.toDos.filter(object => object.monthDay === monthDay)[0] &&
data.toDos.filter(object => object.monthDay === monthDay)[0].id,
monthDay: monthDay,
dayToDo: value,
importEvent: isImportWhether
},
update: (proxy, { data: { createToDo } }) => {
const data = proxy.readQuery({ query: SEE_TODO_OF_ME });
data &&
data.toDos &&
data.toDos.filter(object => object.monthDay === monthDay)[0] &&
data.toDos
.filter(object => object.monthDay === monthDay)[0]
.dayToDo.push(createToDo);
proxy.writeQuery({ query: SEE_TODO_OF_ME, data });
},
optimisticResponse: {
createToDo: {
__typename: "DayToDo",
id: Math.random().toString(),
toDoList: value,
importEvent: isImportWhether
}
}
});
return (
<>
);
};
export default CalendarTodo;
As you already guessed the reason is the asynchronous request that keeps on running even after un-mounting the component due to navigating away from it.
There are many ways to solve this. One is to add a check whether or not the component you are calling the async request from is still mounted and only update its state if so, e.g.:
useEffect(() => {
let isMounted = true;
apollo.mutate({query, variables, update: {
if(isMounted) {
// update state or something
}
})
return () => {
isMounted = false;
};
}, []);
This way however the data might be lost. If you want to make sure that you receive and store the return value you should add the request to a higher level component or context hat will not be unmounted on navigation. This way you can trigger the async call but dont have to worry about navigating away.
I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!
Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.
This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.
Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.
And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.
Edit: My views on the Vuex cycle of uni-directional data flow:
If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.
The promise from action is only to let the component know that action is complete.
The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.
The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.
Just for an information on a closed topic:
you don’t have to create a promise, axios returns one itself:
Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4
Example:
export const loginForm = ({ commit }, data) => {
return axios
.post('http://localhost:8000/api/login', data)
.then((response) => {
commit('logUserIn', response.data);
})
.catch((error) => {
commit('unAuthorisedUser', { error:error.response.data });
})
}
Another example:
addEmployee({ commit, state }) {
return insertEmployee(state.employee)
.then(result => {
commit('setEmployee', result.data);
return result.data; // resolve
})
.catch(err => {
throw err.response.data; // reject
})
}
Another example with async-await
async getUser({ commit }) {
try {
const currentUser = await axios.get('/user/current')
commit('setUser', currentUser)
return currentUser
} catch (err) {
commit('setUser', null)
throw 'Unable to fetch current user'
}
},
Actions
ADD_PRODUCT : (context,product) => {
return Axios.post(uri, product).then((response) => {
if (response.status === 'success') {
context.commit('SET_PRODUCT',response.data.data)
}
return response.data
});
});
Component
this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
if (res.status === 'success') {
// write your success actions here....
} else {
// write your error actions here...
}
})
TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.
For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.
But, there are EDGE CASES where returning a promise from your actions might be "necessary".
Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently.
In that case, one would need to pass the caller component as a parameter to set different flags in the store.
Dumb example
Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous.
If the promise fails, the page should only display an error in the component the user was trying to change the username from.
Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.
actions.js
const axios = require('axios');
const types = require('./types');
export const actions = {
GET_CONTENT({commit}){
axios.get(`${URL}`)
.then(doc =>{
const content = doc.data;
commit(types.SET_CONTENT , content);
setTimeout(() =>{
commit(types.IS_LOADING , false);
} , 1000);
}).catch(err =>{
console.log(err);
});
},
}
home.vue
<script>
import {value , onCreated} from "vue-function-api";
import {useState, useStore} from "#u3u/vue-hooks";
export default {
name: 'home',
setup(){
const store = useStore();
const state = {
...useState(["content" , "isLoading"])
};
onCreated(() =>{
store.value.dispatch("GET_CONTENT" );
});
return{
...state,
}
}
};
</script>