NGRX execution order of dispatch and select - javascript

I have a post button which will trigger the addPost() method to dispatch action (Add post into server) and then select the added post Id. I have a Add_Success reducer to assign the added post id into selectedPostId which will be used by the getCurrentPostHeaderId selector.
The correct execution order that I'm expecting is:
1. Dispatch AddPost Action
2. Select the added post Id
But the order always went wrong after the first execution:
1. Select the previous post Id
2. Dispatch AddPost Action
3. Select the added post Id
On the first run, the execution is correct, the action was dispatched and correct added id was displayed in the log.
But if i immediately add another post after the first run, it seems the old selector will be executed first and the previous id will be displayed. After this, only the new post will be added successfully and new post id is selected.
My Component:
addPost() {
const postStatus = Object.assign({}, this.newPostForm.value);
let postHeader: any = {};
postHeader.postStatus = postStatus;
this.store.dispatch(new postHeaderActions.AddToPost(postHeader)); //Add post
this.store.select(
fromPost.getCurrentPostHeaderId
)
.subscribe((post) => {
if (post) {
console.log(post); //Return the new added post id
}
});
}
My Reducer & Selector:
case ActionTypes.Add_Success: {
console.log('hey');
return postHeaderAdapter.addOne(action.payload, {
...state,
selectedPostId: action.payload.id,
loaded: true
});
}
export const getCurrentPostHeaderId = createSelector(
getPostHeaderFeatureState,
(state: PostHeaderState) => state.selectedPostId
);
The same goes on for multiple run, you can see that from second run onward, it will return the previous Id before showing the new Id.
Can anyone help me on this? Thanks

One way to listen only the next id is to listen only when the id has changed:
this.store.select(
fromPost.getCurrentPostHeaderId
)
.pipe(distinctUntilChanged())
.subscribe(...)

Related

How to redirect after or during the onClick function of Button?

I am trying to add a feature in my Next.js website that allows users to click a button to create a new group, then redirect them to an "Invite members" page that uses the auto generated group_id in the URL with dynamic routing. I am currently using Next.js's Router, but I feel like there is a better (or working) way to do this.
JS (within export default function groups ()):
const [num, setNum] = useState("");
const router = useRouter()
const createGroup = async () => {
const { data, error } = await supabase
.from("chatgroups")
.insert([
{
group_name: groupName,
creator: user.email,
description: groupDesc,
accepted_members: user.email + " ",
invited_members: ""
}
]);
if (error) {
console.log(error);
} else {
setNum("/groups/" + String(data[0].group_id));
router.push(num);
}
};
HTML (returned in same JS script):
<button type="button" className="bg-yellow-500 rounded px-12 py-2" onClick={()=> {
createGroup()
}} >Insert test group</button>
I have tried using the Router for both server and client and both did not recognize .push()
import { Router } from "next/dist/client/router";
import { Router } from "next/dist/server/router"; //neither of these recognized the method.
My goal is to execute the function createGroup(), in which the value of "data" will receive the auto-generated int id of the new SQL row. Then I want to use this id to redirect to "/groups/[id]", where [id] is the new ID. I tried using a Link with the const [num, setNum], but it performs the redirect BEFORE the new value is set. I am relatively new to Next.js, so I would appreciate any help with this.
Desired output:
Click button -> adds row to SQL table with group_id = 17.
Redirect user to "/groups/invite_members/17".
Edit: I have updated my main JS code above to use useRouter(), now it only works every second click.
The issue is that calling setNum does not update num immediately, as setting state is an asynchronous operation.
This means that on the first button click num will still have its default value ("") when router.push(num) is called, and only when clicking the button a second time will the num state have updated with the value set previously.
To fix it, you can set the value to a variable and use that in the router.push call instead.
const path = "/groups/" + String(data[0].group_id);
setNum(path);
router.push(path);

Async issue with State in React Native

