React context not saving updates - reactjs

I created a react project for music/artists.
I'm using the React context for accessing artists data across components:
import React, { createContext, useState } from "react";
export const ArtistContext = createContext(null);
const ArtistContextProvider = props => {
const [artists, setArtists] = useState([]);
const addArtist = new_artist => {
setArtists([...artists, new_artist])
};
return (
<ArtistContext.Provider value={{artists, addArtist}}>
{props.children}
</ArtistContext.Provider>
);
};
export default ArtistContextProvider;
I can successfully get the data from the artist context object and also execute de addArtists methods, but the artist I pass to the function is not saved and the variable stills returns the default value.
My usage in a component:
const { artists, addArtist } = useContext(ArtistContext);
useIonViewWillEnter(() => {
api.get('artists?manager=1'))
.then(function(response) {
artist = response.data.items[0];
addArtist(artist);
window.location.href = '/artist/' + artist.id + '/tabs';
})
.catch(error => console.log(error));
});
If i log inside the addArtist method, i can see that the artist object is valid, but then if i log again the artists array, it has the initial value.
I have tried different methods and syntax like class componenets with state, functional components with useState... So i'm guessing my syntax is correct but i'm missing some key concept about React.
Its my first project with React, so i'm probably missing something, but i can not figure it out even after reading the context documentation.

You need to store your state/context object to localStorage, sessionStorage, cookies or server since accessing a different url path refreshes the page.
This link may help you store your state when you refresh the page.
How to maintain state after a page refresh in React.js?
Just encountered this today. Took me 2 days to resolve this lol

Related

Display firebase data with react

I'm new to react, so please give me some slack. I'm trying to extract data from a collection in firebase based on the url and display it on the page. My attempt is below:
import React, { useEffect, useState, map } from "react";
import { useParams } from "react-router-dom";
import { db } from "../../firebase";
import { doc, getDoc } from "firebase/firestore";
export default function UserPage() {
const { userUrl } = useParams();
const [user, setUser] = useState();
const docUserInfo = doc(db, "userinfo", userUrl);
useEffect(() => {
getDoc(docUserInfo).then((doc) => {
setUser(doc.data());
});
});
return (
<>
<div>
{user.map((user) => (
<div>{user.title}</div>
))}
</div>
</>
);
}
I get an error saying:
TypeError: Cannot read properties of undefined (reading 'map')
According to other posts here, this error suggests that my object (user) is not an array, and it doesn't seem to be. I tried using typeof to understand what type of object it is, but it just says "object". So I checked using Array.isArray(), which returns false, but here is where I get stuck.
Using setUser(doc.data().title) gives me what I want, sort of, but I want all the items inside the user array and don't want to create a useState for all of them. I also don't think there is an issue with useParams().
In addition, sometimes undefined is returned and sometimes this is done in an infinite loop, but I haven't figured out exactly when this happens.
Lastly, there are a lot of tutorials for exactly what I'm trying to do, but all of them are just displaying the fetched data in in the console (which I'm able to do), or they are using firebase<v9.
Your code loads a single document from Firestore, so there's no need to loop over multiple users as you'd do with the user.map that you have. Instead, just display the properties from your user state variable:
return (
<>
<div>
<div>{user.title}</div>
</div>
</>
);

Understanding Context in Svelte (convert from React Context)

