After setState render is not calling - javascript

I have a large set of data that I'm retrieving from an API.
For this I have added a promise. As API taking too much time to fetch the data I am trying to display the loader. Here is my sample
import React from 'react';
import {Button} from 'react-bootstrap';
import ApplicationService from '../services/applicationServices';
import renderIf from 'render-if';
import OverlayLoader from '../../common/components/overlayLoader';
class SampleComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoader : false
};
};
onTapNext = () => {
this.setState({isLoader: true});
ApplicationService.getPersonalDetails(AppService.personaDetails).then(function(resp) {
// Code for fetching data and do some sorting
// hide loader once get the result
this.setState({isLoader: false});
}, function(error) {
this.setState({isLoader: false});
});
}
render() {
var _component = "";
if(this.state.isLoader){
_component = <OverlayLoader></OverlayLoader>;
} else {
_component = <Button className="footerButtons" onClick={this.onTapNext}>FETCH DATA</Button>;
}
return (
<div> {_component}</div>
)
}
}
export default SampleComponent;
But when the promise get called I am not able to see the Loader. I am using OverlayLoader component for showing the loader.
Can anyone let me know what I am missing here.
Thanks

I think state is not changing, Try this code for onTapNext method
onTapNext = () => {
this.setState({isLoader: true});
const self = this;
ApplicationService.getPersonalDetails(AppService.personaDetails).then(function(resp) {
// Code for fetching data and do some sorting
// hide loader once get the result
self.setState({isLoader: false});
}, function(error) {
self.setState({isLoader: false});
});
}

you set the state first and then you call the api., in your code both are executing concurrently so that loader not populating, first set the loader state and in callback of setState call the api. you could refer bellow code
onTapNext = () => {
this.setState({isLoader: true}, this.onCallback());
}
onCallback = () => {
const self = this;
ApplicationService.getPersonalDetails(AppService.personaDetails).then(function(resp) {
// Code for fetching data and do some sorting
// hide loader once get the result
self.setState({isLoader: false});
}, function(error) {
self.setState({isLoader: false});
});
}

Try something similar to this, the loader is displayed directly and only when your promise is resolved in the last then will your loader disappear
this.state = {
isLoader : true
};
onTapNext = () => {
this.setState({isLoader: true});
ApplicationService.getPersonalDetails(AppService.personaDetails).then( resp => {
// Code for fetching data and do some sorting
// hide loader once get the result
}).then(
this.setState({isLoader: false});
);
}
Also API call should probably be made in the componentDidMount() function (a call of mine) unless you really need it onClick
componentDidMount() {
this.setState({
loading: true
})
fetch('/api/data')
.then(res => {
return res.json()
})
.then(datas =>
this.setState(
{datas, loading: false}
)
).catch(err => this.setState({
APIerror: true,
APIerrorMessage: err
}))
}

Related

React's useTransition() stucked in isPending = true when calling api routes from /pages/api folder

I am having issue with useTransition() that it is being set to true but actually never changes back to false.
I am trying to delete record from MongoDB and once it is finished I would like to refresh React Server Component as explained here:
https://beta.nextjs.org/docs/data-fetching/mutating
Issue is that in this case server component won't get refreshed and Button is stucked with loading text.
'use client'
const DeleteButton = ({ details }) => {
const [isPending, startTransition] = useTransition();
const router = useRouter();
const handleDelete = async () => {
await fetch('/api/clients', { method: 'DELETE', body: details._id });
startTransition(() => {
console.log('tran started', isPending);
router.refresh();
});
}
useEffect(() => {
// at load isPending = false
// after start tranisition it is set to false
// but it never returns back to false
console.log('is pending ? ', isPending);
}, [isPending]);
return <Button onClick={() => handleDelete()}>{ isPending ? 'Loading' : 'Delete' }</Button>
}
This is BE code at /api/clients
import type { NextApiRequest, NextApiResponse } from 'next';
import ClientsCollection from '../../db/collections/clients';
import Client from '../../helpers/interfaces/client';
type Data = {
name: string;
};
const clientsCollection = new ClientsCollection();
export default function handler(req: NextApiRequest, res: NextApiResponse<any>) {
switch (req.method) {
case 'DELETE': {
const result = clientsCollection.deleteClientById(req.body);
res.status(200).json(result);
}
default:
res.status(403);
}
}
Closing this.
I misunderstood why error did happen, I was basically having useEffect to react on changes from Redux which for some reason blocked setting isPending back to to false.
I did debug it the way that I've commented out current table that was displaying all records and created simple html table without any functionality which helped me identify which code was wrong.

