I have setup a project with react-redux and I use redux-thunk in my action-creators to do fetching. Here is an example of my thunk:
export const doPostRequest = id => {
return (dispatch, getState) => {
const { id : initiailId } = getState().currentSelection
return api.post(id).then(response => {
if (!isEqual(initialId, getState().currentSelection.id)){
return;
}
dispatch(someOtherAction(id))
return Promise.resolve(true)
})
.catch(err => {})
}
}
As you can see i want to escape the doPostRequest if the currentSelection of my state is changed by the time the response is receieved. Otherwise I return Promise.resolve(true) so the onSubmit in MyComponent can reset the form:
Inside a component (which is a form) i have the following for onSubmit:
class MyComponent extends React.PureComponent{
onSubmit = id => {
this.props.dispatch(doPostRequest(id))
.then(shouldReset => shouldReset && resetForm())
}
render(){
return <form onSubmit={this.onSubmit}>.....</form>
}
}
In most cases, when I really dont have to do anything else except fetching values, I dont do a Promise-chain on the thunk, even though it returns a promise, but here I need to do a resetForm once the postrequest is a success.
Is this implementation good enough, also when it comes to GC ? How are Promises garbage collected ? Is there a problem if I return a fetch().then() without chaining it further?
Related
I'm trying to render some JSON and the error I get references some fields that don't exist in my JSON structure. The fields are getting logged to the console properly.
Which object is this referring to, and how do I fix it?
Error: Objects are not valid as a React child (found: object with keys {_events, _eventsCount, _maxListeners, uri, callback, readable, writable, _qs, _auth, _oauth, _multipart, _redirect, _tunnel, headers, setHeader, hasHeader, getHeader, removeHeader, method, localAddress, pool, dests, __isRequestRequest, _callback, proxy, tunnel, setHost, originalCookieHeader, _disableCookies, _jar, port, host, path, httpModule, agentClass, agent}). If you meant to render a collection of children, use an array instead.
Postlist:
class PostList extends React.Component {
render() {
return (
request('http://194.5.192.153:3044/api/posts/', function (error,response,body) {
let items = JSON.parse(body).items;
for(let i=0; i<items.length; i++) {
console.log(items[i].author,items[i].desc,items[i].updatedAt,items[i].title);
return (
<Post author={items[i].author} desc={items[i].desc} image={items[i].image} title={items[i].title} createdAt={items[i].createdAt} updatedAt={items[i].updatedAt} />
);
}
})
)
}
}
Post:
const Post = (props) => {
return (
<>
<img src={props.image} alt="" />
<h1>{props.title}</h1>
<h2>by {props.author}</h2>
<div>Created at {props.createdAt}</div>
<div>Updated at {props.updatedAt}</div>
<div>{props.desc}</div>
</>
);
}
export default Post;
In your code, you are effectively trying to render the return value from request, which is certainly not what you want. Since request is asynchronous, the general pattern here is to set state in the callback and then map over that state in the render method.
class PostList extends React.Component {
state = { items: [] };
componentDidMount() {
request(
"http://194.5.192.153:3044/api/posts/",
(error, response, body) => {
const items = JSON.parse(body).items;
this.setState({ items });
}
);
}
render() {
return this.state.items.map((item) => (
<Post
key={item.title}
author={item.author}
desc={item.desc}
image={item.image}
title={item.title}
createdAt={item.createdAt}
updatedAt={item.updatedAt}
/>
));
}
}
I am performing an authentication module where I when I click the sign in button , I am verifying user present is MySQL db or not . I am dispatching the function in here in sign in page
Basically when I dispatch it , the null state of the rSignedIn is not changed immediately after dispatch function. I am completely using react hooks. Please help me solve this , I have been trying this for three days.
But the rSignedIn state value updates when I click the login button again, in general , the when I use the state value using the useSelector the value is updated the second the time when the handleLogin() is invoked
//Sign in Page
...
...
const status=useSelector((state)=>state);
...
...
const handleLogin=(event)=>{
dispatch(LoginUser(loginData));
console.log(status.auth.rSignedIn);
if(status.auth.rSignedIn){
console.log("LOGIN success");
History.push('/');
}else{
console.log("LoginFailed") ;
}
}
this is the action index page where I sent a request to MySQL db , then if there is a response I am dispatching it else an error.
export const LoginUser=(loginData)=>async(dispatch)=>{
await mysqlDB.post('/fetch/retreive',loginData)
.then((response)=>dispatch({type:ActionTypes.LOGIN_SUCCESS,payload:response.data}))
.catch((error)=>dispatch({type:ActionTypes.LOGIN_FAILED}))
}
This is my Reducer for this :
const initialState = {
gSignedIn:null,
userId:null,
registered:null,
data:null,
rSignedIn:null,
}
export default (state=initialState,action)=>{
switch (action.type){
case ActionTypes.GSIGN_IN:
return {...state,gSignedIn:true,userId: action.payload};
case ActionTypes.GSIGN_OUT:
return {...state,gSignedIn:false,userId:null};
case ActionTypes.REGISTER_SUCCESS:
return {...state,registered:true,data: action.payload};
case ActionTypes.REGISTER_FAILED:
return {...state,registered:false,data:null};
case ActionTypes.LOGIN_SUCCESS:
return {...state,rSignedIn:true,data: action.payload};
case ActionTypes.LOGIN_FAILED:
return {...state,rSignedIn:false,data:null};
case ActionTypes.LOGOUT:
return {...state,rSignedIn:false,data:null};
default:
return state;
}
};
dispatch will not update your state value immediately. State value is bound by closure and will only update in your next render cycle.
You can either use history.push within your action or make use of useEffect
const handleLogin=(event)=>{
dispatch(LoginUser(loginData, History));
}
...
export const LoginUser=(loginData, history)=>async(dispatch)=>{
await mysqlDB.post('/fetch/retreive',loginData)
.then((response)=>{
dispatch({type:ActionTypes.LOGIN_SUCCESS,payload:response.data}));
history.push('/')
}
.catch((error)=>{
dispatch({type:ActionTypes.LOGIN_FAILED}))
}
}
With the useEffect, you need to run the it only on change and not on initial render
const initialRender = useRef(true);
useEffect(() => {
if(!initialRender.current) {
if(state.auth.rSignedIn) {
history.push('/');
} else {
console.log(not signed in);
}
} else {
initialRender.current = false;
}
}, [state.auth.rSignedIn])
I want to race between a redux action and an event channel using redux-saga.
export function * createEventChannel () {
return eventChannel(emit => {
MyModule.addEventListener(MyModule.MY_EVENT, emit)
return () => { MyModule.removeEventListner(MyModule.MY_EVENT, emit)}
})
}
....
function * raceWithActionAndEvent() {
const channel = yield call(createEventChannel)
// I want to race between Redux Action: 'MY_ACTION' and channel here
}
This should do it:
export function* raceWithActionAndEvent() {
const channel = yield call(createEventChannel);
const winner = yield race({
channel: take(channel),
action: take(MY_ACTION),
});
if (winner.action) {
// then the action was dispatched first
}
if (winner.channel) {
// then the channel emitted first
}
}
In my opinion the code is quite readable. You set up a race between two takes and act on whichever wins.
Please note,createEventChannel doesn't need to be a generator function (like you have in the original question)
Here is my problem.
I'm running a method that sends me a json (method = myTableService.getAllTables ()), to create an object (object = this.myTables).
Then I execute the method for each, for each element of this.myTables I execute a new request (request = this.myTableService.getTableStatut (element.theId)).
I retrieve data from a new json to create an object (object = myTableModel).
Each result will be added to this.myTableListProvisory.
The problem is the order of execution.
It execute the console.log before the end of the for each...
This.myTableListProvisory.length and this.myTableList.length return 0.
How to wait for the end of the for each run before running the console.log?
Thank you
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
console.log("this.myTableList.length : " + this.myTableList.length);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
console.log("this.myTableListProvisoire.length : " + this.myTableListProvisoire.length);
})
return this.myTableListProvisoire;
}
Result of console.log
this.myTableListProvisoire.length : 0
this.myTableList.length : 0
UPDATE
I have simplified the code ... I put it in its entirety for the understanding. What I need is to sort the array after it is done. The problem is that I don't know how to use a flatMap method in a query inside a foreach ... I have temporarily placed the sort method inside the subscribe which is a bad solution for the performance. That's why I want to do my sort after the creation of the array. Thank you
export class MyTableComponent implements OnInit {
myTables: any;
statut: any;
myTableModel: MyTableModel;
myTableList: Array<MyTableModel>;
myTableListProvisoire: Array<MyTableModel>;
i: number;
j: number;
myTableModelProvisoire: MyTableModel = null;
constructor(public myTableService: MyTableService) { }
ngOnInit() {
this.myTableService.getAllTables()
.subscribe(data => {
this.myTables = data;
this.myTableList = this.getAllTableStatut(this.myTables);
}, err => {
console.log(err);
})
}
getAllTableStatut(myTables: any) {
this.myTableListProvisoire = [];
myTables.forEach(element => {
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
for (this.j = 0; this.j < this.myTableListProvisoire.length; this.j++) {
for (this.i = 0; this.i < this.myTableListProvisoire.length - 1; this.i++) {
if (this.myTableListProvisoire[this.i].getTableNumber() > this.myTableListProvisoire[(this.i + 1)].getTableNumber()) {
this.myTableModelProvisoire = this.myTableListProvisoire[this.i];
this.myTableListProvisoire[this.i] = this.myTableListProvisoire[(this.i + 1)];
this.myTableListProvisoire[(this.i + 1)] = this.myTableModelProvisoire;
}
}
}
}, err => {
console.log(err);
})
}, err => {
console.log(err);
})
return this.myTableListProvisoire;
}
}
Well Observables are asynchronous actions and will be executed after finishing the current execution block. So when the js engine comes to your
this.myTableService.getTableStatut(element.theId)
.subscribe(data => {
this.statut = data;
this.myTableModel = new MyTableModel(element.tableNumber, this.statut.name, element.theId);
this.myTableListProvisoire.push(this.myTableModel);
})
it will only create a subscription, but the code inside of it will be executed after all the other code in the block. So that's why your console.log is being executed before you get any data. So you need to place it inside the .subscribe block to see the. I think there can be a better solution to get the data, but I don't know the structure of the app, so I can't advice. If you create an example on https://stackblitz.com/ I could probably help you out with a better solution.
Consider this scenario:
app loads => fetches json from api => needs to modify json returned
In this case, I'm using moment to make some date modifications and do some grouping that I'll use in the UI. I looked on stack and found a similar question but didn't feel like it provided the clarity I am seeking.
Where should I use .map to create the new objects that contain the formatted & grouped dates? Should I manipulate the raw json in the api call or in the redux action before I dispatch? What is the best practice?
Is it OK to add properties and mutate the object as I am showing below,
service["mStartDate"] = mStartDate before I put the data into my store and treat it as immutable state?
First Approach - changing raw json in the api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
})
}
}
2nd Approach, make a simple api call
class TicketRepository extends BaseRepository {
getDataByID(postData) {
return this.post('api/lookup', postData)
}
}
Change the json in the action before dispatching
export function getDataByID() {
return (dispatch, getState) => {
dispatch(dataLookupRequest())
const state = getState()
const groupedData = {}
return TicketRepository.getDataByID(userData)
.then(result => {
const groupedData = {}
return result.map(ticket => {
const mStartDate = moment(ticket.startDate)
const mEndDate = moment(ticket.endDate)
const serviceLength = mStartDate.diff(mEndDate,'hours')
const duration = moment.duration(serviceLength,"hours").humanize()
const weekOfYear = mStartDate.format('WW')
const dayOfWeek = mStartDate.format("d")
if(!groupedData.hasOwnProperty(weekOfYear)){
groupedData[weekOfYear] = {}
}
if (!groupedData[weekOfYear].hasOwnProperty(dayOfWeek)) {
groupedData[weekOfYear][dayOfWeek] = []
}
service["mStartDate"] = mStartDate
service["mEndDate"] = mEndDate
service["serviceLength"] = serviceLength
service["duration"] = duration
groupedData[weekOfYear][dayOfWeek].push(service)
})
return groupedData
})
.then(groupedData => {
dispatch(lookupSuccess(groupedData))
})
.catch(err => dispatch(dataLookupFailure(err.code, err.message)))
}
}
All data manipulation should be handled by your reducer. That is, the returned response data should be passed on to a reducer. This practice is common, because this way if there's a problem with your data, you will always know where to look - reducer. So neither of your approaches is "correct". Actions should just take some input and dispatch an object (no data manipulation).
When you want to manipulate data for 'view' purposes only, consider using reselect library, which makes it easier to handle "data views" that are composed of the existing data.