I have a react app that uses ContextAPI to manage authentication and I am trying to implement a similar thing in Svelte. [Web Dev Simplified][1]
In Authenticate.js I have this:
import React, { useContext, useState, useEffect } from "react"
import { auth } from "../firebase"
const AuthCt = React.createContext()
export function Auth() {
return useContext(AuthCt)
}
export function AuthComp({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password)
}
function logout() {
return auth.signOut()
}
useEffect(() => {
const unmount = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unmount
}, [])
const value = {
currentUser,
login,
signup
}
return (
<AuthCt.Provider value={value}>
{!loading && children}
</AuthCt.Provider>
)
}
This context is used in other Login.js component like this:
import { Auth } from "./Authenticate"
const Login = () => {
const { currentUser, login } = Auth()
And in App.js I have:
import { AuthComp } from "./Authenticate";
function App() {
return (
<AuthComp>
<div> All others go here </div>
</AuthComp>
);
}
How do I achieve this in Svelte, particularly the Authenticate context?
I haven't been able to do much in Svelte as I don't know how to proceed from here. So far I have AuthComp.svelte. I don't know if I am doing the right thing.
<script>
import { getContext, setContext } from 'svelte';
import { auth } from '../firebase';
import { writable } from 'svelte/store';
let Auth = getContext('AuthCt')
setContext('Auth', Auth)
let currentUser;
let loading = true;
const unmount = auth.onAuthStateChanged(user => {
currentUser = user;
loading = false
});
function login(email, password) {
return auth.signInWithEmailandPassWord(email,password)
}
function logout() {
return auth.signOut()
}
const value = { currentUser, login, signUp }
</script>
<slot value={value}></slot>
Migrating from React Context to Svelte
Context in Svelte and React may seem similar, but they are actually used differently. Because at the core, Svelte's context is much more limited. But that's ok. In fact, it actually will make your code simpler to write and understand.
In Svelte, you have more tools at your disposal for passing data round your app (and keeping it in sync) than just context. Each one does pretty much one thing (making everything predictable), and they do it well. Of these, you have:
Context
Stores
Props
As someone who's recently switched from React to Svelte, I think I can help explain some of the differences between each of these and help you avoid some of my conceptual mistakes. I'll also go over some differences in life cycle methods, because if you used to use useEffect, you might feel very lost since Svelte doesn't have an equivalent API. Yet combining everything together in Svelte will make everything simple.
Context
Context in Svelte does one thing: pass data from a parent component to any children (not necessarily direct children). Unlike in React, context is not reactive. It is set once when the component mounts, and then will not be updated again. We'll get to "reactive context" in a second.
<!-- parent.svelte -->
<script>
import { setContext } from 'svelte'
setContext('myContext', true)
</script>
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const myContext = getContext('myContext')
</script>
Notice that context involves two things, a key and a value. Context is set to a specific key, then the value can be retrieved using that key. Unlike React, you do not need to export functions to retrieve the context. Both the key and value for the context can be anything. If you can save it to a variable, you can set it to context. You can even use an object as a key!
Stores
If you have data that needs to stay in sync in multiple places across your app, stores are the way to go. Stores are reactive, meaning they can be updated after they're created. Unlike context in either React or Svelte, stores don't simply pass data to their children. Any part of your app can create a store, and any part of your app can read the store. You can even create stores outside of Svelte components in separate JavaScript files.
// mystore.ts
import { writable } from 'svelte/store'
// 0 is the initial value
const writableStore = writable(0)
// set the new value to 1
writableStore.set(1)
// use `update` to set a new value based on the previous value
writableStore.update((oldValue) => oldValue + 1)
export { writableStore }
Then inside a component, you can subscribe to the store.
<script>
import { writableStore } from './mystore'
</script>
{$writableStore}
The dollar sign subscribes to the store. Now, whenever the store is updated, the component will rerender automatically.
Using stores with context
Now that we have stores and context, we can create "reactive context"(a term I just made up, but it works). Stores are great because they're reactive, and context is great to pass data down to the children components. But we can actually pass a store down through context. This makes the context reactive and the store scoped.
<!-- parent.svelte -->
<script>
import { setContext } from 'svelte'
import { writable } from 'svelte/store'
const writableStore = writable(0)
setContext('myContext', writableStore)
</script>
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const myContext = getContext('myContext')
</script>
{$myContext}
Now, whenever the store updates in the parent, the child will also update. Stores can of course do much more than this, but if you were looking to replicate React context, this is the closest you can get in Svelte. It's also a lot less boilerplate!
Using "reactive context" with "useEffect"
Svelte does not have an equivalent of useEffect. Instead, Svelte has reactive statements. There's a lot on these in the docs/tutorial, so I'll keep this brief.
// doubled will always be twice of single. If single updates, doubled will run again.
$: doubled = single * 2
// equivalent to this
let single = 0
const [doubled, setDoubled] = useState(single * 2)
useEffect(() => {
setDoubled(single * 2)
}, [single])
Svelte is smart enough to figure out the dependencies and only run each reactive statement as needed. And if you create a dependency cycle, the compiler will yell at you.
This means that you can use reactive statements to update stores (and hence update the context). Here, the valueStore will be update on every keystroke to the input. Since this store is passed down through context, any child can then get the current value of the input.
<script>
import { setContext } from 'svelte'
import { writable } from 'svelte/store'
// this value is bound to the input's value. When the user types, this variable will always update
let value
const valueStore = writable(value)
setContext('inputContext', valueStore)
$: valueStore.set(value)
</script>
<input type='text' bind:value />
Props
For the most part, props function exactly the same in React and Svelte. There are a few differences because Svelte props can take advantage of two-way binding (not necessary, but possible). That's really a different conversation though, and the tutorial is really good at teaching two-way binding with props.
Authentication in Svelte
Ok, now after all of that, let's look at how you'd create an authentication wrapper component.
Create an auth store
Pass the auth store down via context
Use Firebase's onAuthStateChanged to listen to changes in auth state
Subscribe to the auth store in the child
Unsubscribe from onAuthStateChanged when the parent is destroyed to prevent memory leaks
<!-- parent.svelte -->
<script>
import { writable } from 'svelte/store'
import { onDestroy, setContext } from 'svelte'
import { auth } from '../firebase'
const userStore = writable(null)
const firebaseUnsubscribe = auth.onAuthStateChanged((user) => {
userStore.set(user)
})
const login = (email, password) => auth.signInWithEmailandPassWord(email,password)
const logout = () => auth.signOut()
setContext('authContext', { user: userStore, login, logout })
onDestroy(() => firebaseUnsubscribe())
</script>
<slot />
<!-- child.svelte -->
<script>
import { getContext } from 'svelte'
const { login, logout, user } = getContext('authContext')
</script>
{$user?.displayName}
In Svelte, context is set with setContext(key, value) in a parent component, and children can access the value object with getContext(key). See the docs for more info.
In your case, the context would be used like this:
<script>
import { getContext, setContext } from 'svelte';
import { auth } from '../firebase';
import { writable } from 'svelte/store';
// you can initialize this to something else if you want
let currentUser = writable(null)
let loading = true
// maybe you're looking for `onMount` or `onDestroy`?
const unmount = auth.onAuthStateChanged(user => {
currentUser.set(user)
loading = false
});
function login(email, password) {
return auth.signInWithEmailandPassWord(email,password)
}
function logout() {
return auth.signOut()
}
const value = { currentUser, login, signUp }
setContext('Auth', value)
</script>
{#if !loading}
<slot></slot>
{/if}
Here, currentUser, login, and signup (not sure where that's coming from?) are set as context with setContext(). To use this context, you would probably have something like this:
<!-- App -->
<AuthComp>
<!-- Some content here -->
<Component />
</AuthComp>
<!-- Component.svelte -->
<script>
import { getContext } from 'svelte'
const { currentUser, login, signup } = getContext('Auth')
// you can subscribe to currentUser with $currentUser
</script>
<div>some content</div>
As written in the docs, context is not reactive, so currentUser is first converted into a store so it can be subscribed to in a child. As for the useEffect, Svelte has lifecycle functions that you can use to run code at different points, such as onMount or onDestroy.
If you're new to Svelte, their tutorial is very comprehensive with plenty of examples that you can refer back to.
Hope this helped!

How to properly passing data through functional component in ReactJS?

I am new to react and this is very confusing to me. Any help would be appreciated.
So I have an Axios Interceptor, making sure the user is authenticated, but that not the issue, the issue is the ".then()" part of the interceptor. So I am trying to pass "res" into my functional component "Profile" like below.
export function GetProfiles(history) {
axiosInstance(history)
.get('/profile')
.then((res) => <Profile userData={UserProfile(res)} />)
.catch((err) => console.log("err", err));
}
So this is how to write my "UserProfile(res)" function
function UserProfile(props) {
let data = {
firstName: props.data.firstName,
lastName: props.data.lastName,
email: props.data.email,
phone: props.data.phone,
};
return { data };
}
export default UserProfile;
If I do console.log(data) in "UserProfile" I get all the data I needed. So everything is working as intended. However, when I try to retrieve those same data in the "Profile" component I get "undefined". So this is how I write my "Profile" component
function Profile({ userData }) {
console.log(userData);
}
export default Profile;
Again, any help would very much appreciate, I am new to this so there is a very big chance I am doing it wrong. Please point me in the right direction.
When you are fetching data from an API, normally you'd assign the response (res) to a variable, that way you separate the View (The component structure) from the Data (The user info from the API). So, in your case, you'd have to:
Define a variable to store the user data.
After that, inside the getProfile function, assign the response to the variable.
And finally, pass the variable as a prop to your component.
You can use this code as an example:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
const [profileData, setProfileData] = useState();
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'yourapiurl/profile',
);
setProfileData(result.data);
};
fetchData();
}, []);
return (
<>
<Profile userData={profileData} />
</>
);
}
export default App;
In this example, I'm using React Hooks, so you do your API call inside the useEffect hook. I defined a variable called profileData, where I will store the data from the API. Inside the fetchData function, I call the method setProfileData, so all the data that you got from the API will be stored inside the profileData variable. Finally, you pass the profileData as a prop to your Profile Component, and it will update as soon as the data is fetched from your API.
I got all the information from this link:
https://www.robinwieruch.de/react-hooks-fetch-data
In case you are using Class Components and not React Hooks, the process is very similar, just instead of defining the variable like this:
const [profileData, setProfileData] = useState();
You'd have to define it as the state of your component:
state = {
profileData: []
}
More info about how to fetch data from an API using Axios in React Class Components in the following link:
https://www.digitalocean.com/community/tutorials/react-axios-react
I hope this info was useful.
Happy Hacking!
I think you're trying to write UserProfile as a helper function but instead it looks like a function component the way you have it. You could map data in the .then chain before passing it down to your Profile component i.e.
let userData = userProfile(res);
return <Profile userData={userData} />