Canceling request of nuxt fetch hook

Since Nuxt's fetch hooks cannot run in parallel, I needed a way to cancel requests done in fetch hook when navigating to some other route so users don't have to wait for the first fetch to complete when landed on the homepage navigated to some other. So I found this approach: How to cancel all Axios requests on route change
So I've created these plugin files for Next:
router.js
export default ({ app, store }) => {
// Every time the route changes (fired on initialization too)
app.router.beforeEach((to, from, next) => {
store.dispatch('cancel/cancel_pending_requests')
next()
})
}
axios.js
export default function ({ $axios, redirect, store }) {
$axios.onRequest((config) => {
const source = $axios.CancelToken.source()
config.cancelToken = source.token
store.commit('cancel/ADD_CANCEL_TOKEN', source)
return config
}, function (error) {
return Promise.reject(error)
})
}
and a small vuex store for the cancel tokens:
export const state = () => ({
cancelTokens: []
})
export const mutations = {
ADD_CANCEL_TOKEN (state, token) {
state.cancelTokens.push(token)
},
CLEAR_CANCEL_TOKENS (state) {
state.cancelTokens = []
}
}
export const actions = {
cancel_pending_requests ({ state, commit }) {
state.cancelTokens.forEach((request, i) => {
if (request.cancel) {
request.cancel('Request canceled')
}
})
commit('CLEAR_CANCEL_TOKENS')
}
}
Now this approach works fine and I can see requests get canceled with 499 on route change, however, it is flooding my devtools console with "Error in fetch()" error. Is there some preferred/better way to do this?
Example of fetch hook here:
async fetch () {
await this.$store.dispatch('runs/getRunsOverview')
}
Example of dispatched action:
export const actions = {
async getRunsOverview ({ commit }) {
const data = await this.$axios.$get('api/frontend/runs')
commit('SET_RUNS', data)
}
}
Edit: I forgot to mention that I'm using fetch here with fetchOnServer set to False to display some loading placeholder to users.
The main problem is the flooded console with error, but I can also see that it also enters the $fetchState.error branch in my template, which displays div with "Something went wrong" text before route switches.
Edit 2:
Looked closer where this error comes from and it's mixin file fetch.client.js in .nuxt/mixins directory. Pasting the fetch function code below:
async function $_fetch() {
this.$nuxt.nbFetching++
this.$fetchState.pending = true
this.$fetchState.error = null
this._hydrated = false
let error = null
const startTime = Date.now()
try {
await this.$options.fetch.call(this)
} catch (err) {
if (process.dev) {
console.error('Error in fetch():', err)
}
error = normalizeError(err)
}
const delayLeft = this._fetchDelay - (Date.now() - startTime)
if (delayLeft > 0) {
await new Promise(resolve => setTimeout(resolve, delayLeft))
}
this.$fetchState.error = error
this.$fetchState.pending = false
this.$fetchState.timestamp = Date.now()
this.$nextTick(() => this.$nuxt.nbFetching--)
}
Have also tried to have everything using async/await as #kissu suggested in comments but with no luck :/

React setState after Promise resolves in componentDidMount

I am still new to React and trying to wrap my head around.
I am fetching some data from an API in ProjectAPI.js file.
const getProjects = async () => {
const projectsAPI = Common.baseApiUrl + '/project';
let projects = [];
axios.get(projectsAPI)
.then((response) => {
for (let i = 0; i < response.data.length; i ++){
let project = {
projectNameInitials: "NO",
projectNumber: response.data[i].projectNumber,
projectName: response.data[i].projectName,
clientName: response.data[i].client,
currentStage: response.data[i].currentStage,
lastUpdated: response.data[i].updatedOn
}
projects.push(project);
}
})
.catch((error) => {
console.log(error);
});
return projects;
}
Then, in my React component I call this function and sets the state after the Promise resolves using then.
componentDidMount(){
ProjectAPI.getProjects().then((response) => this.setState({projects: response}));
}
I try to retrieve the same from the state in my render() function.
render(){
const {
projects,
} = this.state;
//...
}
This does not work and I get projects as empty array inside render() function. However, using the React dev tools, I can see the state is having the exact data. Interestingly, when I modify one of the state value manually using React dev tools, the render() is able to retrieve the state data, since it triggers the render() again. Any idea what I am doing wronng here?
Cause getProjects return empty array. Try this
const getProjects = () => {
const projectsAPI = Common.baseApiUrl + '/project';
let projects = [];
return axios.get(projectsAPI)
.then((response) => {
for (let i = 0; i < response.data.length; i ++){
let project = {
projectNameInitials: "NO",
projectNumber: response.data[i].projectNumber,
projectName: response.data[i].projectName,
clientName: response.data[i].client,
currentStage: response.data[i].currentStage,
lastUpdated: response.data[i].updatedOn
}
projects.push(project);
}
return projects
})
.catch((error) => {
console.log(error);
});
}
You just need to wait for data to come, because in this case render method getting called before your data comes from api.
So Just put condition before you render if state is null then render null else your html
render(){
const {
projects,
} = this.state;
{projects && 'your code' }
}

