React useEffect Hook not Triggering on First Render with [] Dependencies - javascript

I'm getting data via an Axios GET request from a local API and trying to save the data in a Context Object.
The GET request works properly when I run it outside the Context Provider function. But when I put it within a UseEffect function with no dependencies - ie. useEffect( () => /* do something*/, [] )the useEffect hook never fires.
Code here:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [])
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I never see 'this does not' in the console (double and triple checked). I'm trying to initialise the context to an empty value at first, make the GET request on first render, and then update the context value.
I'd really appreciate any help on what I'm doing wrong.
EDIT - Where Context Provider is being rendered
import React from 'react';
import AppNavbar from "./Components/AppNavbar";
import ShoppingList from "./Components/ShoppingList";
import ItemModal from "./Components/ItemModal";
//IMPORTED HERE (I've checked the import directory is correct)
import ItemsContextProvider from "./ItemsContext";
import { Container } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css";
import './App.css';
function App() {
return (
<div className="App">
<ItemsContextProvider> //RENDERED HERE
<AppNavbar />
<Container>
<ItemModal />
<ShoppingList /> //CONSUMED HERE
</Container>
</ItemsContextProvider>
</div>
);
}
export default App;
I have it being consumed in another file that has the following snippet:
const {items, dispatch} = useContext(ItemsContext);
console.log(items, dispatch);
I see console logs showing the empty array I initialised outside the useEffect function in the Context Provider and also a reference to the dispatch function.

I had the same problem for quite a while and stumbled upon this thred which did not offer a solution. In my case the data coming from my context did not update after logging in.
I solved it by triggering a rerender after route change by passing in the url as a dependency of the effect. Note that this will always trigger your effect when moving to another page which might or might not be appropriate for your usecase.
In next.js we get access to the pathname by using useRouter. Depending on the framework you use you can adjust your solution. It would look something like this:
import React, { createContext, useReducer, useEffect } from 'react';
import rootReducer from "./reducers";
import axios from 'axios';
import { GET_ITEMS } from "./reducers/actions/types";
import { useRouter } from "next/router"; // Import the router
export const ItemsContext = createContext();
function ItemsContextProvider(props) {
const [items, dispatch] = useReducer(rootReducer, []);
const router = useRouter(); // using the router
console.log('this logs');
useEffect(() => {
console.log('this does not');
axios.get('http://localhost:27015/api/items')
.then(data => dispatch({type: GET_ITEMS, payload: data}))
}, [router.pathname]) // trigger useEffect on page change
return (
<ItemsContext.Provider value={{items, dispatch}}>
{ props.children }
</ItemsContext.Provider>
);
}
export default ItemsContextProvider;
I hope this helps anyone in the future!

<ItemsContextProvider /> is not being rendered.
Make sure is being consumed and rendered by another jsx parent element.

Related

Can't get map to render on react page

im trying to render a list of objects, when i try map on console it returns the information correctly, i just need to render the object name in a list, but everytime i reload nothing renders on the screen, i don't know what i'm doing wrong.
import React from "react";
import { useState } from 'react'
import Swal from 'sweetalert2'
import AuthService from "../services/auth.service";
import { FcInfo } from 'react-icons/fc'
import { AiFillEdit } from 'react-icons/ai'
import { FiDelete } from 'react-icons/fi'
import { Card, InputGroup, FormControl, Button, DropdownButton, Dropdown,Table} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import '../auth/login.css'
import solicitudeService from "../services/solicitude.service";
import edificeService from "../services/edifice.service";
import { render } from "#testing-library/react";
function MySolicitudes(){
var data=[]
solicitudeService.getAll().then(response =>{
data=response.data
data.map((val, key)=>{
console.log("TEST:",val.nombresolicitud,key);
});
console.log(data);
}).catch(e=>{
console.log(e);
});
return(
<div className="container">
<h3>Lista Espacios</h3>
<ul className="list-group">
{data && data.map((val,index) =>{
return( <li key={index}>
{val.nombresolicitud}
</li>);
})
}
</ul>
</div>
);
} export default MySolicitudes;
The problem is that data isn't part of the component's state, so reassigning it won't trigger a re-render.
Also, your code will currently call solicitudeService.getAll() on every render, which you probably don't want.
Try using the useEffect and useState hooks:
const [data, setData] = useState([])
useEffect(() => {
solicitudeService.getAll().then(response =>{
setData(response.data);
console.log(data);
}).catch(e=>{
console.log(e);
});
}, []);
This will call solicitudeService.getAll() once when the component is initially rendered, and then call setData which should trigger a re-render and give you the behavior you expect.
You can adjust the deps array for useEffect if you want to change when solicitudeService.getAll() is called.
data is not a state variable here and as such does not excecute a rerender of your view (your load of the data via the solicitudeService is not synchronos and as such the data is not there when the page renders).
Put the data in a useState hook and call the setter inside of youre solicitudeService.
Something like:
const [data, setData] = useState()
solicitudeService.getAll().then(response =>{
setData(response.data)
}).catch(e=>{
console.log(e);
});

