How to check for subscription.ready() in a react component? - javascript

This is a very basic example how I get the data from the mongoDB to my meteor/react application.
Now I would like to show a loading icon while the data is getting loaded. Therefore I need to use something like subscription.ready() but where should I put this?
import { Meteor } from 'meteor/meteor'
import { createContainer } from 'meteor/react-meteor-data'
import Example from '../components/example.jsx'
import ExampleCollection from '/imports/api/collection.js'
export default createContainer((props) => {
const id = props.params.id,
subscription = Meteor.subscribe('anything', id)
data = ExampleCollection.find({ parent: id }).fetch()
return { data: data }
}, Example)

i do it like this:
render() {
if (this.props.isLoading) {
return null; // or show loading icon
}
return (
// stuff
);
}
export default createContainer((props) => {
const id = props.params.id;
let subscription = Meteor.subscribe('anything', id);
let data = ExampleCollection.find({ parent: id }).fetch();
let isLoading = !subscription.ready();
return {data, isLoading};
}, Example);

For those who might still be looking for this type of functionality, I think this might be a little bit better way to do this. Also note that currently createContainer is deprecated and changed to withTracker.
export default withTracker((props) => {
let isLoading = true;
let data = [];
const id = props.params.id;
const subscription = Meteor.subscribe('anything', id);
if (subscription.ready()) {
isLoading = false;
data = ExampleCollection.find({ parent: id }).fetch();
}
return {data, isLoading};
}, Example);
This is Zim's answer refactored a little.

Related

Failing to fetch dynamic data from firestore using getStaticPaths in nextjs

When I fetch data from firebase firestore using getStaticProps, it works perfectly but when I try implementing the logic of getting the details of each single item using getStaticPaths, I fail and get a 404 page. This is how my [id].js code looks like currently.
import React from 'react'
import { db } from '#/Firebase';
import {collection, getDoc} from "firebase/firestore";
const reference = collection(db, "abantu");
export const getStaticPaths= async () => {
const umuntu = await getDoc(reference);
const paths = umuntu.docs.map(doc => {
return {
params: { id: doc.id }
}
})
return {
paths,
fallback: false
}
}
export const getStaticProps = async (context) => {
const id = context.params.id;
const data = await getDoc(reference) + id;
return {
props: {
umuntu: data
}
}
}
function Details({umuntu}) {
return (
<div>
<h1>{umuntu.ibizo}</h1>
</div>
)
}
export default Details
I dont quite get where my logic is going wrong but where could I be going wrong?.
For finding the right page props for each of the paths that you generate from the database in the getStaticPaths function, you should be able to find each of the pages information based on the id field you are getting from each path, see it here:
export const getStaticProps = async (context) => {
const id = context.params.id;
const umuntu = await getDoc(reference);
const data = umuntu.docs.find((pageData) => pageData.id === id); // this will find the right page based on the id passed via page path
return {
props: {
data
},
};
};
function Details({ data }) {
return (
<div>
<h1>{data.ibizo}</h1>
</div>
);
}
export default Details;

useEffect runs for the old data

