updating useRef is not working for some reason - reactjs

So the following code is an attempt to make two copies of a fetch request. One of them is for state and the other is being stored in as a ref.
I can't understand why the following snippet doesn't do what I need to it to which is add the backgroundColor prop to the ref array and then set the state to that array:
useEffect(() => {
fetchData()
setTimeout(() => {
clone.current.map(item => ({...item, backgroundColor: 'blue'}))
console.log(clone.current)
setData([...clone.current])
console.log(data)
}, 4000)
}, [])
This is my first time using useRef, I'm trying to understand how to keep a copy of the state that won't change through renders. This is the whole code:
import React from 'react'
import './MyProjects.css'
import {useState, useEffect, useRef} from 'react'
const MyProjects = () => {
const [data, setData] = useState([])
let clone = useRef(null);
async function fetchData() {
await fetch('https://jsonplaceholder.typicode.com/todos?userId=1')
.then(response => response.json())
.then(json => {
setData(json)
clone.current = [...json]
})
// .then(setData(data.map((item) => ({...item, backgroundColor: true}))))
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
fetchData()
setTimeout(() => {
clone.current.map(item => ({...item, backgroundColor: 'blue'}))
console.log(clone.current)
setData([...clone.current])
console.log(data)
}, 4000)
}, [])
return (
<div className='project-container'>
{data.length > 0 && (
<div className='data-container'>
{data.map(item => (
<div key={item.id} style={{backgroundColor : item.backgroundColor}} onClick={() => {
}}
className='dataItem'>{item.title}</div>
))}
</div>
)}
</div>
)
}
export default MyProjects

2 Issues with the function:
Javascript map operation would create another array instead of updating current array, use for loop to update the ref.
It's not a guarantee that your fetch call will be executed before your settimeout call is executed, that's why you want to create another useeffect to process the data, you can create dataLoaded flag state and use that to just tigger this updating only once.
const [data, setData] = useState([]);
const [dataLoaded, setDataLoaded] = useState(false);
async function fetchData() {
await fetch('https://jsonplaceholder.typicode.com/todos?userId=1')
.then(response => response.json())
.then(json => {
setData(json)
clone.current = [...json]
setDataLoaded(true);
})
.catch((err) => {
console.log(err);
});
}
useEffect(() => {
fetchData()
}, []);
useEffect(()=>{
if(!dataLoaded) return;
clone.current.map(item => ())
for(let i = 0; i < clone.current.length; i++){
clone.current[i] = {...clone.current[i], backgroundColor: 'blue'};
}
console.log(clone.current)
setData([...clone.current])
console.log(data)
}, [dataLoaded]);

There are several issues:
array.map method does not mutate the array but create a new array
const newArray = clone.current.map(item => ({...item, backgroundColor: 'blue'}))
console.log(clone.current); // clone is not mutated
console.log(newArray); // however the newly created array will contain backgroundColor = 'blue'
setData is async
setData([...clone.current]) // set data is not immediate
console.log(data) // the variable `data` binded here anyway won't reference the updated value
Your fetchData is asynchronous, your setTimeout is very prone to be buggy as you can't ensure that fetchData is completed
As a side note I don't really see the point of using a ref here since the data is already stored in a useState

Related

Why I have error when use Api from DummyJSON

Run code and I get "Consider adding an error boundary to your tree to customize error handling behavior.: How can I fix it
import React, { useState, useEffect } from "react";
const BASE_URL = "https://dummyjson.com/products";
export default function App() {
const [product, setProduct] = useState([]);
useEffect(() => {
fetch(`${BASE_URL}`)
.then((res) => res.json())
.then((res) => {
setProduct(res);
})
// .then(console.log)
}, []);
return (
<ul>
{product.map((item) => (
<li key={item.id}>
{item.products.brand}
</li>
))}
</ul>
);
}
I tried change another API and it dose not have error
The problem is what type of data in file json. In this case data' type is object so I can not use map to render product.
first I change useState hook from empty array to object
const [product, setProduct] = useState({ products: [] });
then fetch data from api
const fetchData = () => {
fetch(`${BASE_URL}?limit=5`)
.then((res) => res.json())
.then((data) => {
setProduct(data);
});
};
useEffect(() => {
fetchData();
});
after fecth data and set to state I render it

React async/await prop not re-rendering in child component

