I'm having some issue with my context file. I have a file called "auth-state" that I use to manage application wide authentication state. It initializes a react context object, and has several helper functions defined that help manage things like logging out, verifying user authentication tokens, and logging in. Excluding a lot of the helper functions, it looks like this:
const noUserObj = {
userName: "Anonymous",
isLoggedIn: false,
authToken: "-",
projList: null,
attemptLogIn: () => {},
userLogout: () => {}
}
const AuthState = React.createContext(noUserObj);
const AuthStateProvider = (props) => {
const authCtx = useContext(AuthState);
let existingToken;
if(localStorage.getItem("token")) {
existingToken = localStorage.getItem("token");
} else {
existingToken = "-"
}
const [ user, setUser ] = useState(noUserObj);
return (
<AuthState.Provider
value={{
userName: user.userName,
isLoggedIn: user.isLoggedIn,
authToken: user.authToken,
projList: user.projList,
attemptLogin: attemptLoginWithAxios,
userLogout: logoutWithAxios
}}
>
{props.children}
</AuthState.Provider>
)
}
export {AuthStateProvider};
export default AuthState
I use a state variable to manage the actual context values and then pass the state variable to the context providers "value" prop. I know you can't see it here, but whenever one of the helper functions log in or out, they update the "user" state variable, and this in turn updates the context values. From my understanding this is relatively standard practice. My issue is that when trying to access the context values from within the auth-state context file itself, I always get outdated or "default" states.
This is especially confusing because when I use the react-dev tools in the chrome browser to look at the values currently saved in the auth-provider, everything looks right. I can see the values update as I login/out, and watch it function as intended. But whenever I try to access the context values from within auth-state (whether that be by using useContext(authState) or by accessing the "user" state variable itself) I only ever get the default object you see declared at the top of the authState file (noUserObj).
This is an issue because one of those helper functions I mentioned earlier (one that logs users out) requires the users authtoken to be able to log them out. And I defined this helper function inside the auth-state file explicitly because it would have easy access to all auth related variables it needed. But everytime I try to access the state variable or the context itself I only get default. I'll even run it in debug mode, pause the execution right before "logging out" runs, and see that the context variable is properly updated, but the helper function only gets default values.
I don't know what the heck is happening, but right now I'm just working around it by passing the the helper functions tokens from outside the file, wherever I actually call them. (this is even weirder because I get these tokens FROM THE AUTH-CONTEXT, its just for some reason when I use useContext(auth-state) outside of the auth-state file itself, everything works fine.) Whatever the case, I'm sure I'm just overlooking something small. Thank you in advance for the help.
Related
I'm developing an app using React Native that allows you to create your own checklists and add items to them.
For example you'd have "Create Checklist", and inside that you'll have the option to "Add Item", "Delete Item" "Edit Item", basic CRUD methods etc.
It's going to be completely offline but I'm wondering what the best approach to storing this data locally would be.
Should I be using a DB such as firebase? I have read that it is overkill and to use something like Redux but I'm not sure if the latter will accomplish everything I need. As long as it's storing data which can be edited, and will save on the user's device (with minimal effort) it sounds good to me.
Would appreciate some input on this, thanks!
You could use AsyncStorage for persisting data locally on the user's phone. It is a simple persistent key-value-storage.
Each checklist is most likely an array of JS objects. The documentation provides an example on how to store objects.
const storeData = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem('#storage_Key', jsonValue)
} catch (e) {
// saving error
}
}
The value parameter is any JS object. We use JSON.stringify to create a JSON string. We use AsyncStorage.setItem in order to persist the data. The string #storage_Key is the key for the object. This could be any string.
We retrieve a persisted object as follows.
const getData = async () => {
try {
const jsonValue = await AsyncStorage.getItem('#storage_Key')
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch(e) {
// error reading value
}
}
Both examples are taken from the official documentation.
Keep in mind that this functionality should be used for persistence only. If the application is running, you should load the complete list, or parts of the list if it is very large, in some sort of application cache. The implementation for this functionality now heavily depends on how your current code looks like. If you have a plain view, then you could access the local storage in an effect and just store it in a local state.
function MySuperList() {
const [list, setList] = useState([]);
React.useEffect(() => {
// retrieve data using the above functionality and set the state
}, [])
// render list
return (...)
}
I would implement some sort of save button for this list. If it is pressed, then we persist the data in the local storage of the phone.
<script>
import {onMount} from 'svelte';
import {session} from "$app/stores"
import {writable} from 'svelte/store';
const store = writable('some value');
let value = null
onMount(() => {
// this works
// return store.subscribe( (storeValue) => {value = storeValue}); // this works
// this throws an error:
// return session.subscribe( (sessionValue) => {value = sessionValue});
// Uncaught (in promise) Error: Function called outside component initialization
});
</script>
can someone please explain to me the problem with session.subscribe and why it keeps throwing?
if I move session.subscribe outside onMount it runs fine.
Note: this code is part of a SvelteKit Project, inside a Svelte component, not a SvelteKit page/route.
What goes wrong
It seems that you are actually experiencing intended behaviour. Under the documentation for $app/stores you will find this:
Stores are contextual — they are added to the context of your root component. This means that session and page are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in session.
Because of that, you must subscribe to the stores during component initialization (which happens automatically if you reference the store value, e.g. as $page, in a component) before you can use them.
When you were attempting this, you probably got a callstack that looks something like this:
Error: Function called outside component initialization
at get_current_component (index.mjs:953:15)
at getContext (index.mjs:989:12) <----------Here is the problem
at getStores (stores.js:19:17)
at Object.subscribe (stores.js:70:17)
at index.svelte:10:13
at run (index.mjs:18:12)
at Array.map (<anonymous>)
at index.mjs:1816:45
at flush (index.mjs:1075:17)
at init (index.mjs:1908:9)
We can see that Svelte attempts to call getContext when you subscribe to the session. Calling getContext outside of the component root is not allowed, which causes the subscription to fail.
I agree that this is quite unintuitive and I am not really sure why they implemented it this way.
Workaround
By the way, are you really sure you only want to subscribe to session on mount? What are you trying to do?
If you really only want to subscribe to session after component mount, you could use this workaround: Create your own store that updates whenever the session changes, then listen to that.
<script>
import { onMount } from "svelte";
import { session } from "$app/stores";
import { writable } from "svelte/store";
let mySession = writable($session);
$: $mySession = $session;
onMount(()=>{
mySession.subscribe(...whatever...);
})
</script>
This must be user error, but I've got an app with a simple currentUser query that looks at a JWT for an id, looks it up, and returns the appropriate user.
I can look at devtools and see that it's in the cache as __ref:User:19
export const CURRENT_USER_QUERY = gql`
query{
currentUser {
id
fullName
email
}
}
`
But in my component, when I do const { currentUser } = client.readQuery({ query: CURRENT_USER_QUERY }); it (intermittently) blows up because:
Cannot destructure property 'currentUser' of 'client.readQuery(...)' as it is null.
User:19 exists and is all the stuff I need. I hit reload, and that same page works.
I have my default error policy set to "all" in the client.
This is version 3.3.11 of the client. Chrome v88.04, for what that's worth. React 17.01.
Am I missing something here? Should that not return a value synchronously (and dependably) if that item's in the cache?
Is there a better way to deal with that situation? I'm trying to move this app away from storing things in redux or some context provider since it's already being handled by Apollo. Seems redundant to have that responsibility handled by a bunch of different things.
I was facing issues with .readQuery() last night. I was getting null returned everytime, though the logic was right. I was calling .readQuery() within a component I imported into my React page.
What ended up being my issue is that I was not updating the same query I made in the "parent" react page as the one in the component.
I don't know if this is the same problem you're running into, but I thought I'd leave this here for perpetuity and perspective.
I was facing the same issue. I fixed it like this:
const existingData = cache.readQuery({
query: GET_CONTRACT,
variables: {
...variables, // fixed: passing the same reference to variables
},
});
The problem here can be in new variables or absence of variables. So the query was made with variables and you try to get it from the cache without. .readQuery from cache must be identical
You need to pass the same variable with the same values you use when executing the mutation and in the same order
existingData = cache.readQuery({
query: QUERY_NAME,
variables: {
x: mutationValue1,
y: mutationValue2
z: mutationValue3
},
});`
I'm facing an issue with realm.js. I add an object and give permission to that, and it seems to work well, but when I log out and then log in with another account I can see the previous data on device (even the ones for those I don't have permissions, that I can only read but not edit).
So now, I don't have permissions to read or query those data but I'm able to receive live updates (not as expected) and see them! How can I prevent that to happens?
Thank you
I think you need to clear your redux store as defined below.
const { Types, Creators } = createActions({
desktopSuccess: ['payload'],
desktopClear: [],
});
export const desktopReducer = createReducer(INITIAL_STATE, {
[Types.DESKTOP_SUCCESS]: success,
[Types.DESKTOP_CLEAR]: clear,
});
On click of logout clear your store as shown under:
onPressLogOut = () => {
AsyncStorage.clear();
store.dispatch(DesktopAction.desktopClear());
this.props.navigation.navigate('LoginScreen');
};
I'm confused as to the appropriate way to access a bunch of images stored in Firebase storage with a react redux firebase web app. In short, I'd love to get a walkthrough of, once a photo has been uploaded to firebase storage, how you'd go about linking it to a firebase db (like what exactly from the snapshot returned you'd store), then access it (if it's not just <img src={data.downloadURL} />), and also how you'd handle (if necessary) updating that link when the photo gets overwritten. If you can answer that, feel free to skip the rest of this...
Two options I came across are either
store the full URL in my firebase DB, or
store something less, like the path within the bucket, then call downloadURL() for every photo... which seems like a lot of unnecessary traffic, no?
My db structure at the moment is like so:
{
<someProjectId>: {
imgs: {
<someAutoGenId>: {
"name":"photo1.jpg",
"url":"https://<bucket, path, etc>token=<token>"
},
...
},
<otherProjectDetails>: "",
...
},
...
}
Going forward with that structure and the first idea listed, I ran into trouble when a photo was overwritten, so I would need to go through the list of images and remove the db record that matches the name (or find it and update its URL). I could do this (at most, there would be two refs with the old token that I would need to replace), but then I saw people doing it via option 2, though not necessarily with my exact situation.
The last thing I did see a few times, were similar questions with generic responses pointing to Cloud Functions, which I will look into right after posting, but I wasn't sure if that was overcomplicating things in my case, so I figured it couldn't hurt too much to ask. I initially saw/read about Cloud Functions and the fact that Firebase's db is "live," but wasn't sure if that played well in a React/Redux environment. Regardless, I'd appreciate any insight, and thank you.
In researching Cloud Functions, I realized that the use of Cloud Functions wasn't an entirely separate option, but rather a way to accomplish the first option I listed above (and probably the second as well). I really tried to make this clear, but I'm pretty confident I failed... so my apologies. Here's my (2-Part) working solution to syncing references in Firebase DB to Firebase Storage urls (in a React Redux Web App, though I think Part One should be applicable regardless):
PART ONE
Follow along here https://firebase.google.com/docs/functions/get-started to get cloud functions enabled.
The part of my database with the info I was storing relating to the images was at /projects/detail/{projectKey}/imgs and had this structure:
{
<autoGenKey1>: {
name: 'image1.jpg',
url: <longURLWithToken>
},
<moreAutoGenKeys>: {
...
}, ...}
My cloud function looked like this:
exports.updateURLToken = functions.database.ref(`/projects/detail/{projectKey}/imgs`)
.onWrite(event => {
const projectKey = event.params.projectKey
const newObjectSet = event.data.val()
const newKeys = Object.keys(newObjectSet)
const oldObjectSet = event.data.previous.val()
const oldKeys = Object.keys(oldObjectSet)
let newObjectKey = null
// If something was removed, none of this is necessary - return
if (oldKeys.length > newKeys.length) {
return null
}
for (let i = 0; i < newKeys.length; ++i) {// Looking for the new object -> will be missing in oldObjectSet
const key = newKeys[i]
if (oldKeys.indexOf(key) === -1) {// Found new object
newObjectKey = key
break
}
}
if (newObjectKey !== null) {// Checking if new object overwrote an existing object (same name)
const newObject = newObjectSet[newObjectKey]
let duplicateKey = null
for (let i = 0; i < oldKeys.length; ++i) {
const oldObject = oldObjectSet[oldKeys[i]]
if (newObject.name === oldObject.name) {// Duplicate found
duplicateKey = oldKeys[i]
break
}
}
if (duplicateKey !== null) {// Remove duplicate
return event.data.ref.child(duplicateKey).remove((error) => error ? 'Error removing duplicate project detail image' : true)
}
}
return null
})
After loading this function, it would run every time anything changed at that location (projects/detail/{projectKey}/imgs). So I uploaded the images, added a new object to my db with the name and url, then this would find the new object that was created, and if it had a duplicate name, that old object with the same name was removed from the db.
PART TWO
So now my database had the correct info, but unless I refreshed the page after every time images were uploaded, adding the new object to my database resulted (locally) in me having all the duplicate refs still, and this is where the realtime database came in to play.
Inside my container, I have:
function mapDispatchToProps (dispatch) {
syncProjectDetailImages(dispatch) // the relavant line -> imported from api.js
return bindActionCreators({
...projectsContentActionCreators,
...themeActionCreators,
...userActionCreators,
}, dispatch)
}
Then my api.js holds that syncProjectDetailImages function:
const SAVING_PROJECT_SUCCESS = 'SAVING_PROJECT_SUCCESS'
export function syncProjectDetailImages (dispatch) {
ref.child(`projects/detail`).on('child_changed', (snapshot) => {
dispatch(projectDetailImagesUpdated(snapshot.key, snapshot.val()))
})
}
function projectDetailImagesUpdated (key, updatedProject) {
return {
type: SAVING_PROJECT_SUCCESS,
group: 'detail',
key,
updatedProject
}
}
And finally, dispatch is figured out in my modules folder (I used the same function I would when saving any part of an updated project with redux - no new code was necessary)