React component state persist from firebase

I have a simple shopping site, with the data coming from a headless CMS system. I am using nextJS/React to build out the site. When a user adds items to the cart, I want to use firebase to persist the state, as the user navigates different pages. However, I lose all data in the state as well as firebase, when the user navigates to different pages. The main problem is I can't set the initial cart state to the firebase data. How do I solve this problem, so i can use the firebase data to persist cart state?
import React, { useState, useEffect } from 'react';
import Layout from './../components/Layout';
import contextCart from './../components/contextCart';
import firebase from '../data/firestore';
export default function MyApp({ Component, pageProps }) {
//any way to set the initial cart state to data from firebase???
const [ cart, setCart ] = useState([]);
const addToCart = (product) => {
setCart((prevCartState) => {
return [ ...prevCartState, product ];
});
firebase.database().ref().set(cart);
};
//componentDidUpdate => listen for cart changes and update the localStorage
useEffect(
() => {
firebase.database().ref().set(cart);
},
[ cart ]
);
//componentDidMount => run the effect only once and run cleanup code on unmount
useEffect(() => {
//effect code
firebase.database().ref().once('value').then(function(snapshot) {
setCart(snapshot.val() || []);
});
//returned function will be called on component unmount aka cleanup code
//return () => { /*any cleanup code*/ };
}, []);
return (
<contextCart.Provider value={{ cart: cart, addToCart: addToCart }}>
<Layout>
<Component {...pageProps} />
</Layout>
</contextCart.Provider>
);
}
The data is not updating because you are not listening to the changes in your code.
You specify to get the data once in the useEffect Hook , which only runs on the initial load.
To listen to changes you can implement it like this:
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount');
starCountRef.on('value', function(snapshot) {
updateStarCount(postElement, snapshot.val());
});
read more about it in the official firebase docs here: https://firebase.google.com/docs/database/web/read-and-write
I would recommend you to use a state-management library like Redux or even the Context API from React itself.
The reason for this is that firebase charge you for the traffic to your database (this is a bigger issue for the firestore db than the real-time db but still there are costs to consider).
With a state library, you can check if you need to fetch the data from firebase or if you already have what you need in your state.
Also if you can split your code up with the Provider Pattern, this makes it easier to maintain the logic while your application is growing.

