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>;
}
};
Related
I was trying to load CoinbaseWalletSDK in my NextJS application, but it always throw an error of ReferenceError: localStorage is not defined due to it was imported before the window is loaded. I tried dynamic loading but it doesn't work. The following is what I am using at this moment.
export async function getServerSideProps({
params,
}: {
params: { project_id: string };
}) {
const project_id = params.project_id;
let project: any = fakeProjects[0];
if (project_id && typeof project_id === 'string' && !isNaN(parseInt(project_id))) {
const id = project_id;
project = fakeProjects.find(p => p.id === parseInt(id));
// Fetch project detail here
let item = await (
getNFTStatsByProjectId(
parseInt(project_id)
)
);
if (project && item && item['nftTotal'] && item['nftSold']) {
if (item.nftSold > item.nftTotal) {
item.nftSold = item.nftTotal;
}
project.nftTotal = item.nftTotal;
project.nftSold = item.nftSold;
}
}
const { coinbaseEth } = (await import('../../components/services/coinbase'));
return {
props: {
project: project,
coinbaseEth: coinbaseEth
},
};
}
And this is what I have in the coinbase service:
// TypeScript
import CoinbaseWalletSDK from '#coinbase/wallet-sdk'
import Web3 from 'web3'
const APP_NAME = 'Practice App'
const APP_LOGO_URL = process.env.WEBSITE_URL + '/logo.png'
const DEFAULT_ETH_JSONRPC_URL = 'https://mainnet.infura.io/v3/' + process.env.INFURA_PROJECT_ID
const DEFAULT_CHAIN_ID = 1
// Initialize Coinbase Wallet SDK
export const coinbaseWallet = new CoinbaseWalletSDK({
appName: APP_NAME,
appLogoUrl: APP_LOGO_URL,
darkMode: false
})
// Initialize a Web3 Provider object
export const coinbaseEth = coinbaseWallet.makeWeb3Provider(DEFAULT_ETH_JSONRPC_URL, DEFAULT_CHAIN_ID)
// Initialize a Web3 object
export const web3 = new Web3(coinbaseEth as any)
The new CoinbaseWalletSDK is where the error was thrown if that's a concern.
Based on my research, I will need to get it imported after the page is fully loaded (which is the point when "window" become available, as well as "localStorage"), which I have no clue how to achieve. Can anyone help me out on this?
I solved it by loading it later. What I did was to assign this variable with this function.
setTimeout(async () => {
coinbaseEth = (await import('../../components/services/coinbase')).coinbaseEth;
}, 1000)
I choose not to use useEffect because the value will be lost on render, which prevents the function to work properly.
I had a Nuxt.js application working with the options API. And with the new Nuxt3 coming out, I was trying to migrate things over to the supposedly 'better' alternative. So far i've had nothing but challenges, perhaps that's my lack of knowledge.
I'm building a basic E-Commerce platform with a component of
# products/_id.vue
<template>
<div>
{{ product }}
</div>
</template>
<script>
import {
defineComponent,
useFetch,
useStore,
useRoute,
ssrRef, reactive, watch
} from '#nuxtjs/composition-api'
export default defineComponent({
setup () {
const store = useStore()
const route = useRoute()
const loading = ref(false)
// LOAD PRODUCT FROM VUEX STORE IF ALREADY LOADED
const product = reactive(store.getters['products/loaded'](route.value.params.id))
// GET PAGE CONTENT
const { fetch } = useFetch(async () => {
loading.value = true
await store.dispatch('products/getOne', route.value.params.id)
loading.value = false
})
// WATCH, if a use navigates to another product, we need to watch for changes to reload
watch(route, () => {
if (route.value.params.id) {
fetch()
}
})
return {
loading
product
}
}
})
</script>
One thing I need to note, is, if the product gets a comment/rating, I want the UI to update with the products star rating, thus needing more reactivity.
I continue to get an undefined product var
Inside my VueX store I have my getters
loaded: state => (id) => {
try {
if (id) {
return state.loaded[id]
}
return state.loaded
} catch {
return {}
}
}
Looking for directions on how to get this to work, improve any of the code i've currently setup.
If you want to maintain reactive referece to your getter, then you have to create a computed property.
So, what you return from your setup function is
product: computed(() => getters['products/loaded'](route.value.params.id))
This will make sure that whenever the getter updates, your component will receive that update.
Also, if the product already exists, you should bail out of the fetch function. So that you do not make the extra API call.
And, finally, if there is an error, you could redirect to a 404 error page.
All in all, your setup function could look something like this
setup() {
const route = useRoute();
const { error } = useContext();
const { getters, dispatch } = useStore();
const loading = ref(false);
const alreadyExistingProduct = getters['products/loaded'](route.value.params.id);
const { fetch } = useFetch(async () => {
// NEW: bail if we already have the product
if (alreadyExistingProduct) return;
try {
loading.value = true;
await dispatch('products/getOne', route.value.params.id);
} catch {
// NEW: redirect to error page if product could not be loaded
error({ statusCode: 404 });
} finally {
loading.value = false;
}
});
watch(route, () => {
if (route.value.params.id) {
fetch();
}
});
return {
loading,
// NEW: computed property to maintain reactive reference to getter
product: computed(() => getters['products/loaded'](route.value.params.id)),
};
},
You will probably also run into this harmless issue FYI.
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' }
}
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
}))
}
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.