React.JS State Update with Socket.io session data - reactjs

I'm trying to update the state of a react app with data from a websocket and it all works but the issue is this:
In the view where I monitor the users currently logged in, the state is only reflecting the second to last update and not the last. If I refresh the page the data (I'm passing a current_url) gets updated but if I navigate to the page from another page, the previous url is shown instead of the current. I'm assuming that the state array is being updated only after the component renders and showing an old state.
I've tried using the setState callback to forceUpdate() but this doesn't seem to work. Any help would be very much appreciated. Thanks.
class UsersLoggedIn extends Component {
constructor(props) {
super(props);
this.state = {
usersLoggedIn: []
}
}
getUsersLogged() {
const socketIo = SocketIO();
if (typeof socketIo === 'object') {
socketIo.emit('getUsersLogged', (data) => {
const usersLoggedIn = [];
// groups the data from the socket by session id.
for (let id in data) {
let temp = [];
for (let socketId in data[id]) {
temp.push(data[id][socketId]);
}
usersLoggedIn.push({
...temp[0],
children: temp
});
}
this.setState({usersLoggedIn}, () => {
this.forceUpdate();
});
});
}
}
componentDidMount() {
// we need to pass data to the socket here.
const socketIo = SocketIO();
if (typeof socketIo === 'object') {
socketIo.on('loadUserLogged', (data) => {
this.getUsersLogged();
});
}
}

Related

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
}
}
}

How to get updated States at first time?

I am using react states to store data that I am getting through axios from API. And I am changing some of the key values in my react states. those states are getting updated but not getting rendered those updated states.
constructor(props) {
super(props);
this.state = {
NewOrders:[],
}
this.getNewOrders();
}
getNewOrders = async () => {
let data = await api.get(`SellerOrdersAPI/${store.getState().auth.user.id}/`).then(({data})=>data);
data.map(a =>
this.get(a)
)
console.log(this.state.NewOrders)
this.setState({NewOrders:data})
this.testt()
}
get = async(a) => {
let data1 = await itemApi.get(`/${a.item}/`).then(({data})=>data);
a.item = data1.title
}
here in console.log item value got updated but I can not render this newly updated value.
If I perform some operation on this component then this value (22) got changed by new value. but on start I can not access that.
Call getNewOrders in componentDidMount instead of constructor
componentDidMount(){
this.getNewOrders();
}

Uncaught (in promise) TypeError: this.setState is not a function

In my parent component, I'm loading data from a json file and using it to set state. Using react-router, I then pass the state down to a child component which renders the data in a route. This behavior works as expected. However, when I attempt to refresh the route, the parent component's state (and the child component's props) are lost, and the page throws an undefined error.
I've attempted to call the data loading functions from the parent component in the ComponentDidMount lifecycle method of the child component, but the app throws the error Uncaught (in promise) TypeError: this.setState is not a function.
The parent component code:
async loadWorldData() {
await axios.get("../factbook.json")
.then(res => {
let Data = res && res.data.countries;
Data = Object.values(Data).map(country => country.data) || [];
let newData = this.removeNull(Object.values(Data));
this.setState({ worldData: newData})
});
}
The child component code:
componentWillMount = () => {
this.props.loadWorldData();
}
I don't know another way to reload the app state without calling the parent function in the lifecycle methods of the child.
Try this: This is probably a binding issue.
loadWorldData = async() => {
const res = await axios.get("../factbook.json")
let data = res && res.data.countries;
data = Object.values(Data).map(country => country.data) || [];
let newData = this.removeNull(Object.values(Data));
this.setState({ worldData: newData });
}
Why is this not working?
In order to use setState, you must have access to this. Normal functions are not bound to this. There are two options to fix this issue:
First: Bind the function to this inside the constructor.
class App extends React.Component {
constructor(props) {
super(props);
this.loadWorldData = this.loadWorldData.bind(this);
}
}
Second: Fat Arrow functions:
Since Fat arrow functions are bound to this, you don't have to worry about binding them inside the constructor as denoted by the first example.
async loadWorldData() {
const self = this;
await axios.get("../factbook.json")
.then(res => {
let Data = res && res.data.countries;
Data = Object.values(Data).map(country => country.data) ||
[];
let newData = this.removeNull(Object.values(Data));
self.setState({ worldData: newData})
});
}
you can delete async await you don't need them .

Reset the state before render in reactjs

I am using react with Meteor and my data is coming from createContainer
export default createContainer((props) => {
const { search } = props.dataTables.favorites;
let data = [];
data = Meteor.user().favorites;
return { data };
}, StarredListView);
Now I wanted to do some processing on the data after state intialization
constructor(props) {
super(props);
this.state = {
data: props.data
};
this.resetOldData = this.resetOldData.bind(this);
}
Now how can I make sure that before render the data my resetOldData called and reset the state. Where can I call this this function to reset my state.
resetOldData() {
Meteor.call('reset.old.favorite.data', (err, res) => {
if (err) { this.props.renderAlert(err.reason, null, true); }
if (res) {
this.props.renderAlert(res.message);
this.setState({ data: res });
}
});
}
You need to take a look at React lifecycle documentation: https://facebook.github.io/react/docs/react-component.html
There are very specific places where you will want to respond to data coming in and how that should effect the state of your component.
Certain changes to the state only belong in very specific places in the lifecycle of a component.

Ajax request won't display in react render function

I don't know why the result of my axios promise doesn't show up in the render function. I'm using the create-react-app tools by the way.
_getPrice() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
return prices;
})
}
render() {
return(<div><h3> {this._getPrice()} </h3></div>);
}
React only re-renders components when either the state or props of the component change. If data changes during the render cycle, but doesn't interact with those variables, then the changes will not show up.
You can save the result of your promise to state as follows:
getInitialState() {
return {prices: undefined}
}
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then(function (response) {
//console.log(response.data.data.amount);
let prices = response.data.data.amount;
this.setState({prices: prices});
}.bind(this))
}
render() {
return(<div><h3> {this.state.prices} </h3></div>);
}
first you cant call a function in return in render function and if you want update your view you must update state or props...
When requesting data to the server, the request is async, this means it will take time for the server to respond and the browser will continue the execution, than been said, in your current implementation you are returning a promise in your _getPrice function and then when the server responds you are not doing anything with the data.
The second problem is that react will only re-render the component when there are changes on the state or on the props, and in your current implementation you are not changing any of that.
Here's a sample of how you need to do it in order to make it work.
class YourComponent extends Component {
state = {
prices: 0,
};
componentDidMount() {
const url = 'https://api.coinbase.com/v2/prices/BTC-USD/spot';
axios.get(url)
.then((response) => {
let prices = response.data.data.amount;
this.setState({ prices });
});
}
render() {
const { prices } = this.state;
return(
<div>
<h3> {prices} </h3>
</div>
);
}
}
Good luck!

Resources