Link to CodeSandBox of what I am experiencing:
https://codesandbox.io/s/intelligent-chaum-eu1le6?file=/src/About.js
I am stuggling to figure out why a component will not re-render after a state changes. In this example, it is an array prop given from App.js to About.js.
a fetch request happens three times in a useEffect. Each time, it pushes it to stateArr before finally setState(stateArr)
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
setState(stateArr);
The About component is imported, and the useState variable is passed to it as a prop.
return (
<div>
<About arrayProp={state} />
</div>
);
Finally, About.js destructs the prop, and arrayProp.map() is called to render each array item on the page.
const About = ({ arrayProp }) => {
const [rerender, setRerender] = useState(0);
return (
<>
{arrayProp.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</>
);
};
In the CodeSandBox example, I've added a button that would manually re-render the page by incrementing a number on the page.
The prop should prompt a component re-render after the fetch requests are completed, and the state is changed.
The issue is that useEffect is not behaving as described.
Each time, it pushes it to stateArr before finally setState(stateArr)
The individual fetches are not pushing to "before finally" calling setState.
const [state, setState] = useState([]);
useEffect(() => {
let stateArr = [];
function getReq() {
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
});
setState(stateArr);
}
getReq();
}, []);
What is actually happening is: fetch 1 is starting, then fetch 2 is starting, then fetch 3 is starting, then setState(stateArr) is being called.
There's no guarantee that these fetch will resolve before setState is called (there's similarly no guarantee that the fetches won't complete before calling setState). Though, in normal circumstances none of the fetches will resolve before setState is called.
So the only thing that's guaranteed is that state will be updated to reference the same array as stateArr. For this reason, pushing to stateArr is the same as pushing to state which is mutating state without using setState. This can cause results to be overwritten on future setState calls and it does not cause a re-render.
Well then, why does forcing re-render in About work?
As each fetch resolves it pushes values to stateArr (which is the same array as is referenced by state) for this reason the values are in the state there's just been nothing to tell React re-render (like a setState call).
Here's a small snippet which logs the promises as they complete. It also has a button that will console log the state array. (Nothing will ever render here as nothing will cause the state to update despite the state array being modified)
// Use import in normal cases; const is how use* are accessed in Stack Snippets
const {useState, useEffect} = React;
const App = () => {
const [state, setState] = useState([]);
useEffect(() => {
let stateArr = [];
function getReq() {
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 1 resolves', stateArr);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 2 resolves', stateArr);
});
fetch("https://catfact.ninja/fact")
.then((res) => {
return res.json();
})
.then((res) => {
stateArr.push(res);
console.log('Promise 3 resolves', stateArr);
});
console.log('Calling Set State')
setState(stateArr);
}
getReq();
}, []);
return (
<div>
<button onClick={() => console.log(state)}>Log State Array</button>
{state.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>
To resolve this, simply wait for all promises to complete with Promise.all, then call setState with all the values.
const [state, setState] = useState([]);
useEffect(() => {
Promise.all([
// Promise 1
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 2
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 3
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
})
]).then((newStateArr) => {
// Wait for all promises to resolve before calling setState
setState(newStateArr);
});
}, []);
And here's a snippet demoing the result when waiting for all promises to resolve:
// Use import in normal cases; const is how use* are accessed in Stack Snippets
const {useState, useEffect} = React;
const App = () => {
const [state, setState] = useState([]);
useEffect(() => {
Promise.all([
// Promise 1
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 2
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
}),
// Promise 3
fetch("https://catfact.ninja/fact").then((res) => {
return res.json();
})
]).then((newStateArr) => {
// Wait for all promises to resolve before calling setState
setState(newStateArr);
});
}, []);
return (
<div>
{state.map((e) => (
<div key={e.length}>
<h6>Break</h6>
{e.fact}
</div>
))}
</div>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<App/>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
<div id="root"></div>

ReactJS : Failing to fetch an API

Hey I'm trying to fetch an API, but it dosnt returns anything.
I've checked and I cannot access my pre-built values inside my fetch.
How can I access my values inside the fetch ?
import React, { useState, useEffect } from 'react';
function App() {
const [ positionLat, setPositionLat ] = useState('') ;
const [ positionLong, setPositionLong] = useState('') ;
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
console.log(positionLat) // returns good result
console.log(positionLong) // returns good result
// I obviously need to call those values inside my fetch
useEffect(() => {
console.log(positionLat) // returns undefined
console.log(positionLong) // returns undefined
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${positionLat}&lon=${positionLong}&appid={api_key}b&units=metric`)
.then(res => {
return res.json();
})
.then(data => {
console.log(data)
})
}, []);
return (
<div className="App">
<p>lattitude :{positionLat}</p>
<p>longitude :{positionLong}</p>
</div>
);
}
export default App;
One option is to change your effect hook to only run the main body once the values are defined:
useEffect(() => {
if (positionLat === '' || positionLong === '') {
return;
}
// rest of function
fetch(...
}, [positionLat, positionLong]);
You also need to fix your geolocation call to occur only once, on mount.
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {
setPositionLat(position.coords.latitude);
setPositionLong(position.coords.longitude);
});
}, []);
Another option is to split it up into two components, and only render the child component (which does the fetching) once the geolocation call is finished, which might look cleaner.
const App = () => {
const [coords, setCoords] = useState();
useEffect(() => {
navigator.geolocation.getCurrentPosition(function (position) {
setCoords(position.coords);
});
}, []);
return coords && <Child {...coords} />;
};
const Child = ({ latitude, longitude }) => {
useEffect(() => {
fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid={api_key}b&units=metric`)
.then(res => res.json())
.then(data => {
// do stuff with data
})
// .catch(handleErrors); // don't forget to catch errors
}, []);
return (
<div className="App">
<p>latitude :{latitude}</p>
<p>longitude :{longitude}</p>
</div>
);
};