I'm trying to build a simple app that lets the user type a name of a movie in a search bar, and get a list of all the movies related to that name (from an external public API).
I have a problem with the actual state updating.
If a user will type "Star", the list will show just movies with "Sta". So if the user would like to see the actual list of "Star" movies, he'd need to type "Star " (with an extra char to update the previous state).
In other words, the search query is one char behind the State.
How should it be written in React Native?
state = {
query: "",
data: []
};
searchUpdate = e => {
let query = this.state.query;
this.setState({ query: e }, () => {
if (query.length > 2) {
this.searchQuery(query.toLowerCase());
}
});
};
searchQuery = async query => {
try {
const get = await fetch(`${API.URL}/?s=${query}&${API.KEY}`);
const get2 = await get.json();
const data = get2.Search; // .Search is to get the actual array from the json
this.setState({ data });
} catch (err) {
console.log(err);
}
};
You don't have to rely on state for the query, just get the value from the event in the change handler
searchUpdate = e => {
if(e.target.value.length > 2) {
this.searchQuery(e.target.value)
}
};
You could keep state updated as well if you need to in order to maintain the value of the input correctly, but you don't need it for the search.
However, to answer what you're problem is, you are getting the value of state.query from the previous state. The first line of your searchUpdate function is getting the value of your query from the current state, which doesn't yet contain the updated value that triggered the searchUpdate function.
I don't prefer to send api call every change of letters. You should send API just when user stop typing and this can achieved by debounce function from lodash
debounce-lodash
this is the best practise and best for user and server instead of sending 10 requests in long phases
the next thing You get the value from previous state you should do API call after changing state as
const changeStateQuery = query => {
this.setState({query}, () => {
//call api call after already changing state
})
}

AXIOS VueJs passing response parameter within v-for

I have creating VueJs application and passing API calls using AXIOS. Within current scenario user is able to click a button, which will execute function and display list of all unique manufacturers. Within the list a button is assigned, which should let user to see all the models under the manufacturer. As of yer I am unsure how to connect to functions so when clicking on one object it will return user a filter view where models assigned to manufacturer will be showed.
Below I have displayed my code
VueJs
<div v-for="(manufacturerResponse) in manufacturerResponse ">
<p> <b>Manufacturer ID {{manufacturerResponse.manufacturerId}} </b>
<b-btn variant="warning" v-on:click="show(); getModels(response.manufactuerId);">View Models</b-btn>
</p>
</div>
AXIOS - getManufacturer, which displays only unique Manufacturers
getManufacturers () {
AXIOS.get(`/url/`)
.then(response => {
console.log(this.manufacturerResponse)
this.response = response.data
this.manufacturerResponse = []
response.data.forEach(item => {
if (!this.manufacturerResponse.some(fltr => {
return item.manufacturerId == fltr.manufacturerId
})) {
this.manufacturerResponse.push(item);
}
});
})
},
AXIOS - getModel, which displays models under Manufacturer
getModels () {
AXIOS.get(`/url/`)
.then(response => {
const id = 0;
this.testResponse = response.data.filter (kp6 => kp6.manufacturerId === this.manufacturerResponse[id].manufacturerId );
console.log(this.testResponse)
})
},
If it helps also added example how the response appears in the simple array
[
{"id":1,"manufacturerId":1,"model":"Focus"},
{"id":2,"manufacturerId":1,"model":"Transit"},
{"id":3,"manufacturerId":2,"model":"Clio"},
{"id":4,"manufacturerId":3,"model":"Niva"},
{"id":5,"manufacturerId":3,"model":"Yaris"},
]
In template you have below:
v-on:click="show(); getModels(response.manufactuerId);"
But it should be:
v-on:click="show(); getModels(manufacturerResponse.manufacturerId);"
since manufacturerResponse.manufacturerId is the id you are currently displaying and the button click should get the models for that id.
getModels() would receive that param like getModels(manufacturerId) then use that to filter as below:
this.testResponse = response.data.filter (kp6 => kp6.manufacturerId === manufacturerId);
The show() method should be setup to accept a parameter of response.manufactuerId
So ...
v-on:click="show(response.manufacturerId);"
Now... inside your Vue instance
you will need to make sure the method for show looks something like this...
show(manufacturerId){
this.getModels(manufacturerId) // This will call the getModels method on the Vue instance and pass in the manufacturerId that you provided via your click event
}
You can probably just bypass the show method and just have the click event call getModels directly and pass in the manufacturerId directly.

