Am trying to make a script that easy to edit for users from the back-end, so they won't need to re-build the react app each time they edit something (such as color, phone number etc)
so i make a call to the back-end by axios
let config={};
(async () => {
try {
config = await axios.get("http://localhost:8000/settings");
config = config.data;
} catch (e) {
alert(e);
}
})();
const App = () => {
let show;
///
const [name, setName] = useState("");
const [inchat, setInchat] = useState(false);
const { t, i18n } = useTranslation();
///settings
const [direction, setDirection] = useState("text-left");
const [socket] = useState(openSocket(config.url || '/'));
console.log(socket)
const [settings] = useState(config);
as you see, after loading the config file from back-end, am using it here in front-end.
the problem is that sometimes the App component load first, and the script throw error of config undefind, how can i make the script http request lunch first, i tried to put the http request inside a useEffect hook but still the same problem.
thank you
This is how I usually implement things when it comes to http requests.
This is assuming that you're not using Server Side Rendered (SSR) service like Next.js, where you want the data to be fetched before the page is even rendered, then you would use a different approach.
Otherwise you could show a loading animation or just show a blank screen until the data is loaded.
Click the "Run code snippet" button below to see it work.
// main.js
const { useState, useEffect } = React;
const App = () => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
/*
StackOverflow not letting me do async/await so doing a promise
*/
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => {
setData(json);
setLoading(false);
})
.catch(error => {
console.log('ERROR', error);
setError(error);
setLoading(false);
});
}, []);
return <div>
{loading
? <p>Loading...</p>
: <div>
{error
? <div>{JSON.stringify(error)}</div>
: <div>Loaded <br />{JSON.stringify(data)}</div>
}
</div>}
</div>
}
ReactDOM.render(<App />, document.querySelector('#root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Since the call to your API is async you will need to wait for the call to be finished to show your app, useEffect is a good way to do it, but you will need to make some changes in your code to work.
I see that you are handling all the state variables separately but some of them depend on each other, for this cases is better to use an object so you can be sure all the properties are updated at the same time and also to know if you have already the needed data to show your app
// show your app until this is not null
const [state, setState] = useState(null);
// set all the state variables at the same time
useEffect(() => { axios.get(url).then(config => setState(config)) })
if(!state) return <div>Loading...</>
return <div>App</div>
Another alternative is to use another state variable to control if you should show your app or not
// all your state variables
const [state, setState] = useState(null);
.
.
const [isLoading, setIsLoading] = useState(false);
// set all the state variables at the same time
useEffect(() => {
setLIsLoading(true);
axios.get(url).then(config => {
setIsLoading(false);
setState(config);
)
})
if(isLoading) return <div>Loading...</>
return <div>App</div>
Related
my question is if it is possible to trigger useEffect with a variable from outside the component.
In my case i have this main component that has the useEffect responsible to update info every time the variable "refresh" changes.
function Main() {
const [data, setData] = useState([])
const [refresh, setRefresh] = useState(false)
useEffect(async () => {
await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(res => {
setData(res.data);
})
}, [refresh]);
And then i have a function that i can invoke inside the component or child that triggers the useEffect, updating the data.
const refreshData = () => setRefresh(!refresh);
So far so good, it works as i wanted but now i needed to export this function to use in a component not related to this one, but i know that you cannot export a function declared inside a component.
So my idea was to create this same function outside the component, like so:
let refreshOutside = false;
export const refreshMainFromOutside = () => {
refreshOutside = !refreshOutside;
}
So now i can add the variable "refreshOutside" to the useEffect, like so:
useEffect(async () => {
await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(res => {
setData(res.data);
})
}, [refresh, refreshOutside]);
But if i import it in other component and invoke the method it does not trigger the useEffect, i am kinda new to react but i think its because the component is not re-rendering.
Is there any solution that might work on my case?
Thanks in advance.
I suggest you put your hooks inside another file for example useComponent.js and export your refreshData as const inside it, then use that hook inside any component you wish:
const useComponent = () => {
const [data, setData] = useState([])
const [refresh, setRefresh] = useState(false)
useEffect(async () => {
await axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(res => {
setData(res.data);
})
}, [refresh, refreshOutside]);
const refreshData = () => setRefresh(!refresh);
export { refreshData }
}
export default useComponent
import the hook inside any component then destructure functions and use them:
import useComponent from '../hooks/useComponent'
const MyComponent = () => {
const { refreshData } = useComponent()
return <button onClick={refreshData}>Click to refresh!</button>
}
export default MyComponent
As mentioned in the comment, you can simply define a separate function to fetch the data (and memoize it with the useCallback() hook), then you can use that function wherever you want in your Main component and in any Child component to whom you pass it as prop.
Maybe an example would make it easier to understand:
const Main = () => {
const [data, setData] = React.useState([]);
const updateData = React.useCallback((startIndex) => {
/*
axios.get(`${process.env.REACT_APP_SERVER_URL}/api/data/`)
.then(result => {
setData(result.data);
})
*/
axios.get('https://jsonplaceholder.typicode.com/posts/')
.then((result) => {
console.log('fetching data...');
setData(result.data.slice(startIndex, startIndex + 5));
console.log('... data updated');
});
}, [setData]);
React.useEffect(() => {
updateData(0); // fetch & update `data` when the Main component mounts
}, [])
return (
<div>
<h1>Main</h1>
{
data.length > 0
? <ul>{data.map(item => <li>{item.title}</li>)}</ul>
: <p>'There are no data'</p>
}
<button
onClick={() => updateData(5)} // fetch & update `data` on request, on button click
>Refresh from Main</button>
<Child handleClick={updateData} />
</div>
)
}
const Child = ({handleClick}) => {
return (
<React.Fragment>
<h1>Child</h1>
<button
onClick={() => handleClick(10)} // fetch & update `data` on request, on button click
>Refresh from Child</button>
</React.Fragment>
)
}
ReactDOM.render(<Main />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.js"></script>
<script crossorigin src="https://unpkg.com/react#17/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.production.min.js"></script>
<div id="app"></div>
P.S. in the above example I used a parameter (startIndex) for the updateData() function just to keep the data state short and make it change when different buttons are clicked (because the API I used always returns the same data). In a real case use you are unlikely to do something like that because you can implement pagination on API side (if needed) and your API response is supposed to change over time (or you would not need a way to refresh your data).
Anyway, the point is that what I did inside the updateData() function body is mostly irrelevant; the main take away of the snippet is supposed to be how you can handle a function that needs to be called inside hooks, inside the main component and by child components.
This question relates a lot with loading and fetching. So I have this react components that loads comments from an API
function App (){
const [com, setCom] = useState([])
const [isLoading, setLoading] = useState(true)
useEffect(() =>{
fetch('https://jsonplaceholder.typicode.com/comments?postId=5').then(ddd => ddd.json())
.then(data => {
setCom(data)
setLoading(false)
})
})
const coms = com.map(data => <h3>{data.body} <br/></h3>)
if(isLoading){
return <h1>Loading</h1>
}
else if(isLoading === false){
return (
<div className="con">
{coms}
</div>
)
}
}
so in this components I have two states one to store the comments and other to store a loading value that will change after it's done fetching the state in the useEffect.
The problem with this code is that let's say the server went down or my internet went out, even if it comes back this component is going to stay in the loading phase forever until the user refreshes the page manually. So how do I make the components re-fetch the data or rather just refresh the components ?.
Thank you in advance
Here are a few improvements on how you can handle above logic.
function App() {
const [com, setCom] = useState([]);
const [isLoading, setisLoading] = useState(false); //Set initial value to false to avoid your component in loading state if the first call fails
const refreshTime = 2000 //How frequently you want to refresh the data, in ms
const fetchComments = async () => {
setisLoading(true) //set to true only when the api call is going to happen
const data = await fetch('https://jsonplaceholder.typicode.com/comments?postId=5').then(ddd => ddd.json()).then(data => {
if(Array.isArray(data)) setCom(data);
})
setisLoading(false); //make sure to set it to false so the component is not in constant loading state
}
useEffect(() => {
const comInterval = setInterval(fetchComments, refreshTime); //This will refresh the data at regularIntervals of refreshTime
return () => clearInterval(comInterval) //Clear interval on component unmount to avoid memory leak
},[])
const coms = com.map(data => <h3>{data.body} <br/></h3>)
if(isLoading){
return <h1>Loading</h1>
}
else if(isLoading === false){
return (
<div className="con">
{coms}
</div>
)
}
}
You could just use setInterval() to update the state with fetched data every second or however long is required. Here is an example:
const [fetchedData, setFetchedData] = useState(/* data type */);
setInterval(() => setFetchedData(/* call to the method */), 1000);
This way, the component will fetch data every second and re-render the component.
Keep in mind though, this should ONLY be used if you know you are going to get an update every so often. If you are uselessly re-rendering and re-fetching data, that will be a constant hinderance towards performance.
I keep this error Undefined is not an object(evaluating ....), I find it funny, because I get the object when i make a correction and it refreshes, but when i open a app at first, i get the error, So i delete the part that has the error then the app loads, i then bring back the delete part, it will show correctly. Please help, Im just learning react.
Im not sure but i think there is a method that makes my app wait when it fetches the data, then its loads, rather than just load at first nonetheless...
This is my code please.
function ExploreScreen() {
const getListingsApi = useApi(client.get(endpoint));
useEffect(() => {
getListingsApi.request();
}, []);
return ({getListingsApi && <Text>{getListingsApi.data[0].latitude}</Text>}) -this is the part i delete and bring back to ake it display.
I removed the parts that are working. This is just a brief version of the code, below is my useAPI hook
import { useState } from "react";
export default useApi = (apiFunc) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const request = async (...args) => {
setLoading(true);
const response = await apiFunc(...args);
setLoading(false);
setError(!response.ok);
setData(response.data);
return response;
};
return { data, error, loading, request };
};
It's hard without a reproducible example, but I would guess that data[0] might be the culprit:
return ({getListingsApi && <Text>{getListingsApi.data[0].latitude}</Text>})
The collection is being initialized to [] (in the hook via const [data, setData] = useState([]);) which means it won't have a first element.
This would explain why adding it later, would not cause an exception, since at that point, the data will already be loaded and the array will have been initialized.
Consider trying something like this:
return (
{getListingsApi && getListingsApi.data.length > 0 && (
<Text>{getListingsApi.data[0].latitude}</Text>
)}
)
Okay, so my problem is that I'm trying to modify an array after fetching JSON and storing as an array with hooks. How would I go about doing that?
I tried using another .then and calling the function I want to call, but when logging the array to the console in the function it just gives me an "cannot read property of description undefined error" Any help? Thanks!
const [repos, setRepos] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
function updateDates() {
var i;
console.log(repos[1].description);
}
// fetch github repo sorted by updated date and store in repos
useEffect(() => {
fetch("https://api.github.com/users/benngagne/repos?sort=updated")
.then(res => res.json())
.then(
(result) => {
setRepos(result);
setIsLoaded(true);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
// .then(() => {
// updateDates();
// })
}, [])
Updating state is asynchronous, and you are trying to access updated state data immediately after state updating function(setRepos()).
useEffect hook is what you should be looking for. Set dependency array of useEffect to your state, so it will only work when your state changes. There, you can do your manipulations of your new state, do functions etc.
useEffect(() => {
if(repos){ //checking if state is not null; to not catch initial state.
//Do your functions, calculations etc..
}
},[repos]);
how are you?
Your code has an error in your fetch, I'm adding those corrections and also a full class showing how to rander the incomming data. btw I just showing the full_name property from the response, but you can also grab all, like:
const {full_name,description} = repo;
Here's the full class:
import React, {useState, useEffect} from 'react'
function App() {
const [repos, setRepos] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [error, setError] = useState(null);
// fetch github repo sorted by updated date and store in repos
useEffect(() => {
fetch("https://api.github.com/users/benngagne/repos?sort=updated")
.then(res => res.json())
.then(result => {
setRepos(result);
setIsLoaded(true);
})
.catch((error) => {
setIsLoaded(true);
setError(error);
})
}, [])
return (
<div>
{
repos.map((repo, idx) => {
const {full_name} = repo;
return <div>{full_name}</div>
})
}
</div>
)
}
export default App
After a huge amount of trial and error for a complex webGL project I have landed on a solution that will reduce the amount of re-engineering working, threejs code (from another developer) and, as this project is extremely time restrained, reduce the amount of time needed. It's also worth noting my experience of this is limited and I am the only developer left on the team.
The project current accepts a large array of random user data, which is exported from a js file and then consumed here...
import Users from "./data/data-users";
class UsersManager {
constructor() {
this.mapUserCountries = {};
}
init() {
Users.forEach(user => {
const c = user.country;
if (!this.mapUserCountries[c])
this.mapUserCountries[c] = { nbUsers: 0, users: [] };
this.mapUserCountries[c].nbUsers++;
this.mapUserCountries[c].users.push(user);
});
}
getUsersPerCountry(country) {
return this.mapUserCountries[country];
}
}
export default new UsersManager();
Here is my fetch request..
import { useState, useEffect } from "react";
const FetchUsers = () => {
const [hasError, setErrors] = useState(false);
const [users, setUsers] = useState({});
async function fetchData() {
const res = await fetch(
"https://swapi.co/api/planets/4/"
);
res
.json()
.then(res => setUsers(res))
.catch(err => setErrors(err));
}
useEffect(() => {
fetchData();
}, []);
return JSON.stringify(users);
};
export default FetchUsers;
I have run into lots of issues as the UserManager is a class component and if I import my fetchUsers into this file, call it and save it to a variable like so const Users = fetchUsers(); it violates hooks.
I want to be able to return a function that will return my users from the database as an array.
That will then be able to be passed into the UserManager in the same way the hard coded data is and mapped over to be actioned by LOTS of other files.
I've mocked up a small codesandbox with what the flow would be ideally but I know I need a solution outside of hooks...
https://codesandbox.io/s/funny-borg-u2yl6
thanks
--- EDIT ---
import usersP from "./data/data-users";
class UsersManager {
constructor() {
this.mapUserCountries = {};
this.state = {
users: undefined
};
}
init() {
usersP.then(users => {
this.setState({ users });
});
console.log(usersP);
this.state.users.forEach(user => {
const c = user.country;
if (!this.mapUserCountries[c])
this.mapUserCountries[c] = { nbUsers: 0, users: [] };
this.mapUserCountries[c].nbUsers++;
this.mapUserCountries[c].users.push(user);
});
}
getUsersPerCountry(country) {
return this.mapUserCountries[country];
}
}
export default new UsersManager();
console.log (UsersManager.js:16 Uncaught TypeError: Cannot read property 'forEach' of undefined
at UsersManager.init (UsersManager.js:16)
at Loader.SceneApp.onLoadingComplete [as callback] (App.js:39)
at Loader.onAssetLoaded (index.js:20)
at index.js:36
at three.module.js:36226
at HTMLImageElement.onImageLoad)
I fixed your sandbox example.
You cannot load the users synchronously (using import) as you need to make a http call to fetch the users so it's asynchronous.
As a result you can fetch the users inside the componentDidMount lifecycle method and use a state variable to store them once they are fetched
There are a couple guidelines that will help separate functions that are Hooks and functions that are Components (these are true most of the time):
1 Component functions use pascal case (start with a capital letter) and always return JSX.
2 Custom Hooks functions conventionally begin with the word "use" and never return JSX.
In your case you probably want to make a custom Hooks function that must be called in a component;
function useUserData() {
const [hasError, setErrors] = useState(false);
const [users, setUsers] = useState({});
const networkCall = useCallback(async fetchData = () => {
const res = await fetch(
"https://swapi.co/api/planets/4/"
);
res
.json()
.then(res => setUsers(res))
.catch(err => setErrors(err));
} , [])
useEffect(() => {
fetchData();
}, []);
return {users, hasError};
}
Then call that custom hook in one of your components:
function App() {
const {users, hasError} = useUserData();
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div>{users}</div>
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
}
If you then need to share that fetched data throughout your app, you can pass it down via props or the context API: https://reactjs.org/docs/context.html
(post a message if you'd like an example of this).