React Meteor How to execute a function after withTracker data arrives? - reactjs

I am publishing data from server and catching it using withTracker.
export default withTracker(() => {
let towndatasub = Meteor.subscribe("userTownDataPublisher",Meteor.userId());
let resourcedatasub = Meteor.subscribe("userResourcePublisher",Meteor.userId());
return{
townData : Towns.find({"ownerId":Meteor.userId()}).fetch(),
resourceData : Resources.find({"ownerId":Meteor.userId()}).fetch()
}
})(TownPage);
The problem is i would like to run a function when townData and resourceData arrives.If i call updateResources in componentDidMount i get undefined on this.props.componentWillReceive props not called.townData and this.props.resourceData
updateResources = () =>{
Meteor.call("updateUserResources",Meteor.userId(),(err,result)=>{
if(err){
console.log(err)
}else{
console.log("asdasd");
//console.log(this.props.resourceData); undefined
// here i will do something with this.props.resourceData
}
})
}
So where should i call updateResources function to not get undefined ?

Firstly, componentDidMount is only called once when a page is loaded, right after the first call to render finishes. Therefore, you shouldn't call updateResources there since there's a chance that the collections haven't finished loading from the server by then. I would recommend calling it in render because render will be called once before the data has arrived and again after the data arrives.
Secondly, if you want to be even more accurate with when the data arrives, you can return two more properties in withTracker involving the ready function like so,
export default withTracker(() => {
let towndatasub = Meteor.subscribe("userTownDataPublisher",Meteor.userId());
let resourcedatasub = Meteor.subscribe("userResourcePublisher",Meteor.userId());
return{
townData : Towns.find({"ownerId":Meteor.userId()}).fetch(),
resourceData : Resources.find({"ownerId":Meteor.userId()}).fetch(),
townsReady : towndatasub.ready(),
resourcesReady : resourcedatasub.ready()
}
})(TownPage);
And then in render, you can call updateResources only when the data has arrived,
if(this.props.townsReady && this.props.resourcesReady) {
this.updateResources();
}

Related

How to make React Suspense and pending promise working

Many articles writing about how to return pending promise and work with React suspense but it's not working in real world.
They don't consider if the component got visited second time, and it won't refetch the data from the server.
e.g. => https://dev.to/darkmavis1980/a-practical-example-of-suspense-in-react-18-3lln?signin=true
The below example would only work for the first time we visit the component but not re-fetch data for the following times.
Any idea to let it work to prevent not doing re-fetching?
Component
const dataFetchWithWrapPromise = (url) => {
return wrapPromise(window.fetch(url, {
}));
}
const resource = dataFetchWithWrapPromise('http://localhost:3000/data');
function Articles() {
const data = resource.read();
React.useEffect(() => {
return () => {
resource.reset();
}
}, []);
return (
<>
<h1>Data</h1>
<pre>
{JSON.stringify(data, null, 4)}
</pre>
</>
);
}
export default Articles;
function wrapPromise(promise) {
let status = 'pending';
let response;
const suspender = promise.then(
async res => {
status = 'success';
response = await res.json();
},
err => {
status = 'error';
response = err;
},
);
const handler = {
pending: () => {
throw suspender;
},
error: () => {
throw response;
},
success: () => {
console.log(response)
return response
},
default: () => {
throw suspender;
},
};
const read = () => {
const result = handler[status] ? handler[status]() : handler.default();
return result;
};
const reset = () => {
if(status!=='pending') {
status = 'pending';
response = undefined;
}
}
return { read, reset };
}
export default wrapPromise;
Ok, so I think I got you covered. It so happens that I liked <Suspense> ever since I heard of it. I stumbled with it in my learning of asynchronous JavaScript because I was coding wj-config. This preface is just to let you know that I'm no React master, but it so happens that I ended up creating a React example for wj-config v2.0.0, which is currently in BETA 2. This example does what you want.
So no more chit-chat. The code of interest is here.
It is a table component that loads person data from Mockaroo. The web page (parent) has two controls to specify the number of rows wanted as well as the minimum birth date wanted. Whenever the value of any of those controls change, the person data is re-fetched. The table itself uses <Suspense> in two places.
The component module starts by defining the fetching functions needed for person and country data. Then it declares some variables that are captured in scopes later on. The starting promise is required for the first render. Its resolver is exposed through startingResolver, and the starting promise is wrapped as per the <Suspense> mechanics that you clearly know.
Focus your attention now to the PersonsTable function. It sets up a useEffect call to re-trigger the data fetching operations based on changes of props. As I'm not a super master in ReactJS, maybe there's a better way than props. I just know props will trigger the effect automatically, so I used them.
On the first render, the starting promise is thrown, but it will never be fulfilled since it is a bogus promise. The code inside useEffect makes this promise resolve at the same time the fetching promise resolves. Then, using the fetching promise, the readPersons function is defined.
NOTE: I'm not a native English speaker. Pardon my horrible "persons" instead of "people" mistake. :-( I'll correct whenever I have time.
Anyway, with this set up, you'll have completed your goal. The linked code sample goes beyond this by having an inner <Suspense> that waits for country data, but I suppose an explanation is not needed since I believe the question is now covered.
Hope this helps!