Proper way to access store in ngrx/effect

I am using Angular 6, ngrx/store, ngrx/effects.
I have an effect that should be triggered when i press "Save" button. I am using withLatestFrom there to collect all data what i need for sending it to the server:
#Effect({dispatch: false})
saveAll$ = this.actions$.pipe(
ofType(ActionTypes.Save),
withLatestFrom(
this.store.select(fromReducers.getData1),
this.store.select(fromReducers.getData2),
this.store.select(fromReducers.getData3),
this.store.select(fromReducers.getData4)
),
switchMap(([action, data1, data2, data3, data4]: [ActionType, Data1[], Data2[], Data3[], Data4[]]) => {
// here is some operations with these data
return this.apiService.saveData({data1, data2, data3, data4})
})
)
Here is getData1 selector:
export const getData1= createSelector(
getItems,
getIndexes,
(items, indexes) => {
console.log('HI, I AM getData1');
return transformItems(items, indexes);
}
);
getItems, in turn, return state.items. The problem is that state.items can be modified in another effect:
#Effect()
handleItemsChanges$ = this.actions$.pipe(
ofType(ActionTypes.ChangesInItems),
withLatestFrom(
this.store.select(fromReducers.getItems),
this.store.select(fromReducers.getUsers),
),
switchMap(([action, items, users]: [ActionType, Item[], User[]]) => {
console.log('I AM handleItemsChanges');
const actions = [];
if (itemsShouldBeUpdated) {
actions.push(new UpdateData(changes))
}
})
)
So getData1 selector gets data from the store depend on another effect named handleItemsChanges. handleItemsChanges effect is triggered every time something is changed related to the items and recalc it again.
As a result, in saveAll i am getting not actual state.items.
What am i doing wrong? May be i should use another operator insted of withLatestFrom or what ca be the solution? Thank you
P.S. Btw i am using withLatestFrom every time when i want to get some data from the store. Is it correct?
you need to have action handleItemsChanges fired before saveAll gets fired. One way to do it is to create an effect on handleItemsChanges action and trigger the save action.
The framework will guarantee the order of execution (handleItemsChanges first then save), this way the withLatestFrom operation will work as you expected.
I've found discussion on ngrx github : https://github.com/ngrx/platform/issues/467
Looks like we have 2 ugly variants for accessing store from effects now.

node.js server is hit multiple times into async function in React