React, getting Error: Invalid hook call. Hooks can only be called inside of the body of a function component

Can anyone help me with React Hooks basics, I am relatively new and couldn't find proper help online
import React from 'react'
import { auth, provider } from "../../../firebaseSetup";
import { useNavigate } from "react-router-dom"
const GoogleAuth = async() => {
const navigate = useNavigate()
auth.signInWithPopup(provider).then(() => {
navigate('/home');
}).catch((error) => {
console.log(error.message)
})
}
export default GoogleAuth
I get error on const navigate = useNavigate() saying:
Error: Invalid hook call. Hooks can only be called inside of the body of a function component
What they want for useNavigate (and all hooks) is to be called only at the top level of a React component or a custom hook.
Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns.
See Rules of Hooks for more.
A solution to your problem could be calling const navigate = useNavigate() in the component where you will use GoogleAuth, and pass navigate as parameter.
As an example like so:
import React from 'react'
import { auth, provider } from "../../../firebaseSetup";
import { useNavigate } from "react-router-dom"
const GoogleAuth = async(navigate) => {
auth.signInWithPopup(provider).then(() => {
navigate('/home');
}).catch((error) => {
console.log(error.message)
})
}
export default GoogleAuth
import GoogleAuth from "GoogleAuth";
const App = ()=>{
/*
here at the top level, not inside an if block,
not inside a function defined here in the component...
*/
const navigate = useNavigate();
useEffect(()=>{
GoogleAuth(navigate)
},[])
return <div></div>
}
export default App;

How to pass state/data from one component to another in React.js (riot api specifically)

