React Context outside a React Component or alternatives - javascript

I have different endpoints defined in a file called endpoints.ts. Each of those endpoints receive a token (which is used later in Authentication header).
endpoints.ts
const getAllObjects = (token) => fetch(...);
const getSingleObject = (id, token) => fetch(...);
The problem
Every time I use call one of those endpoints from a React component, I have to pass the token (which is saved in React context) like this:
const user = await getSingleObject(id, context.user.token);
I find that very repetitive since I have multiple requests through my application.
What I have tried so far
I tried to simplify endpoints.ts by making it have access to React Context. I wanted to do something like this:
const context = useContext(context);
const token = context.user.token
const getAllObjects = () => fetch(...);
const getSingleObject = (id) => fetch(...);
However, that is not allowed because edpoints.ts does not have a React component so, it is not a valid Consumer for React Context.
Can you suggest another solution?

Rewrite your endpoints.ts to a custom hook using useContext:
const getAllObjects = () => fetch();
const useFetchFunctions = () => {
const context = useContext(context);
const token = context.user.token;
// Can be memoized by token value using React.useCallback
const getSingleObject = (id) => fetch(id, token);
return { getSingleObject, getAllObjects };
};
And use it within context consumers (so useContext will be valid).

Related

Why does my react function function iterate twice?