Get state from display component

I have one fetch and one display .js file. However I am trying to figure out how to read the state. Of course as it's done now it's returned from the other .js file. But I would like to use the state that was set instead. How would I refactor to do this?
I would like to use the stateURL prop in the DataLoader.js
DataLoader.js
import React, { useState, useEffect } from 'react';
import useFetch from "./useFetch";
export default function DataLoader({stateURL}) {
const data = useFetch("/api");
// Should not be used
console.log("data", data);
const data2 = Object.keys(data).map(data => data);
console.log("data2", data2);
const data3 = data2.map(key => {
console.log("inside data3", key );
return data[key];
});
//This is empty
console.log("state", stateURL);
return (
<div>
<h1>Testing</h1>
<ul>
{Object.keys(data3).map(key => {
return <li>{data3[key].href}</li>;
})}
</ul>
</div>
);
}
useFetch.js
import { useState, useEffect } from "react";
export default function useFetch(url) {
const [stateURL, setStateURL] = useState([]);
console.log("url", url);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => setStateURL(data._links));
}, []);
console.log("stateURL", stateURL);
return stateURL;
}
That is not possible. The hooks can only be referred from the original creating component.
Why do you just use the fetch hook within the display file?
If you want to keep these two components separated:
To access the data, you have to share the data somehow to be accessible to your other components. There are several ways to do it:
Pass the data up into the parent component via a callback and pass that into the other child component.
Using a state management library like Redux or Mobx.
Using the context API to share data between components but that might not be the best way for this kind of data.
It depends on your setup and your needs. If only these two components ever need that data, pushing it into the parent works fine.
If there are other components, which maybe need that data, you might want to use a state management lib.

Resources