MobX not hydrating in next.js state when fetching async data

I have a MobX store where I have a function doing an API call. It works fine it's getting the data but it doesn't update the already rendered page. I'm following this tutorial https://medium.com/#borisdedejski/next-js-mobx-and-typescript-boilerplate-for-beginners-9e28ac190f7d
My store looks like this
const isServer = typeof window === "undefined";
enableStaticRendering(isServer);
interface SerializedStore {
PageTitle: string;
content: string;
isOpen: boolean;
companiesDto: CompanyDto[],
companyCats: string[]
};
export class AwardStore {
PageTitle: string = 'Client Experience Awards';
companiesDto : CompanyDto[] = [];
companyCats: string[] = [];
loadingInitial: boolean = true
constructor() {
makeAutoObservable(this)
}
hydrate(serializedStore: SerializedStore) {
this.PageTitle = serializedStore.PageTitle != null ? serializedStore.PageTitle : "Client Experience Awards";
this.companyCats = serializedStore.companyCats != null ? serializedStore.companyCats : [];
this.companiesDto = serializedStore.companiesDto != null ? serializedStore.companiesDto : [];
}
changeTitle = (newTitle: string) => {
this.PageTitle = newTitle;
}
loadCompanies = async () => {
this.setLoadingInitial(true);
axios.get<CompanyDto[]>('MyAPICall')
.then((response) => {
runInAction(() => {
this.companiesDto = response.data.sort((a, b) => a.name.localeCompare(b.name));
response.data.map((company : CompanyDto) => {
if (company.categories !== null ) {
company.categories?.forEach(cat => {
this.addNewCateogry(cat)
})
}
})
console.log(this.companyCats);
this.setLoadingInitial(false);
})
})
.catch(errors => {
this.setLoadingInitial(false);
console.log('There was an error getting the data: ' + errors);
})
}
addNewCateogry = (cat : string) => {
this.companyCats.push(cat);
}
setLoadingInitial = (state: boolean) => {
this.loadingInitial = state;
}
}
export async function fetchInitialStoreState() {
// You can do anything to fetch initial store state
return {};
}
I'm trying to call the loadcompanies from the _app.js file. It calls it and I can see in the console.log the companies etc but the state doesn't update and I don't get to see the actual result. Here's the _app.js
class MyApp extends App {
constructor(props) {
super(props);
// Don't call this.setState() here!
this.state = {
awardStore: new AwardStore()
};
this.state.awardStore.loadCompanies();
}
// Fetching serialized(JSON) store state
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
const initialStoreState = await fetchInitialStoreState();
return {
...appProps,
initialStoreState
};
}
// Hydrate serialized state to store
static getDerivedStateFromProps(props, state) {
state.awardStore.hydrate(props.initialStoreState);
return state;
}
render() {
const { Component, pageProps } = this.props;
return (
<Provider awardStore={this.state.awardStore}>
<Component {...pageProps} />
</Provider>
);
}
}
export default MyApp;
In the console.log I can see that this.companyCat is update but nothing is changed in the browser. Any ideas how I can do this? Thank you!
When you do SSR you can't load data through the constructor of the store because:
It's does not handle async stuff, so you can't really wait until the data is loaded
Store is created both on the server side and on the client too, so if theoretically constructor could work with async then it still would not make sense to do it here because it would load data twice, and with SSR you generally want to avoid this kind of situations, you want to load data once and reuse data, that was fetched on the server, on the client.
With Next.js the flow is quite simple:
On the server you load all the data that is needed, in your case it's loaded on the App level, but maybe in the future you might want to have loader for each page to load data more granularly. Overall it does not change the flow though
Once the data is loaded (through getInitialProps method or any other Next.js data fetching methods), you hydrate your stores and render the application on the server side and send html to the client, that's SSR
On the client the app is initialized again, though this time you don't want to load the data, but use the data which server already fetched and used. This data is provided through props to your page component (or in this case App component). So you grab the data and just hydrate the store (in this case it's done with getDerivedStateFromProps).
Based on that, everything you want to fetch should happen inside getInitialProps. And you already have fetchInitialStoreState method for that, so all you need to do is remove data fetching from store constructor and move it to fetchInitialStoreState and only return the data from it. This data will then go to the hydrate method of your store.
I've made a quick reproduction of your code here:
The huge downside if App.getInitialProps is that it runs on every page navigation, which is probably not what you want to do. I've added console.log("api call") and you can see in the console that it is logged every time you navigate to any other page, so the api will be called every time too, but you already have the data so it's kinda useless. So I recommend in the future to use more granular way of loading data, for example with Next.js getServerSideProps function instead (docs).
But the general flow won't change much anyway!
Calling awardStore.loadCompanies in the constructor of MyApp is problematic because the loadCompanies method is populating the store class. What you want is to hydrate the store with the companyCats data. Since server and client stores are distinct, you want to load the data you need on the server side i.e. fetchInitialStoreState (or load it from a page's getStaticProps/getServerSideProps method) so that you can pass it into the hydrate store method from page/app props.
Note loadCompanies is async so it'll be [] when getDerivedStateFromProps is called so there's nothing to hydrate. For your existing hydrate method to work you need initialStoreState to be something like the fetchInitialStoreState method below. Alternatively if it's fetched on the page level, the hydrate may be closer to initialData?.pageProps?.companyCats
It's common to see the store hydration as needed for each page though it's still valid to call loadCompanies() from the client side. There's a lot I didn't get a chance to touch on but hopefully this was somewhat helpful.
export const fetchInitialStoreState = async() => {
let companyCats = [];
try {
const response = await axios.get < CompanyDto[] > ('MyAPICall')
response.data.map((company: CompanyDto) => {
if (Array.isArray(company.categories) && company.categories.length > 0) {
companyCats.push(...company.categories)
}
})
} catch (error) {
// Uh oh...
}
return {
serializedStore: {
companyCats,
// PageTitle/etc
}
}
}

setState is not updating in my other method

I'm very new to react and i'm confused why my state is not updated in another method of mine see example below.
fetchMovies = () => {
const self = this;
axios.get("https://api.themoviedb.org/3/trending/movie/day?api_key=XXXXXXX")
.then(function(response){
console.log(response.data)
self.setState({
collection: response.data.results
})
console.log(self.state.collection)
});
}
makeRow = () => {
console.log(this.state.collection.length);
if(this.state.collection.length !== 0) {
var movieRows = [];
this.state.collection.forEach(function (i) {
movieRows.push(<p>{i.id}</p>);
});
this.setState({
movieRow: movieRows
})
}
}
componentDidMount() {
this.fetchMovies();
this.makeRow();
}
When inside of fetchMovies function i can access collection and it has all the data but this is the part i can't understand in the makeRow function when i console log the state i would of expected the updated state to show here but it doesn't i'm even executing the functions in sequence.
Thanks in advance.
the collection is set after the async call is resolved. Even though makeRow method is called after fetchMoview, coz of async call, u will never know when the call will be resolved and collection state will be set.
There is no need to keep movieRows in the state as that is just needed for rendering. Keeping html mockup in the state is never a good idea.
So u should just call fetchMoviews in the componentDidMount and render the data in as follows:
render() {
const { collection } = this.state;
return (
<>
{
collection.map(c => <p>{c.id}</p>)
}
</>
)
}
make sure the initial value for collection in the state is [] .
The setState() documentation contains the following paragraph:
Think of setState() as a request rather than an immediate command
to update the component. For better perceived performance, React may
delay it, and then update several components in a single pass. React
does not guarantee that the state changes are applied immediately.
To access the modified state you need to use the function signature setState(updater, [callback]), so in your case it should be;
self.setState({
collection: response.data.results
}, () => { // Will be executed after state update
console.log(self.state.collection)
// Call your make row function here and remove it from componentDidMount if that is all it does.
self.makeRow()
} )

Accessing data returned from promise in Redux

I'm trying to access the data of the first dataElement in the array. How can I reach it? I want to console.log it's name.
import React, { Component } from 'react';
class Submit extends Component {
componentDidMount() {
const programStage = this.props.getProgramStage();
if (programStage !== null) {
console.log('Stage loaded...');
}
console.log(this.props.getForm());
}
render() {
return <div />;
}
}
export default Submit;
How the console looks like
As shown in the pic, the promise is resolved. Hence you should be able to access the data like :
this.props.getForm().then((data) => console.log(data[0].name))
It seems that the return type of the call getForm() is a Promise (according to the output). You would need to append a handler via the then method of the promise to actually get the value you are looking for.
E.g.
componentDidMount() {
...
this.props.getForm().then(result => console.log(result))
}

undefined within the react component - but not in action

Have a call to action (backend call) response is some array - getting undefined here - in few other cases where I am mapping over the array I am seeing map of undefined error as well. Is componentDidMount right place to put such calls?
I get the response back when I console log in action creator before dispatching.
componentDidMount() {
this.props.fetchData(test, foo);
console.log(this.props.responseData); //getting here undefined
}
function mapStateToProps(state) {
return {
responseData: state.abc.responseData,
};
}
Mycomp.propTypes = {
responseData: React.PropTypes.array,
fetchData: React.PropTypes.func,
};
export default connect(mapStateToProps, actions)(Mycomp);
It is undefined because the request has not been made at the time you're trying to log the response – it's async.
You can either call the action in here:
componentDidMount() {
this.props.fetchData(test, foo);
}
componentWillReceiveProps(props) {
if (props.responseData) { console.log(props.responseData); } // should be getting the data if the request works
}
or define a callback that is executed after the request was successful.
I think your fetchData() is returning Promise which is async
So you can get data from componentWillReceiveProps() which is called when ever you have new props.
componentWillReceiveProps(nextProps){
console.log(nextProps.responseData); //do whatever you want with response
}
Since componentDidMount() is called at only once. It's not reliable place to do something with your props.

Resources