UseEffect is not called

I have a question about useEffect. My useEffect is not fetching the data the first time, I have to switch route for it to have the data I needed
const Comments = ({ ...rest }) => {
const theme = useTheme();
const classes = useStyles({ theme });
const [users, setUsers] = useState([]);
const { push } = useHistory();
const { token, loading } = useContext(AuthContext)
const dispatch = useDispatch();
const allUsers = useSelector(state => state.allUsers);
const comments = useSelector(state => state.listCommentsByBookId);
const listBooks = useSelector((state) => state.userListBooks);
const isFetching = useSelector((state) => state.isFetching);
const [stateReady, setReadyForRender] = useState(false)
const redirectTo = ( rowData ) => {
push({
pathname: ROUTE.USERS_DETAILS,
user: rowData
});
}
const options = {
filterType: 'checkbox',
selectableRowsHeader: false,
selectableRowsHideCheckboxes: false,
selectableRowsOnClick: false,
onRowClick: redirectTo,
};
const getAllComments = async () => {
var allusersId = [];
//get all ids
await allUsers.map((user) => {
allusersId.push(user.uid);
})
//get all books from users
await allusersId.map(async (id) => {
await dispatch(getUserListBooks(apiURL + `api/bdd/userListBooks/${id}`, token))
})
var listArray = [];
//filter the array and delete empty rows
listArray.push(listBooks);
var newArray = listArray.filter(e => e);
//map every user and stock the list of books in string
await newArray.forEach(async (book)=> {
await book.map(async (book) => {
await dispatch(getCommentsByBookId(apiURL + `api/bdd/ratingByBook/${book.id}`, token));
})
})
setReadyForRender(true)
}
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
}, [stateReady])
console.log('COM', comments);
return (
<div>
{stateReady &&
<Card>
<Box className={classes.tableContainer} sx={{ minWidth: 1050 }}>
<MUIDataTable
data={comments}
columns={columns}
options={options}
/>
</Box>
</Card>}
</div>
);
};
Why? It might be related to async await but I'm stuck here.
If you want to fetch these informations on the first render, you'll have to pass an empty array as the second parameter of your useEffect.
The reason your useEffect is not called is because stateReady does not change during the course of your current code.
See this link, particularly the note section, it explains way better than me how the empty array as second parameter works.
Can you replace the useEffect section to the below code:
useEffect(() => {
(async () => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
})()
}, [stateReady])
You can read more about this in this link
You can use eslint to show errors when coding with hooks. In this case if you want useEffect to handle stateReady, please provide it in the function getAllComments() => getAllComments(stateReady) and when you call this function in useEffect with [stateReady] as dependencies, it'll work.
You should remove stateReady from your dependency array in the useEffect hook. Adding variables in the dependency array means that the use Effect hooks fires only when one of the dependencies changes. Here's how to use useEffect as lifecycle methods https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
console.log('is fetching', isFetching)
if(comments.length === 0) {
getAllComments();
}
});

How to set local array to global react?

I have an array of Names(Commented in code):=
export default Main_homepage = (props) => {
var Names = []
useEffect(() => {
fetch('https://www.amrutras.com/Items.php')
.then((response) => response.json())
.then((responseJson) => {
{
Names = responseJson //***Names Array***
console.log(Names[0].ID) //****Its working, I am getting outpu for this in console
console.log(Names[0].Name)
}
})
.catch((error) => {
console.error(error)
})
})
return(
<View>{console.log(Names[0].ID)}</View> //****Its not working.
)
}
But when I am trying to access outside of the use effect it's not working.
In short, I am trying to access the response array in JSX.
As suggested by Praveen Kumar sir, utilize useState hook.
Here is the Full Working Example: Expo Snack
import React, { useEffect, useState } from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
export default App = (props) => {
const [names, setNames] = useState([]);
useEffect(() => {
fetch('https://www.amrutras.com/Items.php')
.then((response) => response.json())
.then((responseJson) => {
{
console.log(responseJson);
setNames(responseJson); //***Names Array***
}
})
.catch((error) => {
console.error(error);
});
}, []);
return (
<View style={{ marginTop: Constants.statusBarHeight }}>
<Text>{JSON.stringify(names)}</Text>
</View>
);
};
So this is an asynchronous call and it will not work because after the return statement is sent out, the value gets changed.
Change Names into a state hook - Using the State Hook:
// Remove this
// var Names = []
// Replace with:
const [Names, setNames] = useState([]);
And when you're updating, use setNames:
// Remove this inside the promise
// Names = responseJson
// Replace with the following:
setNames(Names);
If you want to understand what an asynchronous call, read more at How do I return the response from an asynchronous call?

Resources