I am trying to pull information from one component's API call to then use that data in another API call in a separate component. However, I am unsure how to export and use the data from the first API call in the second component.
App.js
import './App.css';
import FetchMatch from './fetch-match/fetch.match';
import FetchPlayer from './fetch-player/fetch.player';
function App() {
return (
<div className="App">
<h1>Hello world</h1>
<FetchPlayer></FetchPlayer>
<FetchMatch></FetchMatch>
</div>
);
}
export default App;
fetch.player then makes the first API call to get a users specific ID which will be used in the second API call too fetch that users match history.
fetch.player.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const FetchPlayer = () => {
const [playerData, setPlayerData] = useState([]);
const userName = 'users name';
const userTagLine = '1234';
const apiKey = '???';
useEffect( () => {
axios.get(`https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${userName}/${userTagLine}?api_key=${apiKey}`)
.then(response => {
console.log(response.data)
setPlayerData([response.data])
})
.catch(error => console.log(error))
}, []);
return (
<div>
{playerData.map( data => (
<div>
<p>{data.puuid}</p>
<p>{data.gameName}#{data.tagLine}</p>
</div>
))}
</div>
)
}
export default FetchPlayer;
not much here but just in case...
fetch.match.js
import React, { useState } from 'react';
// Somehow take in the puuid set in the state of fetch.player to make a second API call below
const FetchMatch = () => {
const [matchData, setMatchData] = useState([]);
return (
<div>
// players match list goes here
</div>
)
}
export default FetchMatch;
I am unsure if I should make a separate function instead which would allow me to create consts to handle both API calls in a single file. Or if there is a way to pass the state from fetch.player as a prop to fetch.match from App.js. I have tried to do the former but it either doesn't work or I am messing up the syntax (most likely this)
If you render both component parallelly in a parent component, they are called sibling components.
Data sharing in sibling components can be done by multiple ways (Redux, Context etc) but the easiest and simplest way (the most basic way without 3rd party API) involves the use of parent as a middle component.
First you create the state in the parent component and provide it as props to the child component which need the data from its sibling (in your case is FetchMatch).
import React from 'react';
import './App.css';
import FetchMatch from './fetch-match/fetch.match';
import FetchPlayer from './fetch-player/fetch.player';
function App() {
const [data,setData] = React.useState();
return (
<div className="App">
<h1>Hello world</h1>
<FetchPlayer></FetchPlayer>
<FetchMatch data={data} ></FetchMatch>
</div>
);
}
export default App;
Provide the function to setData as a props to the child component which will fetch the initial API (in your case is FetchPlayer)
<FetchPlayer onPlayerLoad={(data) => setData(data)} />
Then, in that child component when you finish calling the API and get the result, pass that result to the onPlayerLoad function which will call the setData function with the result as parameters. It will lead to state change and re-rendering of the second FetchMatch component feeding the props data with API results.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const FetchPlayer = ({onPlayerLoad}) => {
const [playerData, setPlayerData] = useState([]);
const userName = 'users name';
const userTagLine = '1234';
const apiKey = '???';
useEffect( () => {
axios.get(`https://americas.api.riotgames.com/riot/account/v1/accounts/by-riot-id/${userName}/${userTagLine}?api_key=${apiKey}`)
.then(response => {
console.log(response.data)
setPlayerData([response.data])
onPlayerLoad(response.data)
})
.catch(error => console.log(error))
}, []);
return <></>;
Coming to FetchMatch, you will have the data in its second rendering.
import React, { useState } from 'react';
// Somehow take in the puuid set in the state of fetch.player to make a second API call below
const FetchMatch = ({data}) => {
const [matchData, setMatchData] = useState([]);
//console.log(data);
return (
<div>
// players match list goes here
</div>
)
}
export default FetchMatch;
Now, you can do whatever you want with the shared data in second component which in your case is trigger match API. 🎉

How to fix Too many re-renders. React limits the number of renders to prevent an infinite loop

I am new to react and recently i got into this problem and i dont know how to solve it.
it says Too many re-renders. React limits the number of renders to prevent an infinite loop. Hows it infinte loop? is it beacuase of on("value")?
import React from "react";
import fire from "./firebase";
import firebase from "firebase"
import { useState } from "react"
const UserPage = ({ match }) => {
const [user, setUser] = useState(null)
const { params: { userId } } = match;
var userName;
console.log(userId)
firebase.database().ref("users/"+userId+"/praivate/login credentials").on("value", (snapshot)=>{
setUser(snapshot.val().userName)
})
return(
<>
<h1>Hey {user}</h1>
</>
)
}
export default UserPage
Plz help me to fix it, thank you.
You should do your Firebase staff inside a lifecyle method.As your working with functionnal components you can use the useEffect hook:
import React from "react";
import fire from "./firebase";
import firebase from "firebase"
import { useState } from "react"
const UserPage = ({ match }) => {
const [user, setUser] = useState(null)
const { params: { userId } } = match;
useEffect(()=>{
//Put your Firebase staff here
},[])
return(
<>
<h1>Hey {user}</h1>
</>
)
}
export default UserPage
I dont know what you're trying to achieve, but inside you <h1>{user}</h1> i think that {user} is an object so if you want to access a specific attribute you can do something like <h1>{user.attributeName}</h1>.
I hope that it helped

use NextRouter outside of React component

I have a custom hook that will check whether you are logged in, and redirect you to the login page if you are not. Here is a pseudo implementation of my hook that assumes that you are not logged in:
import { useRouter } from 'next/router';
export default function useAuthentication() {
if (!AuthenticationStore.isLoggedIn()) {
const router = useRouter();
router.push('/login');
}
}
But when I use this hook, I get the following error:
Error: No router instance found. you should only use "next/router" inside the client side of your app. https://err.sh/vercel/next.js/no-router-instance
I checked the link in the error, but this is not really helpful because it just tells me to move the push statement to my render function.
I also tried this:
// My functional component
export default function SomeComponent() {
const router = useRouter();
useAuthentication(router);
return <>...</>
}
// My custom hook
export default function useAuthentication(router) {
if (!AuthenticationStore.isLoggedIn()) {
router.push('/login');
}
}
But this just results in the same error.
Is there any way to allow routing outside of React components in Next.js?
The error happens because router.push is getting called on the server during SSR on the page's first load. A possible workaround would be to extend your custom hook to call router.push inside a useEffect's callback, ensuring the action only happens on the client.
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export default function useAuthentication() {
const router = useRouter();
useEffect(() => {
if (!AuthenticationStore.isLoggedIn()) {
router.push('/login');
}
}, []);
}
Then use it in your component:
import useAuthentication from '../hooks/use-authentication' // Replace with your path to the hook
export default function SomeComponent() {
useAuthentication();
return <>...</>;
}
import Router from 'next/router'
create a HOC which will wrap your page component
import React, { useEffect } from "react";
import {useRouter} from 'next/router';
export default function UseAuthentication() {
return () => {
const router = useRouter();
useEffect(() => {
if (!AuthenticationStore.isLoggedIn()) router.push("/login");
}, []);
// yous should also add isLoggedIn in array of dependancy if the value is not a function
return <Component {...arguments} />;
};
}
main component
function SomeComponent() {
return <>...</>
}
export default UseAuthentication(SomeComponent)

Categories

Resources