I'm new to NgRx, and trying to retrieve and cache paginated table data using Effects and http request.
But on any first time page load (if page isn't cached already) I got empty page, even though if I do console.log of state object, I see data inside?
When I go on previous page, data is there, so I'm guessing something in async world I'm doing wrong, but can't figure out what :/
here is my initialization in
component.ts
ngAfterViewInit() {
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
this.store.dispatch(new ListLoad(this.getQueryParams()));
return this.store.pipe(select('list'));
}),
map((state: State) => {
this.isLoadingResults = false;
this.resultsLength = state.totalRecords;
return this.cacheKey in state.data ? state.data[this.cacheKey] : [];
}),
catchError((err) => {
this.isLoadingResults = false;
this.resultsLength = 0;
return observableOf([]);
})
)
.subscribe((data: any[]) => {
return this.data = data
});
}
and here is my effect definition
effects.ts
#Effect()
loadData = this.actions$.pipe(
ofType(actions.actionTypes.ListLoad),
mergeMap((action: actions.actionTypes.ListLoadSuccess) => this.service.getAll(action.payload).pipe(
map(
response => {
let apiResponse = new ApiResponse(response);
let cacheKey = JSON.stringify(action.payload);
return apiResponse.isSuccess ?
new actions.ListLoadSuccess({ key: cacheKey, data: apiResponse.data }) :
new actions.ListLoadFailed(`code: ${apiResponse.status.error_code}; message: ${apiResponse.status.error_message}`);
}
),
catchError(err => observableOf(new actions.ListLoadFailed(err)))
))
)
In addition to this, I would like to cancel http request, if page containing the data is present in NgRx store
I was able to resolve it. Issue was that I was updating property of store which is object, by adding new property to it. Store does not emit event that fragment is updated, so Select subscription is not triggered. I've introduced another boolean param for loading state, which I listen for changes, and if loading is false (page is loaded), I select desired fragment.
I've also added extra code for page caching
component.ts
ngOnInit() {
this.isLoadingResults$ = this.store.pipe(
select(state => state.stateFragment.isListLoading),
takeWhile(() => this.componentActive) //unsubscribe
);
this.store.dispatch(new ListLoad());
this.isLoadingResults$.pipe(
filter((isLoading:boolean) => !isLoading),
switchMap(() => this.store.pipe(
select(state => state.stateFragment),
takeWhile(() => this.componentActive) //unsubscribe
)),
map(...)
).subscribe(...);
//Other stuff here
}
effects.ts
#Effect()
load$ = this.actions$.pipe(
ofType(actions.actionTypes.ListLoad),
withLatestFrom(this.store.pipe(select(state.stateFragment))),
filter(([action, store]) => {
let isPageCached: boolean = action.payload in store.stateFragment;
if (isPageCached) {
this.store.dispatch(new actions.ListLoaded()); //for sake of changing loading state
}
return !isPageCached;
}),
switchMap(([action, store]) => {
return this.service.getAll(action.payload).pipe(
map(
response => {
let apiResponse = new ApiResponse(response);
return apiResponse.isSuccess ?
new actions.ListLoadSuccess({ key: action.payload, data: apiResponse.getData(), totalRecords: apiResponse.getTotalCount() }) :
new actions.ListLoadFailed(`code: ${apiResponse.status.error_code}; message: ${apiResponse.status.error_message}`);
}
),
catchError(err => observableOf(new actions.ListLoadFailed(err)))
);
}
), share()
)
reducer.ts
export function reducer(state = initialState, action: Actions) {
switch (action.type) {
case actionTypes.ListLoad:
return {
...state,
isListLoading: true
};
case actionTypes.ListLoaded:
return {
...state,
isListLoading: false
};
case actionTypes.ListLoadSuccess:
state.listData[action.payload.key] = action.payload.data;
return {
...state,
isListLoading: false,
listData: state.listData,
listTotal: action.payload.totalRecords
};
case actionTypes.ListLoadFailed:
return {
...state,
isListLoading: false,
error: action.payload
};
case actionTypes.ListClear:
return {
...state,
listData: {},
listTotal: 0
};;
default:
return state;
}
}
Related
any idea why my cloud function is telling my that (Not all code paths return a value) I put a return on the promise at the end also on the if statement yet it is not working, I read about similar issues tried to solve according to them but I still missing part of the puzzle, the error started showing when I wrapped the whole thing with try-catch.
export const createClient = functions.https.onCall(async(data, context) => {
try {
const snapshotCheckBelong = await db.collection('data- main').doc(data.sharedId).get();
const getClient = snapshotCheckBelong.data() ? .clients.filter((el: any) => el.clientId === data.clientId);
const snapshotCheckExist = await db.collection('data-registered-clients').doc(data.sharedId).get();
const checkClient = snapshotCheckExist.data() ? .clients.filter((el: any) => el.clientId === data.clientId);
if (checkClient[0]) {
return {
result: 'Registered client already exisits.'
};
}
if (getClient[0] ? .clientId === data.clientId && checkClient.length === 0) {
const payload = {
user: 'client',
verifiedEmail: false,
createdAt: admin.firestore.Timestamp.now(),
};
let auth = admin
.auth()
.getUserByEmail(context.auth ? .token ? .email!)
.then((user) =>
admin.auth().setCustomUserClaims(user.uid, {
userType: 'client',
premiumUnitll: admin.firestore.Timestamp.fromDate(snapshotCheckBelong.data() ? .premiumUntill)
})
)
.catch((err: any) => {
console.log(err);
});
let setClientData = db
.collection('data-clients')
.doc(context.auth ? .uid!)
.set(payload)
.catch((err: any) => {
console.log(err);
});
let updateRegisteredClients = db
.collection('data-registered-clients')
.doc(data.sharedId)
.update({
clients: admin.firestore.FieldValue.arrayUnion({
clientAccount: context.auth ? .uid,
clientId: data.clientId,
trainerId: data.sharedId
})
})
.catch((err: any) => {
console.log(err);
});
return Promise.all([auth, setClientData, updateRegisteredClients]);
} else {
return null;
}
} catch {
(err: any) => {
console.log(err);
};
}
});
Your catch blocks are not returning anything. If you handle an error with catch, you have to determine what value gets returned to the caller.
I typically prefer sending the error back to the caller, as shown in the documentation on handling errors in callable cloud functions and on the calling client.
I have to fetch 2 api from backend, and try to get the result from this two. but, at the moment, the JSON result I get from the first API is object Array in JSON. I need to pass the id from first API(using setState) to second API for path variables. But when I do in my way, it fail to retrieve the data. Consider the code below:
componentDidMount(){
// console.log(loginEmail)
fetch(`http://localhost:9000/api/item/list`,)
.then((resp)=>{
resp.json().then((res)=>{
console.log(res.data);
// localStorage.setItem('id', res.data.user_info.id);
this.setState({data: res.data});
}
)
})
const id = this.state.data.id;
fetch(`http://localhost:9000/api/item/photo/view/${id}`,)
.then((resp)=>{
resp.json().then((res)=>{
console.log(res);
// localStorage.setItem('id', res.data.user_info.id);
this.setState({res});}
)
})
}
The problem is that fetch returns a Promise so, at the line
const id = this.state.data.id;
You do not have data populated yet.
You have to concatenate the two requests in a way like the following:
componentDidMount() {
fetch(`http://localhost:9000/api/item/list`)
.then((resp) => {
// return the id
})
.then((id) => {
fetch(`http://localhost:9000/api/item/photo/view/${id}`)
.then((resp) => {
// do what you need with the result
})
})
}
Fetch is asynchronous, which means javascript will
fetch data on the first call with no waiting, and continue
to the second fetch call where the id is not defined or Null.
In order to fix that you can use promises as follow
My code example
import React from "react";
class Home extends React.Component {
constructor() {
super();
this.state = {
res: [],
}
}
// http://jsonplaceholder.typicode.com/users
fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((resp) => {
resp.json().then((res) => {
console.log(res);
// localStorage.setItem('id', res.data.user_info.id);
resolve(res);
}
)
})
})
}
async componentDidMount() {
let data = await this.fetchData("http://jsonplaceholder.typicode.com/users");
console.log("data :", data);
let id = data[0].id;
console.log("Id :", id);
let newData = await this.fetchData(`http://jsonplaceholder.typicode.com/users/${id}`);
this.setState({ res: newData });
}
render() {
return (
<div>
Call API
</div>
)
}
}
export default Home
Adapted on your code
fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((resp) => {
resp.json().then((res) => {
console.log(res.data);
// localStorage.setItem('id', res.data.user_info.id);
resolve(res.data);
}
)
})
})
}
async componentDidMount() {
// console.log(loginEmail)
let data = await this.fetchData("http://localhost:9000/api/item/list");
let id = data.id;
let newData = await this.fetchData(`http://localhost:9000/api/item/photo/view/${id}`);
this.setState({ res: newData });
}
You need to make sure that each id gets its relevant results.
async componentDidMount() {
await fetch(`http://localhost:9000/api/item/list`)
.then(async (resp) => {
let req_ = resp.map((item)=>{
return await fetch(`http://localhost:9000/api/item/photo/view/${item.id}`)
})
let result = Promise.all(req_)
console.log(result)
})
}
I used componentDidUpdate and in it I put a shift method, which is used to delete an object from a JSON array and thereby re-render the displayed posts, but the shift method deletes the first object from the array independently in which the delete button on the post will I press? Is there any possibility, then, to bypass the deletion of the first element in favor of the one that is designated to be deleted?
componentDidUpdate(prevProps, prevState) {
const {posts} = this.props;
const indexPosts = posts.findIndex((post) => post.id === this.state.postId);
if(prevProps.posts !== posts){
this.handleData();
} else if (indexPosts !== -1)
{
this.informationAlert();
const log = posts.splice(indexPosts, 1);
console.log(log);
}
}
EDIT: Actions
export const deletedPost = (id) => (dispatch) => {
axios
.delete(`https://jsonplaceholder.typicode.com/posts/${id}`, id, {
headers: {
'Content-type': 'application/json'
}
})
.then((post) =>
dispatch({
type: DELETED_POST,
payload: post.data
})
)
.catch((err) => console.log(err));
};
import { FETCH_POSTS, NEW_POST, DELETED_POST, FETCH_COMMENTS, NEW_COMMENT } from '../actions/types';
const initialState = {
items: [],
item: {},
itemComent: [],
itemNewComment: {},
deletedPost: []
};
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_POSTS:
return {
...state,
items: action.payload
};
case NEW_POST:
return {
...state,
item: action.payload
};
case DELETED_POST:
return {
...state,
deletedPost: action.payload
};
case FETCH_COMMENTS:
return {
...state,
itemComent: action.payload
}
case NEW_COMMENT:
return {
...state,
itemNewComment: action.payload
}
default:
return state;
}
}
EDIT 2:
const mapStateToProps = (state) => ({
posts: state.posts.items,
newPost: state.posts.item,
deletedPost2: state.posts.deletedPost
});
EDIT 3:
handleDeletedPost = (id) => {
this.setState({
postId: id
})
}
Edit 4:
//I added in constructor
this.state: dataPost: '',
//next I create function to load data and send to state dataPost
handleData = (e) => {
const {posts} = this.props;
const {dataPost} = this.state;
const letang = posts;
const postsData = dataPost;
if (postsData.length <= 0) {
this.setState({
dataPost: letang
})
} else {
console.log('stop')
}
}
// next i create in componentDidUpdate this code
componentDidUpdate(prevProps, prevState) {
const {posts} = this.props;
const indexPosts = posts.findIndex((post) => post.id === this.state.postId);
if(prevProps.posts !== posts){
this.handleData();
} else if (indexPosts !== -1)
{
this.informationAlert();
const log = posts.splice(indexPosts, 1);
console.log(log);
}
}
** When I added loop if (indexPosts !== -1) then my array is cut only one object :-)
API Posts: https://jsonplaceholder.typicode.com/posts/
The results are visible at this link when you press details and then the delete icon: https://scherlock90.github.io/api-post-task/
You need to use splice to delete an element from array.
In splice you need to provide startIndex and number of elements to remove in second argument. In below code find index using `findIndex method, second argument is 1 as we need to remove only 1 element.
Try this
componentDidUpdate (prevProps, prevState) {
if (prevProps.deletedPost) {
const { posts } = this.props
const letang = posts.splice(posts.findIndex( (post)=> post.id === prevProps.deletedPost.id), 1);
console.log(posts); // this should have array without deletedPost
}
}
This might help:
componentDidUpdate (prevProps, prevState) {
if (prevProps.deletedPost) {
const letang = this.props.posts;
letang.splice(deletedPost, 1);
}
}
the slice() takes the index of the object and an optional number of items to delete. since you just want to delete a single object you pass 1.
This might help, try filtering out the object you dont want in the array.
componentDidUpdate (prevProps, prevState) {
if (prevProps.deletedPost) {
const letang = this.props.items.filter(p => p.id !== prevProps.deletedPost.id);
}
}
UPDATED
I think you should be deleting the items in your redux store rather than trying to delete the posts from your api since the api might rather be generating the same data randomly. Change your actionCreator to
export const deletedPost = (id) => {
dispatch({
type: DELETED_POST,
payload: id
});
};
Then use this in your reducer so you can focus on items array coming from your reducer store. Then remove deletedPost: [] from your reducer.
...
case DELETED_POST:
const newItems = state.items.filter(p => p.id !== action.payload);
return {
...state,
items: [ ...newItems ],
};
...
use splice() to delete :), you can find the index of post which should be deleted and then delete it by using this method.
I not undestand everything with javascript etc, I want to get my data returned by ma action redux but i'have a problem with my code.
const mapStateToProps = state => {
const group = state.groupReducer.group ? state.groupReducer.group : [ ]
return {
group
}
how i can get my data ?
When I try with that:
const mapStateToProps = state => {
const group = state.groupReducer.group.data.data[0] ? state.groupReducer.group.data.data[0] : [ ]
return {
group
}
And my goal is map around group
renderGroup = group => {
return group.map((groups => {
<div key={groups.data.data.id}>
//
</div>
}))
}
Sagas.js
export function* loadApiDataGroup() {
try {
// API
const response = yield
call(axios.get,'http://localhost:8000/api/group');
yield put(loadGroup(response))
} catch (e) {
console.log('REQUEST FAILED! Could not get group.')
console.log(e)
}
}
Action.js
export function loadGroup(data){ return { type: LOAD_GROUP, data }};
export function creatGroup(data){ return { type: CREATE_GROUP, data}};
// reducer
export default function groupReducer( state= {}, action = {}){
switch (action.type){
case LOAD_GROUP:
return {
...state,
group: action.data
}
case CREATE_GROUP:
return {
...state
}
default:
return state
}
thank you to help me
Try
const mapStateToProps = state => ({
group: state.groupReducer.group || []
});
Then you can use this.props.group in the component. Even though you might only want one thing in mapStateToProps, it's usually not directly returned like that.
If group is the response of an API request, you need to unpack data first, this is done in your async action creator (you will want to use redux-thunk or something similar):
const getGroup = () => async (dispatch) => {
dispatch({ type: 'GET_GROUP_REQUEST' });
try {
const { data } = await axios.get('/some/url');
dispatch({ type: 'GET_GROUP_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'GET_GROUP_FAILURE', payload: error });
}
};
I am working on a calculator using MERN stack but my client and server are different projects. React App is running on 3030 and Node Backend on 3000. I am able to retrieve the correct response from Node Backend, but not able to update it to the store, mostly due to the issue with the scope of 'state' or returned data. Below is my code snippet :
const calcReducer = (state = calcState, action) => {
switch(action.type){
case 'ADD_ELEM':
return {
...state,
value: state.value == 0 ? action.text : state.value + action.text
}
case 'CLEAR':
return{
...state,
value: 0
}
case 'EQUAL':
const url = 'http://localhost:3000/calculate';
superagent
.post(url)
.send({ exp : state.value })
.set('Accept', 'application/json')
.end((err,data) => {
if (err)
return state;
else {
console.log(state); //prints the old value of the state
//below prints the correct state value but returning the state from here doesn't work
console.log({
...state,
value : Number(JSON.parse(data.text).result)
})
}
})
return {
...state,
value : VALUE // how can the value be brought here from inside of else loop
}
default:
return state;
}
}
console.log statement inside 'else' prints correctly but no effect if I return state value from there. The place from where I am currently returning 'state' is not working out for me, and the returned state is exactly same as the state before the control came inside the case. Can someone please explain me how to work with the scope as I am new to ES6?
Edit1:
When I try to take the 'async-ness' out of the reducer, and make change as given below:
const mapStateToProps = (state) => {
return{
value: state.value,
btns: state.btns
}
}
const mapDispatchToProps = (dispatch) => {
return{
addElem: (text) => {
dispatch({
type: 'ADD_ELEM',
text
})
},
clear: () => {
dispatch({
type: 'CLEAR'
})
},
equal: (value) => {
console.log(value)
superagent
.post('http://localhost:3000/calculate')
.send({ exp : value })
.set('Accept', 'application/json'))
.end((err,data) => {
dispatch({ type: 'EQUAL', JSON.parse(data.text).result })
})
}
}
}
In this case, code build fails saying:
Module build failed: SyntaxError: Unexpected token (74:2)
72 |
73 | const mapStateToProps = (state) => {
> 74 | return{
| ^
75 | value: state.value,
76 | btns: state.btns
77 | }
There are syntax errors in your mapDispatchToProps, try to well indent your code so it will be more easy to identify them.
const mapDispatchToProps = (dispatch) => {
return {
addElem: (text) => {
dispatch({
type: 'ADD_ELEM',
text
})
},
clear: () => {
dispatch({
type: 'CLEAR'
})
},
equal: (value) => {
console.log(value)
superagent
.post('http://localhost:3000/calculate')
.send({ exp : value })
.set('Accept', 'application/json')
.end((err,data) => {
dispatch({ type: EQUAL, result: JSON.parse(data.text).result })
})
}
};
};
The return statement is within a callback function. So, you are returning only from the callback function. You probably should wrap it in a Promise as follows.
return new Promise((resolve, reject) => {
superagent(...)
...
.end((err, data) => {
if (err)
reject(state);
else {
resolve({
...state,
value : Number(JSON.parse(data.text).result)
});
}
});
});