Svelte: How to manually stop subscribe? - javascript

I have a store that fetches data once in a while – according to user's actions. This is a store because its data is used globally and mainly all components needs the latest data available.
But, for one specific component, I only need the first data loaded.
For this component, there is no reason to keep a subscribe() function running after the first fetch. So, how can I stop this subscribe function?
The only example in Svelte doc's uses onDestroy(), but I need to manually stop this subscribe().
I tried with a simple "count" (if count > 1, unsubscribe), but it doesn't work.
import user from './store'
let usersLoaded = 0
const unsubscribe = user.subscribe(async (data) => {
if(data.first_name !== null) {
usersLoaded = usersLoaded + 1
}
if(usersLoaded > 1) {
unsubscribe;
}
});
Here's a full working REPL:
→ https://svelte.dev/repl/95277204f8714b4b8d7f72b51da45e67?version=3.35.0

You might try Svelte's get. A subscription is meant for situations where you need to react to changes; it's a long-term relationship. If you just need the current value of the store, get is the way to go.
Occasionally, you may need to retrieve the value of a store to which you're not subscribed. get allows you to do so.
import { get } from 'svelte/store';
const value = get(store);

I had to use unsubscribe() instead of unsubscribe 🤡
Here's the final working REPL with some improvements:
https://svelte.dev/repl/95277204f8714b4b8d7f72b51da45e67?version=3.35.0