I'm working with React, MongoDB, node.js and Express and this is my situation:
I have this piece of code inside my component:
renderWishlist(){
var quantity;
var itemID;
var tmp;
var myData = this.props.currentCart;
// console.log(myData.length) returns 3
for (var k=0; k<myData.length; k++){
tmp = myData[k];
quantity = tmp.quantity;
itemID = tmp.itemID;
this.props.fetchBook(itemID).then(function(book){
console.log("book: "+JSON.stringify(book));
});
}
}
myData is an object which holds a bunch of books info.
As you can see from my code above I'm iterating through all these books, retrieving the ID of the book and the available quantity, then I try to get other information (price, pages, etc...) for that particular book from another collection inside the same MongoDB database.
Once this piece of code is running I keep getting multiple logs like this inside chrome console:
book: {"type":"fetch_book","payload":{"data":{"_id":"58f6138d734d1d3b89bbbe31","chef":"Heinz von Holzen","title":"A New Approach to Indonesian Cooking","pages":"132","price":23,"image":"IndonesianCooking"},"status":200,"statusText":"OK","headers":{"content-type":"application/json; charset=utf-8","cache-control":"no-cache"},"config":{"transformRequest":{},"transformResponse":{},"timeout":0,"xsrfCookieName":"XSRF-TOKEN","xsrfHeaderName":"X-XSRF-TOKEN","maxContentLength":-1,"headers":{"Accept":"application/json, text/plain, */*"},"method":"get","url":"http://localhost:3001/books/58f6138d734d1d3b89bbbe31"},"request":{}}}
Which retrieves correctly the book but it seems to hit the server multiple times for the same book and I don't know why.
For completeness this is fetchBook action creator:
export function fetchBook(id){
const request = axios.get('http://localhost:3001/books/'+id);
return {
type: FETCH_BOOK,
payload: request
};
}
The reducer:
import {FETCH_BOOKS, FETCH_BOOK,FETCH_WISHLIST} from '../actions/types';
const INITIAL_STATE = { myBooks:[], currentCart:[], currentBook:[] };
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_BOOKS:
return { ...state, myBooks:action.payload };
case FETCH_BOOK:
return { ...state, currentBook:action.payload };
case FETCH_WISHLIST:
return { ...state, currentCart: action.payload };
default:
return state;
}
}
My node.js server call:
router.get('/books/:id', function(req, res, next){
Book.findById({_id:req.params.id}).then(function(book){
res.send(book);
}).catch(next);
});
Why the server is hit multiple times? If I have, let's say 3 books inside myData, I'd expect the server to be hit only 3 times.
Another question I have is: How can I make the for loop to wait for fetchBook action to finish before going on iterating the next myData item?
You say renderWishList is called from the render() method. Your call to this.props.fetchBook(itemID) is updating your state, which triggers a re-render, which calls this.props.fetchBook(itemID) and ad infinitum it goes. You can put a console.log() at the start of your render() method to confirm this.
I would call renderWishList() from your constructor() or your componentDidMount() method. And I would rename it to something like createWishList(), because you are not rendering it with this function, but creating the list which needs to be rendered.
Next, you will want to make sure you are updating your state correctly every time your call to fetchBook returns, and then you'll want to use that to render correctly.
Update State
I would change the FETCH_BOOKS reducer to:
case FETCH_BOOK:
return {
...state,
myBooks: [
...state.myBooks,
action.payload
]
};
This will add the book just fetched to the end of the array of book objects myBooks. I am not clear on what is books vs wishlist, so you may want to change the names I've used here. The idea is that when each loop of the for loop is done, your myBooks array in your state has each book that was passed in from this.props.currentCart.
Note, I'm not sure, but you may need to execute a dispatch inside the .then of your this.props.fetchBooks() call.
Render with State
I'm not sure how your are accessing your state, but probably you then want to take your state.myBooks, and map it to create a separate line item, which you can use in your render method. You do this by defining a const like this at the top of your render method:
const mappedBooks = state.myBooks.map(book =>
<div>Title: {book.title}</div>
);
You can then use {mappedBooks} in the return() of your render method where you want a list of the books in myBooks to show on the screen.
Async For Loop
Last, I wouldn't worry that you are running each fetchBook asynchronously. In fact, this is good. If you implement it so that each response updates the state, as I've suggested, then that will trigger a re-render each time and your screen will load with each book. Of course with a few books it will happen so fast it won't matter. But this is a very "React-y" way for it to work.
I can't figure out why your server is hit multiple times. But you can use bluebird npm for your second question. reduce function of bluebird will do exactly what you want.
You can see the documentation here: http://bluebirdjs.com/docs/api/promise.reduce.html
renderWishlist() {
var bluebird = require('bluebird')
// myData is an array
var myData = this.props.currentCart;
bluebird.reduce(myData, function(value, tmp) {
var quantity = tmp.quantity;
var itemID = tmp.itemID;
return this.props.fetchBook(itemID).then(function(book) {
console.log("book: "+JSON.stringify(book));
});
}, 0).then(function() {
// called when myData is iterated completely
});
}
This code should work for you.

Categories