Access sever loaded config from react

I've had a good look around but I can't quite construct a search to get any relevant results.
What is the best way to load in a config object that is generated from the server when it isn't possible to call an API?
The config is printed to the page like so:
var config = {configItem: 'configAnswer', ...etc }
Then in react I'm literally reaching in and accessing it as normal
const item = config.configItem;
This may be fine it just doesn't seem very 'react like'.
Any suggestions would be appreciated.
EDIT:
Example of component code. As you can see product.handle is coming from a server generated global variable.
const Cart = class extends React.Component {
constructor() {
super();
this.state = { cartLoaded: false, cart: {} };
}
getCart = async () => {
const cart = await fetch(`/products/${product.handle}.js`);
const json = await cart.json();
this.setState({ cartLoaded: true, cart: json });
};
componentDidMount() {
if (this.state.cartLoaded === false) {
this.getCart();
}
}
render() {
if (this.state.cartLoaded === false) {
return null;
}
return <div className="purchase-container"></div>;
}
};

flickering doing setState using nested api calls in react

I want to load a bunch of data that involves multiple APIs, I did setState in foreach, it worked but I think the design is wrong, as I see flickering on my screen.
API.fetchMain().then(main => {
main.forEach(o => {
const main_id = o.main_id
this.setState({
main: o
})
API.fetchSub(main_id)
.then(sub => {
this.setState({
sub
})
API.fetchOthers(main_id, sub.id)
.then(others => {
this.setState({
others
})
})
})
})
}
I think I should use promises to refactor, I tried but I think my design was wrong.
API.fetchMain().then(main => {
let promise = []
main.forEach(o => {
const main_id = o.main_id
this.setState({
main: o
})
promise.push(
API.fetchSub(main_id)
.then(sub => {
return API.fetchOthers(main_id, sub.id)
})
)
})
Promise.all(promise).then(resp => console.log('do setState here'))
}
Need help.
It looks to me that you are fetching a resource that provides you with information about how to make further requests. If you are open to using a fetch library I would recommend axios. Heres how I would envision it looking
import axios from 'axios'
fetch(){
// Make the initial request
var options = { url: "URL of main resource", method: "GET" }
axios(options).then(res => {
// Create an array of next requests from the response
var next_requests = res.data.main.map(id => axios.get(`${resource_url}/${id}`))
//Make the requests in parallel
axios.all(next_requests).then(axios.spread(() => {
//since we don't know how many request we can iterate
//over the arguments object and build the new state
var newState = {}
arguments.forEach(i => {
// how you want to structure the the state before setting it
})
this.setState(newState)
})
}).catch(err => //error handling logic here)
}
From my understanding of your question you could also (since you are using react) break your fetch request into components that get called when they mount. A quick example:
const class MainComp extends Component {
constructor(props){
super(props)
this.state = {
main: []
}
}
componentDidMount(){ this.fetchMain() }
fetchMain() {
axios.get('url').then(res =>
this.setState({main_id: res.data.main.id})
}
sendSubFetchToParent(dataFromChild){
// Do what you need to with the data from the SubComp child
}
render(){
return (
{this.state.main.map(id => <SubComp id={id) afterFetch={this.sendSubFetchToParent}/>}
)
}
}
const class SubComp extends Component {
componentDidMount(){ this.fetchSub() }
fetchSub() {
//Pass the results to the parent after the fetch completes.
// You can add the usual error handling here as well.
axios.get('url').then(res => this.props.afterFetch(res.data))
}
render(){
//Return null or render another sub component for further nested requests
return null
}
}
In the above, MainComp initiates a request. When it gets a response (which in your example is an array) we set that response to the state. This triggers rerender which will mount n number of SubComp. When those mount they will initiate their requests to get data. For SubComp we pass a callback from the parent so SubComp can send its fetch response back to MainComp (And handle it appropriately by setting state etc etc). You can return null in SubComp or have it mount a component that will make further request.
In that way your fetch requests are now componentized.

Categories

Resources