I am currently trying out a project with the PokeAPI. And have used his guide for help. I can't get rid of the problem that the function iterates twice when called in the useEffect.
When I run the following code with the getAllPokemons in the useEffect
const PokeListPage = () => {
const [layoutToggle, setLayoutToggle] = useState(false);
const [allPokemons, setAllPokemons] = useState([]);
const [loadPoke, setLoadPoke] = useState(
"https://pokeapi.co/api/v2/pokemon?limit=20"
);
useEffect(() => {
getAllPokemons();
console.log(allPokemons);
}, []);
const getAllPokemons = async () => {
const res = await fetch(loadPoke);
const data = await res.json();
setLoadPoke(data.next);
function createPokemonObject(result) {
result.forEach(async (pokemon) => {
const res = await fetch(
`https://pokeapi.co/api/v2/pokemon/${pokemon.name}`
);
const data = await res.json();
setAllPokemons((currentList) => [...currentList, data]);
});
}
createPokemonObject(data.results);
console.log(allPokemons);
};
I get doublets of the first 20 objects in allPokemons. See output:
enter image description here
But when I remove the function and uses a button to trigger the function it behaves as expected. Which means that the function populates the allPokemon array with one object per pokemon. See output.
enter image description here
I have tried everything from copying entire files from other repositories and with an accuracy I didn't knew I had followed different tutorials but the problem remains. Does anyone know why?
It's bcz, you are rendering your app into a React. Strict mode component that runs specific functions and methods twice as a way to help you detect unintentional side effects. Since the side-effect is a state update, this triggers a rerender.
Use a useEffect to run the effect once when the component mounts.

How to call useRouter inside useFetch's onFetchError by VueUse?

I need to create a custom fetch composable from VueUse using createFetch() and I want to check if a request returns 401 status, I'd like the route to be redirected to the login route.
export const useApiFetch = createFetch({
baseUrl: import.meta.env.VITE_API_BASE_URL,
options: {
beforeFetch({ options }) {
const { user } = useSessionStore()
if (user)
options.headers.Authorization = `Basic ${user.user_id}:${user.password}`
return { options }
},
onFetchError(response) {
const route = useRoute()
const router = useRouter()
if (route.name !== 'login' && response.status === 401)
return router.push('/login')
}
}
})
But everytime it hits the error, useRoute and useRouter are undefined, and yes.. I have checked that it runs in setup
<script setup>
const submit = async () => {
const { error, data } = await useApiFetch('/login').post(form)
}
</script>
Did I miss something or is there a better way to do this? thanks
Vue composables are primarily expected to be called in setup block. Other usages depend on their implementation and needs to be confirmed. The main restriction is that a composable is linked to specific component instance, in this case useRouter uses provide/inject to get router instance through component hierarchy; this often can be be deduced without checking the actual implementation.
It's possible to directly import router instance instead of using useRouter but this may result in module circular dependencies and this may not be work for other composables.
createFetch wasn't designed for this usage and needs to be wrapped with custom composable that guarantees that other composables will be called in time:
let useApiFetchFn;
const useApiFetch = (...args) => {
if (!useApiFetchFn) {
const sessionStore = useSessionStore()
const route = useRoute()
const router = useRouter()
useApiFetchFn = createFetch(...)
}
return useApiFetchFn(...args);
}
Whether it's correct to cache the result to useApiFetchFn depends on the implementation, in this case it's acceptable. At this point it may be more straightforward to use useFetch directly and compose the options similarly to how createFetch does that, most of its code is dedicated to TS support and variadic arguments that may not be needed in this case.

Call an async function with javascript

I am trying to call an async function but I am getting an error
getUsersList(db).then is not a function
this is my code
async function getUsersList(db) {
const userCol = collection(db, 'Users');
const userSnapshot = await getDocs(userCol);
const tempUserList = userSnapshot.docs.map(doc => doc.data());
return tempUserList;
}
function App() {
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const auth = getAuth(app);
var currentUser = auth.currentUser;
if(currentUser != null){
getUsersList(db).then((value) => {
console.log(value);
});
I also tried using await getUsersList but got the following error
Unexpected reserved word 'await'
So what I assume you're trying to do here const userSnapshot = await getDocs(userCol); is fetch some data that is then going to be used in your react component to render something (Maybe there's a fetch request in getDocs ?)
There is no return in your react component (but that's not what's causing your issue).
As it is it won't work using await since App() isn't an async function BUT you can't make it an async function since this is a standard react component.
What do you want to happen whilst waiting for the data to be fetched (= whilst your promise is pending) ? If you're happy to display nothing, why not just remove await before getDocs() ?
For more on this topic :
React: async and await not working with fetch
React Hooks: how to wait for the data to be fetched before rendering

React : make backend API call when user is authenticated, use LocalStorage if not

I'm working on a small note taking app with React and Node.js (Express). If the user is authenticated I make API calls to the backend to fetch, create, update, delete notes persisted in a MongoDB database. If he's not, the notes are stored in localStorage. I have an AuthContext with login, logout and signup functions.
I can know if the user is loggedIn with my useAuth() custom hook in my AuthContext :
const { user } = useAuth();
And I have a separate file to make the API calls that I use in my components (getNotes, createNotes ...)
I fetch my notes in the useEffect hook
React.useEffect(() => {
const notes: Note[] = getNotes();
setNotes(notes);
}, []);
And I render my notes like this (simplified)
{notes.length > 0 && (
<ul>{notes.map(renderNote)}</ul>
)}
const renderNote = (note) => {
return (
<Note note={note} />
);
};
My question is what would be a good practice to implement the different behaviors (API calls or localStorage) ?
I can add a parameter isLoggedIn to the functions and add an if statement inside the function like this (simplified version) :
const getNotes = (isLoggedIn) => {
if (isLoggedIn) {
return notes = fetch("/notes")
} else {
return notes = localStorage.getItem("notes")
}
}
But this does not look like something clean to do if I have do to this in every function.
Thanks in advance for your help
Here's something you could do. I think I'd suggest you create the idea of some store that implements a simple getter/setter interface, then have your useAuth hook return the correct store depending on the auth state. If authenticated, then your hook returns the remote store. If not, then it returns the local storage store. But your store looks the same to your component no matter whether it's a local or remote store.
Now your code can just call get/set on the store and not care about where your info is stored or even whether the user is logged in. A main goal is to avoid having a lot of if (loggedIn) { ... } code all over your app.
Something like...
const useLocalStorageStore = () => {
const get = (key) => {
return localStorage.getItem(key);
};
const set = (key, value) => {
// I append 'local' here just to make it obvious the
// local store is in use in this example
localStorage.setItem(key, `${value} local`);
};
return { get, set };
};
// This contrived example uses localStorage too to make my example easier,
// but you'd add the fetch business to your get/set methods
// here in this remote store.
const useRemoteStore = () => {
const baseUrl = "http://localhost/foo/bar";
const get = async (key) => {
//return fetch(`${baseUrl}/${key}`);
// really should fetch here, but for this example use local
return localStorage.getItem(key);
};
const set = async (key, value) => {
// I append 'remote' here just to make it obvious the
// remote store is in use in this example
localStorage.setItem(key, `${value} remote`);
};
return { get, set };
};
const useAuth = () => {
// AuthContext is your source of loggedIn info,
// however you have it available in your app.
const { login, logout, loggedIn } = React.useContext(AuthContext);
const authedStore = useRemoteStore();
const unauthedStore = useLocalStorageStore();
const store = loggedIn ? authedStore : unauthedStore;
return { login, logout, loggedIn, store };
};
At this point, the store has all you need to get or set key values. Then you can use it in your components with...
const MyComponent = () => {
const { loggedIn, login, logout, store } = useAuth();
const setNotesValue = async (value) => {
// Your store handles communicating with the correct back end.
await store.set("notes", value);
};
const getNotesValue = async () => {
// Your store handles communicating with the correct back end.
const value = await store.get("notes");
};
return (
<div>Your UI...</div>
);
};
Here's a sandbox to demo this: https://codesandbox.io/s/gallant-gould-v1qe0

How to re-render react component depends on a file?

I have a file that stores an array of objects. I have a component that fetches data from this file then render the list. The file could be updated somewhere else, I need the component to be updated if the file is modified. I have following code example
const header = () => {
const [list, setList] = useState([]);
// fetch
useEffect(() => {
const loadList = async () => {
const tempList = await getList("/getlist"); // get call to fetch data from file
setList(tempList);
};
loadList ();
}, [list]);
// function to render content
const renderList = () => {
return list.map(obj => (
<div key={obj.name}>
{obj.name}
</div>
));
};
return (
<div>{renderList()}</div>
)
}
// get call
router.get('/getlist',
asyncWrapper(async (req, res) => {
const result = await getList();
res.status(200).json(result).end();
})
);
const getList= async () => {
const list = JSON.parse(await fs.readFile(listPath));
return list;
}
Code has been simplified. If I remove the list from useEffect, then it will only render once and will never update unless I refresh the page. If I include list there, loadList() will get called constantly, and component will get re-rendered again and again. This is not the behavior I want. I am just wondering without making header component async component, how do I only re-render this component when the file is changed?
Thank you very much.
There are two approaches you can take to this:
Polling
Request the URL on an interval, and clear it when the component is unmounted.
Replace loadList () with:
const interval = setInterval(loadList, 60000); // Adjust interval as desired
return () => clearInterval(interval)
Make sure the cache control headers set in the response to /getlist don't stop the browser from noticing updates.
Server push
Rip out your current code to get the data and replace it with something using websockets, possibly via Socket.IO. (There are plenty of tutorials for using Socket.io with React that can be found with Google, but its rather too involved to be part of a SO answer).

Categories

Resources