I am trying to write a singleton class that will act as a local storage wrapper. I need this because wherever I use localstorage in my whole app, I need each of the items I set to have prefix. This prefix for sure changes, but at only one place, so wrapper seems a good idea so that in my app, I don't have to pass prefix each time I use localStorage.
Here is my wrapper.
let instance;
class LocalStorage {
constructor() {
if(instance){
return instance;
}
instance = this;
instance.cachePrefix = null
}
_getKey(key, usePrefix) {
return usePrefix ? `${this.cachePrefix}:${key}` : key;
}
setPrefix(prefix) {
this.cachePrefix = prefix
}
set(key, value, usePrefix = true) {
if(key == null) {
return
}
localStorage.setItem(this._getKey(key, usePrefix),value)
}
get(key, usePrefix = true) {
return localStorage.getItem(this._getKey(key, usePrefix));
}
}
export const LocalStorageWrapper = new LocalStorage()
Now, where I import this class and call setPrefix, this piece is located in the very parent component tree, so we can say that this setPrefix will be called the first time.
Problem: Even though I call this setPrefix in the very parent, in that very parent, I have async call and when its result gets resolved, that's when I call setPrefix. Even though this is very fast, I am still not sure that this will work all the time.. It's possible that before this async call finishes, child component might start to render and it will try to use wrapper that won't have prefix set up...
I can't use hooks, because the whole react app is written with classes.
I'd appreciate your inputs what can be done here.
UPDATE THIS IS VERY PARENT PROVIDER IN THE TREE.
import React, { useContext, useEffect, useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import BN from 'bn.js'
import { useWallet as useWalletBase, UseWalletProvider } from 'use-wallet'
import { getWeb3, filterBalanceValue } from './web3-utils'
import { useWalletConnectors } from './ethereum-providers/connectors'
import { LocalStorageWrapper } from './local-storage-wrapper'
const NETWORK_TYPE_DEFAULT = 'main'
const WalletContext = React.createContext()
function WalletContextProvider({ children }) {
const {
account,
balance,
ethereum,
connector,
status,
chainId,
providerInfo,
type,
...walletBaseRest
} = useWalletBase()
console.log("========= ", type);
const [walletWeb3, setWalletWeb3] = useState(null)
const [networkType, setNetworkType] = useState(NETWORK_TYPE_DEFAULT)
const connected = useMemo(() => status === 'connected', [status])
// get web3 and networkType whenever chainId changes
useEffect(() => {
let cancel = false
if (!ethereum) {
LocalStorageWrapper.setPrefix(NETWORK_TYPE_DEFAULT)
return
}
const walletWeb3 = getWeb3(ethereum)
setWalletWeb3(walletWeb3)
walletWeb3.eth.net
.getNetworkType()
.then(networkType => {
if (!cancel) {
setNetworkType(networkType)
LocalStorageWrapper.setPrefix(networkType)
}
return null
})
.catch(() => {
setNetworkType(NETWORK_TYPE_DEFAULT)
LocalStorageWrapper.setPrefix(NETWORK_TYPE_DEFAULT)
})
return () => {
cancel = true
setWalletWeb3(null)
setNetworkType(NETWORK_TYPE_DEFAULT)
LocalStorageWrapper.setPrefix(NETWORK_TYPE_DEFAULT)
}
}, [ethereum, chainId])
const wallet = useMemo(
() => ({
account,
balance: new BN(filterBalanceValue(balance)),
ethereum,
networkType: connected ? networkType : 'main',
providerInfo: providerInfo,
web3: walletWeb3,
status,
chainId,
connected,
...walletBaseRest,
}),
[
account,
balance,
ethereum,
networkType,
providerInfo,
status,
chainId,
walletBaseRest,
walletWeb3,
connected,
]
)
return (
<WalletContext.Provider value={wallet}>{children}</WalletContext.Provider>
)
}
WalletContextProvider.propTypes = { children: PropTypes.node }
export function WalletProvider({ children }) {
return (
<UseWalletProvider connectors={useWalletConnectors}>
<WalletContextProvider>{children}</WalletContextProvider>
</UseWalletProvider>
)
}
WalletProvider.propTypes = { children: PropTypes.node }
export function useWallet() {
return useContext(WalletContext)
}

MobX State Tree async actions and re-rendering React component

I am new to MST and is having a hard time finding more examples with async actions. I have an api that will return different data depending on the params you pass to it. In this case, the api can either return an array of photos or tutorials. I have set up my initial values for the store like so:
data: {
photos: [],
tutorials: []
}
Currently, I am using applySnapshot to update the store and eventually, that will trigger a re-render of my React component. In order to display both photos and tutorials, I need to call the api twice (Once with the params for photos and the second time for tutorials). I am running into an issue where the snapshot from the first update shows that photos and tutorials have the same values and only on the second update, do I get the correct values. I am probably misusing applySnapshot to re-render my React components. I would like to know the better/proper way of doing this. What is the best way to re-render my React components after the api has yielded a repsonse. Any suggestions are much appreciated
I have set up my store like this:
import { RootModel } from '.';
import { onSnapshot, getSnapshot, applySnapshot } from 'mobx-state-tree';
export const setupRootStore = () => {
const rootTree = RootModel.create({
data: {
photos: [],
tutorials: []
}
});
// on snapshot listener
onSnapshot(rootTree, snapshot => console.log('snapshot: ', snapshot));
return { rootTree };
};
I have created the following model with an async action using generators:
import {types,Instance,applySnapshot,flow,onSnapshot} from 'mobx-state-tree';
const TestModel = types
.model('Test', {
photos: types.array(Results),
tutorials: types.array(Results)
})
.actions(self => ({
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
applySnapshot(self, {
...self,
photos: [... results, ...self.photos],
tutorials: [... results, ...self.tutorials]
});
})
}))
.views(self => ({
getPhoto() {
return self.photos;
},
getTutorials() {
return self.tutorials;
}
}));
const RootModel = types.model('Root', {
data: TestModel
});
export { RootModel };
export type Root = Instance<typeof RootModel>;
export type Test = Instance<typeof TestModel>;
React component for Photos.tsx
import React, { Component } from 'react';
import Spinner from 'components/Spinner';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root
}
#inject('rootTree')
#observer
class Photos extends Component<Props> {
componentDidMount() {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('photo');
}
}
displayPhoto() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const photoResults = rootTree.data.getPhoto();
if (photoResults.$treenode.snapshot[0]) {
return (
<div>
<div className='photo-title'>{'Photo'}</div>
{photoResults.$treenode.snapshot.map(Item => (
<a href={photoItem.attributes.openUrl} target='_blank'>
<img src={photoItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='photo-module'>{this.displayPhoto()}</div>;
}
}
export default Photos;
Similarly, Tutorials.tsx is like so:
import React, { Component } from 'react';
import Spinner from '';
import { Root } from '../../stores';
import { observer, inject } from 'mobx-react';
interface Props {
rootTree?: Root;
}
#inject('rootTree')
#observer
class Tutorials extends Component<Props> {
componentDidMount() {
if (this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.data.fetchData('tuts');
}
}
componentDidUpdate(prevProps) {
if (prevProps.ctx !== this.props.ctx) {
const { rootTree } = this.props;
if (!rootTree) return null;
rootTree.search.fetchData('tuts');
}
}
displayTutorials() {
const { rootTree } = this.props;
if (!rootTree) return null;
// calling method in MST view
const tutResults = rootTree.data.getTutorials();
if (tutResults.$treenode.snapshot[0]) {
return (
<div>
<div className='tutorials-title'>{'Tutorials'}</div>
{tutResults.$treenode.snapshot.map(tutorialItem => (
<a href={tutorialItem.attributes.openUrl} target='_blank'>
<img src={tutorialItem.url} />
</a>
))}
</div>
);
} else {
return <Spinner />;
}
}
render() {
return <div className='tutorials-module'>{this.displayTutorials()}</div>;
}
}
export default Tutorials;
Why are you using applySnapshot at all in this case? I don't think it's necessary. Just assign your data as needed in your action:
.actions(self => ({
//If you're fetching both at the same time
fetchData: flow(function* fetchData(param) {
const results = yield api.fetch(param);
//you need cast() if using Typescript otherwise I think it's optional
self.photos = cast([...results.photos, ...self.photos])
//do you really intend to prepend the results to the existing array or do you want to overwrite it with the sever response?
self.tutorials = cast(results.tutorials)
})
}))
Or if you need to make two separate requests to fetch your data it's probably best to make it two different actions
.actions(self => ({
fetchPhotos: flow(function* fetchPhotos(param) {
const results = yield api.fetch(param)
self.photos = cast([... results, ...self.photos])
}),
fetchTutorials: flow(function* fetchTutorials(param) {
const results = yield api.fetch(param)
self.tutorials = cast([... results, ...self.tutorials])
}),
}))
Regardless, it doesn't seem like you need applySnapshot. Just assign your data in your actions as necessary. There's nothing special about assigning data in an async action.

ReactRouter v4 Prompt - override default alert

The React Router v4 <Prompt></Prompt> component is perfect for the use case of protecting navigation away from a partially filled out form.
But what if we want to supply our own logic in place of the default browser alert() that this component uses? React is intended for creating UIs, so it seems like a pretty reasonable use case. Digging through the issues on Prompt in the github I did not find anyone asking about this.
Does anyone know of a solution for providing custom behavior for the alert?
Although you can make use of a custom Modal component while preventing navigating between pages through Links, you can't show a custom modal while trying to close browser or reload it.
However if thats fine with you, you can make use of history.listen to and block navigation. I wrote a generic HOC for it which solves this use case.
In the below code whitelisted pathnames are the pathnames that you would want the other person to navigate to without showing the prompt
import React from 'react';
import { withRouter } from 'react-router';
import _ from 'lodash';
const navigationPromptFactory = ({ Prompt }) => {
const initialState = {
currentLocation: null,
targetLocation: null,
isOpen: false
};
class NavigationPrompt extends React.Component {
static defaultProps = {
when: true
};
state = initialState;
componentDidMount() {
this.block(this.props);
window.addEventListener('beforeunload', this.onBeforeUnload);
}
componentWillReceiveProps(nextProps) {
const {
when: nextWhen,
history: nextHistory,
whiteListedPathnames: nextWhiteListedPaths
} = nextProps;
const { when, history, whiteListedPathnames } = this.props;
if (
when !== nextWhen ||
!_.isEqual(nextHistory.location, history.location) ||
!_.isEqual(whiteListedPathnames, nextWhiteListedPaths)
) {
this.unblock();
this.block(nextProps);
}
}
componentWillUnmount() {
this.unblock();
window.removeEventListener('beforeunload', this.onBeforeUnload);
}
onBeforeUnload = e => {
const { when } = this.props;
// we can't override an onBeforeUnload dialog
// eslint-disable-next-line
// https://stackoverflow.com/questions/276660/how-can-i-override-the-onbeforeunload-dialog-and-replace-it-with-my-own
if (when) {
// support for custom message is no longer there
// https://www.chromestatus.com/feature/5349061406228480
// eslint-disable-next-line
// https://stackoverflow.com/questions/38879742/is-it-possible-to-display-a-custom-message-in-the-beforeunload-popup
// setting e.returnValue = "false" to show prompt, reference below
//https://github.com/electron/electron/issues/2481
e.returnValue = 'false';
}
};
block = props => {
const {
history,
when,
whiteListedPathnames = [],
searchQueryCheck = false
} = props;
this.unblock = history.block(targetLocation => {
const hasPathnameChanged =
history.location.pathname !== targetLocation.pathname;
const hasSearchQueryChanged =
history.location.search !== targetLocation.search;
const hasUrlChanged = searchQueryCheck
? hasPathnameChanged || hasSearchQueryChanged
: hasPathnameChanged;
const isTargetWhiteListed = whiteListedPathnames.includes(
targetLocation.pathname
);
const hasChanged =
when && hasUrlChanged && !isTargetWhiteListed;
if (hasChanged) {
this.setState({
currentLocation: history.location,
targetLocation,
isOpen: true
});
}
return !hasChanged;
});
};
onConfirm = () => {
const { history } = this.props;
const { currentLocation, targetLocation } = this.state;
this.unblock();
// replacing current location and then pushing navigates to the target otherwise not
// this is needed when the user tries to change the url manually
history.replace(currentLocation);
history.push(targetLocation);
this.setState(initialState);
};
onCancel = () => {
const { currentLocation } = this.state;
this.setState(initialState);
// Replacing the current location in case the user tried to change the url manually
this.unblock();
this.props.history.replace(currentLocation);
this.block(this.props);
};
render() {
return (
<Prompt
{...this.props}
isOpen={this.state.isOpen}
onCancel={this.onCancel}
onConfirm={this.onConfirm}
/>
);
}
}
return withRouter(NavigationPrompt);
};
export { navigationPromptFactory };
In order to use the above, you can simply provide your custom Prompt Modal like
const NavigationPrompt = navigationPromptFactory({
Prompt: AlertDialog
});
const whiteListedPathnames = [`${match.url}/abc`, match.url];
<NavigationPrompt
when={isEditingPlan}
cancelLabel={'Stay'}
confirmLabel={'Leave'}
whiteListedPathnames={whiteListedPathnames}
title={'Leave This Page'}
>
<span>
Unsaved Changes may not be saved
</span>
</NavigationPrompt>
The prompt component by default doesn't allow overriding the use of window.alert().
Here's a link to a conversation that matches your needs fairly similarly:
https://github.com/ReactTraining/react-router/issues/4635
There's a few key points in there that you can refer to, mostly just that instead of using prompt you can just make your own modal to be triggered on specific user actions. :)
Hope this helps
Here's a component using hooks to achieve block functionality, the <Prompt.../> component didn't work for me because I wanted to ignore the search on the location.
import { useEffect, useRef } from 'react';
import { useHistory } from 'react-router-dom';
interface IProps {
when: boolean;
message: string;
}
export default function RouteLeavingGuard({ when, message }: IProps) {
const history = useHistory();
const lastPathName = useRef(history.location.pathname);
useEffect(() => {
const unlisten = history.listen(({ pathname }) => lastPathName.current = pathname);
const unblock = history.block(({ pathname }) => {
if (lastPathName.current !== pathname && when) {
return message;
}
});
return () => {
unlisten();
unblock();
}
}, [history, when, message]);
return null;
}

How to reload current page in ReactJS?

How to reload current page in ReactJS? in case of javascript we can write window.location.reload();
How to do the same in Reactjs? I'm able to add new data by UI. But without refreshing, I'm not able to see the list. I want it so that whenever I'm adding some data, it refreshes by itself.
onAddBucket() {
let self = this;
let getToken = localStorage.getItem('myToken');
var apiBaseUrl = "...";
let input = {
"name" : this.state.fields["bucket_name"]
}
axios.defaults.headers.common['Authorization'] = getToken;
axios.post(apiBaseUrl+'...',input)
.then(function (response) {
if(response.data.status == 200){
let result = self.state.buckets.concat(response.data.buckets)
}else{
alert(response.data.message);
}
})
.catch(function (error) {
console.log(error);
});
}
use this might help
window.location.reload();
You can use window.location.reload(); in your componentDidMount() lifecycle method. If you are using react-router, it has a refresh method to do that.
Edit: If you want to do that after a data update, you might be looking to a re-render not a reload and you can do that by using this.setState(). Here is a basic example of it to fire a re-render after data is fetched.
import React from 'react'
const ROOT_URL = 'https://jsonplaceholder.typicode.com';
const url = `${ROOT_URL}/users`;
class MyComponent extends React.Component {
state = {
users: null
}
componentDidMount() {
fetch(url)
.then(response => response.json())
.then(users => this.setState({users: users}));
}
render() {
const {users} = this.state;
if (users) {
return (
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
)
} else {
return (<h1>Loading ...</h1>)
}
}
}
export default MyComponent;
Since React eventually boils down to plain old JavaScript, you can really place it anywhere! For instance, you could place it in a `componentDidMount()' function in a React class.
For your edit, you may want to try something like this:
class Component extends React.Component {
constructor(props) {
super(props);
this.onAddBucket = this.onAddBucket.bind(this);
}
componentWillMount() {
this.setState({
buckets: {},
})
}
componentDidMount() {
this.onAddBucket();
}
onAddBucket() {
let self = this;
let getToken = localStorage.getItem('myToken');
var apiBaseUrl = "...";
let input = {
"name" : this.state.fields["bucket_name"]
}
axios.defaults.headers.common['Authorization'] = getToken;
axios.post(apiBaseUrl+'...',input)
.then(function (response) {
if (response.data.status == 200) {
this.setState({
buckets: this.state.buckets.concat(response.data.buckets),
});
} else {
alert(response.data.message);
}
})
.catch(function (error) {
console.log(error);
});
}
render() {
return (
{this.state.bucket}
);
}
}
You can use useNavigate and navigate to the same url you are on. For example, instead of window.location.reload(), you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error
Creating some extra state and tracking them for re-rendering your page might unnecessarily complicate your code.
And using useEffect() to re-render your page, again, will unnecesarily complicate your code.
This is my code .This works for me
componentDidMount(){
axios.get('http://localhost:5000/supplier').then(
response => {
console.log(response)
this.setState({suppliers:response.data.data})
}
)
.catch(error => {
console.log(error)
})
}
componentDidUpdate(){
this.componentDidMount();
}
window.location.reload(); I think this thing is not good for react js
use useHistory method in react
import {useHistory} from 'react-router-dom'
const history = useHistory()
history.go(0) // it will refresh particullar page
or use useEffect method
const [data, setData] = useState([])
useEffect(()=>{
setData(reponseApidata)},[data])
//in useEffect dependcy you mention particullar state for you store reposnse data

Categories

Resources