You can use auto subscribe: $user which will also auto unsubscribe.
Some more details in the docs.
Example:
let user1 = null;
$: if ($user?.first_name && !user1) {
user1 = $user.first_name;
console.log('first user', $user.first_name);
}
And you do not really need a writable store here. You can use a readable and use the set method to handle the fetch.
Something like:
const user = readable(defaultUser, set => {
.... fetch the data ....
.... set(data)
}
By the way: This is already async code and you can use set(data) to store the fetch result.

Updated: 04 Jan 2023
Best way to unsubscribe is using onDestroy Svelte's hook
import { onDestroy } from "svelte"
const subcriber = page.subscribe((newPage) => handleChangePage(newPage.params.id))
onDestroy(subcriber)

Related

Can't find the way to useSelector from redux-toolkit within event handler and pass params to it

There is an event handler for click and when it triggered i want to pull specific data from redux using selector where all logic many-to-many is implemented. I need to pass id to it in order to receive its individual data. Based on rules of the react the hooks can be called in function that is neither a React function component nor a custom React Hook function.
So what is the way to solve my problem ?
const handleMediaItemClick = (media: any): void => {
// For example i check media type and use this selector to pull redux data by id
const data = useSelector(playlistWithMediaSelector(imedia.id));
};
As stated in the error message, you cannot call hooks inside functions. You call a hook inside a functional component and use that value inside the function. The useSelector hook updates the variable each time the state changes and renders that component.
Also, when you get data with useSelector, you should write the reducer name you need from the redux state.
const CustomComponent = () => {
// The data will be updated on each state change and the component will be rendered
const data = useSelector((state) => state.REDUCER_NAME);
const handleMediaItemClick = () => {
console.log(data);
};
}
You can check this page for more information.https://react-redux.js.org/api/hooks#useselector
You should probably use local state value to track that.
const Component = () => {
const [imediaId, setImediaId] = useState(null);
const data = useSelector(playlistWithMediaSelector(imediaId));
function handleMediaClick(id) {
setImediaId(id)
}
useEffect(() => {
// do something on data
}, [imediaId, data])
return <div>...</div>
}
Does that help?
EDIT: I gather that what you want to do is to be able to call the selector where you need. Something like (considering the code above) data(id) in handleMediaClick. I'd bet you gotta return a curried function from useSelector, rather than value. Then you would call it. Alas, I haven't figured out how to that, if it's at all possible and whether it's an acceptable pattern or not.

Returning a value from an Async Function. AWS/React

I'm trying to build a component that retrieves a full list of users from Amazon AWS/Amplify, and displays said results in a table via a map function. All good so far.
However, for the 4th column, I need to call a second function to check if the user is part of any groups. I've tested the function as a button/onClick event - and it works (console.logging the output). But calling it directly when rendering the table data doesn't return anything.
Here is what I've included in my return statement (within the map function)
<td>={getUserGroups(user.email)}</td>
Which then calls this function:
const getUserGroups = async (user) => {
const userGroup = await cognitoIdentityServiceProvider.adminListGroupsForUser(
{
UserPoolId: '**Removed**',
Username: user,
},
(err, data) => {
if (!data.Groups.length) {
return 'No';
} else {
return 'Yes';
}
}
);
};
Can anyone advise? Many thanks in advance if so!
Because you should never do that! Check this React doc for better understanding of how and where you should make AJAX calls.
There are multiple ways, how you can solve your issue. For instance, add user groups (or whatever you need to get from the backend) as a state, and then call the backend and then update that state with a response and then React will re-render your component accordingly.
Example with hooks, but it's just to explain the idea:
const [groups, setGroups] = useState(null); // here you will keep what "await cognitoIdentityServiceProvider.adminListGroupsForUser()" returns
useEffect(() => {}, [
// here you will call the backend and when you have the response
// you set it as a state for this component
setGroups(/* data from response */);
]);
And your component (column, whatever) should use groups:
<td>{/* here you will do whatever you need to do with groups */}</td>
For class components you will use lifecycle methods to achieve this (it's all in the documentation - link above).

how to access redux store from custom javascript

I'm trying to implement an external API library in a redux application.
I'm fresh new in redux so I don't know exactly how it works.
In my javascript using the API library, I wan't to access info from a container (the user firstanme if he's logged).
After reading some doc, I tried to import the store in my js file, to get the state of the user, but I can't reach the info I need.
Here's the code I tried :
import configureStore from '../store/configureStore';
const store = configureStore();
const state = store.getState();
I get many info in state, but not the one I need. Any help ?
First of all it looks like configureStore creates new store every time you call it. But you need the very that store that your components will use and populate. So you need to somehow access the store you are passing your Provider.
Then since store state is "changing" you can't simply read it once. So your user data might be initially empty but available some time later.
In this case you could make it a Promise
const once = selector => available => new Promise(resolve => {
store.subscribe(() => {
const value = selector(value)
if(available(value)) resolve(value)
})
})
And usage
const user = once(state => state.user)(user => user && user.fullName)
user.then(user => console.log(`User name is ${user.fullName}`)
Or if your data might be changing more than once during application lifecycle you might want to wrap it with something that represent changing data (observable). RX examle

ReactJS: How to constantly check if token in localstorage has expired?

In ReactJS, is there a way to constantly check to see if the token saved in the localstorage has expired? If it has expired, would like to remove the token.
Came across the following but doesn't it only get triggered when the page gets reloaded?:
window.onbeforeunload = function() {
//remove token
return '';
}
The following assumes you are using redux... you can create a middleware that will trigger an action when a token expires.. this will allow for you to handle a reducer downstream. The redux approach is mainly because Redux is currently the most popular state management solution used with React.
// export the action type used as a const, so it can be imported for
// the reducer to listen for... The export const/key is more convenient
// then you can use a more namespaced string value to help prevent collisions
export const TOKEN_EXPIRED = 'tokenExpiredMiddleware_TokenExpired';
// redux middleware pattern (store) => (next) => (action) => result;
export default function expiredTokenMiddleware(store) {
// here we only care about a handle to the store for store.dispatch
// start checking every 15 seconds, probably want this configurable
setInterval(checkToken.bind(null, store), 15000);
// return a pass-through for the plugin, so other plugins work
return (next) => (action) => next(action);
}
// export checkToken for testing... etc
// should probably be a separate module
export function checkToken(store) {
var tokenId = ''; // TODO: Identify token
var isExpired = true; // TODO: replace with function to get current state
if (isExpired) {
store.dispatch({
type: TOKEN_EXPIRED,
payload: { tokenId },
});
}
};
Then, when you do your createStore, you can just add this middleware which will emit the appropriate action, and you can handle it in your appropriate reducer... I do something similar for the window's resize/scroll events so that my size/position is always set.
This is using ES6+ syntax, since you're using React I think that is a fair assumption
Since you cannot use code running on the user's machine for security relevant use cases anyway, why not check the token only when it is used?
At some point you most likely load the token from local storage and use it to e.g. authenticate the session. Why not first check its validity then before using it?
This saves you the trouble of having an ongoing activity that checks the token, bundles related functionality and most likely reduces the complexity of your code.
After all, the token won't do any harm by just being stored in the browser's storage without being used, would it?

Idiomatic way to lazy-load with mobx

What is the current idiomatic way to lazy load properties when using MobX?
I've been struggling with this for a few days, and I haven't found any good examples since strict mode has become a thing. I like the idea of strict mode, but I'm starting to think lazy-loading is at odds with it (accessing, or observing a property should trigger the side effect of loading the data if it's not already there).
That's the crux of my question, but to see how I got here keep reading.
The basics of my current setup (without posting a ton of code):
React Component 1 (ListView):
componentWillMount
componentWillMount & componentWillReceiveProps - the component gets filter values from route params (react-router), saves it as an observable object on ListView, and tells the store to fetch 'proposals' based on it
Store.fetchProposals checks to see if that request has already been made (requests are stored in an ObservableMap, keys by serializing the filter object so two identical filters will return the same response object). It makes the request if it needs to and returns the observable response object that contains info on whether the request is finished or has errors.
ListView saves the observable response object as a property so it can display a loading or error indicator.
ListView has a computed property that calls Store.getProposals using the same filter object used to fetch
Store.getProposals is a transformer that takes a filter object, gets all proposals from an ObservableMap (keys on proposal.id), filters the list using the filter object and returns a Proposal[] (empty if nothing matched the filter, including if no proposals are loaded yet)
This all appears to work well.
The problem is that proposals have properties for client and clientId. Proposal.clientId is a string that's loaded with the proposal. I want to wait until client is actually accessed to tell the store to fetch it from the server (assuming it's not already in the store). In this case ListView happens to display the client name, so it should be loaded shortly after the Proposal is.
My closest I've gotten is setting up a autorun in the Proposal's constructor list this, but part of it is not reacting where I'm indending. (truncated to relevant sections):
#observable private clientId: string = '';
#observable private clientFilter: IClientFilter = null;
#observable client: Client = null;
constructor(sourceJson?: any) {
super(sourceJson);
if (sourceJson) {
this.mapFromJson(sourceJson);
}
//this one works. I'm turning the clientId string into an object for the getClients transformer
autorun(() => { runInAction(() => { this.clientFilter = { id: this.clientId }; }) });
autorun(() => {
runInAction(() => {
if (this.clientId && this.clientFilter) {
const clients = DataStore.getClients(this.clientFilter);
const response = DataStore.fetchClients(this.clientFilter);
if (response.finishedTime !== null && !response.hasErrors) {
this.client = clients[0] || null;
console.log('This is never called, but I should see a client here: %o', DataStore.getClients(this.clientFilter));
}
}
})
});
}
The response object is observable:
export class QueryRequest<T extends PersistentItem | Enum> {
#observable startTime: Date = new Date();
#observable finishedTime: Date = null;
#observable errors: (string | Error)[] = [];
#observable items: T[] = [];
#computed get hasErrors() { return this.errors.length > 0; }
#observable usedCache: boolean = false;
}
I'm getting the feeling I'm fighting the system, and setting up autoruns in the constructor doesn't seem ideal anyway. Anyone solve this pattern in a reasonable way? I'm open to suggestions on the whole thing if my setup looks crazy.
EDIT 1: removed #Mobx for clarity.
EDIT 2:
Trying to re-evaluate my situation, I (again) found the excellent lib mobx-utils, which has a lazyObservable function that may suite my needs. Currently it's looking like this:
client = lazyObservable((sink) => {
autorun('lazy fetching client', () => {
if (this.clientFilter && this.clientFilter.id) {
const request = DataStore.fetchClients(this.clientFilter);
if (request.finishedTime !== null && !request.hasErrors) {
sink(request.items[0]);
}
}
})
}, null);
This is working!
I think I need the autorun in there to update based on this objects clientId/clientFilter property (if this object is later assigned to a new client I'd want the lazyObservable to be updated). I don't mind a little boilerplate for lazy properties, but I'm, definitely open to suggestions there.
If this ends up being the way to go I'll also be looking at fromPromise from the same lib instead of my observable request object. Not sure because I'm keeping track of start time to check for staleness. Linking here in case someone else has not come across it:)
I've been using a different approach in my projects and I extracted it into a separate npm package: https://github.com/mdebbar/mobx-cache
Here's a quick example:
First, we need a React component to display the client info:
#observer
class ClientView extends React.Component {
render() {
const entry = clientCache.get(this.props.clientId)
if (entry.status !== 'success') {
// Return some kind of loading indicator here.
return <div>Still loading client...</div>
}
const clientInfo = entry.value
// Now you can render your UI based on clientInfo.
return (
<div>
<h2>{clientInfo.name}</h2>
</div>
)
}
}
Then, we need to setup the clientCache:
import MobxCache from "mobx-cache";
function fetchClient(id) {
// Use any fetching mechanism you like. Just make sure to return a promise.
}
const clientCache = new MobxCache(fetchClient)
That's all you need to do. MobxCache will automatically call fetchClient(id) when it's needed and will cache the data for you.

Categories