UseEffect not filling all states - arrays

SO this is my code, i'm trying to filter my offers by there users , i have called all my offers and all my user and there states are full but when i try to filter offers by there users the state stay empty but when i hit spacebar on my keyboard the state get full like it's the spacebar is triggering useEffect to fill the state
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const[useroffers,setUseroffer]=useState([]);
const isOffer = async () => {
const oflg = await GetAllOff();
setOffer(oflg);
};
const isLoggedIn = async () => {
const userLg = await CurrentUser();
setUser(userLg.data.user);
};
const isUseroffer = async()=>{
setUseroffer(offer.filter((el)=>el.createdbyId === user._id));
};
useEffect( () => {
isOffer();
isLoggedIn();
isUseroffer();
}, []);
console.log(offer);
console.log(user)
console.log(useroffers);
So useEffect is filling the offers and user States but not filling the useroffers state intil i click on the spacebar

useroffers is dependent on both user and offer but you are trying to set it in the same render cycle as those two. Updated state values aren't available until the render cycle after they are set, so setUseroffers doesn't have access to the values it needs to update properly. To solve this you can declare a second useEffect which is dependent on user and offer so that as those values update so does your filtered array.
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const [useroffers, setUseroffer] = useState([]);
const isOffer = async () => {
const oflg = await GetAllOff();
setOffer(oflg);
};
const isLoggedIn = async () => {
const userLg = await CurrentUser();
setUser(userLg.data.user);
};
useEffect(() => {
isOffer();
isLoggedIn();
}, []);
useEffect(() => {
setUseroffer(offer.filter((el) => el.createdbyId === user._id));
}, [user, offer]);
codesandbox
Alternatively you can do it all in a single useEffect by awaiting the offer and user values and using them directly to set all three states once they are available. (This will result in only a single rerender rather than the possible four in the previous example)
const [offer, setOffer] = useState([]);
const [user, setUser] = useState({});
const [useroffers, setUseroffer] = useState([]);
useEffect(() => {
const login = async () => {
const userLg = await CurrentUser();
const ofLg = await GetAllOff();
setUser(userLg.data.user);
setOffer(ofLg);
setUseroffer(
ofLg.filter((el) => el.createdbyId === userLg.data.user._id)
);
};
login();
}, []);
codesandbox

Related

When using the useEffect hook, the method of using a dependency array is confusing

I've been studying react hook lately. I found the following in the book I saw.
It is said that the fetchAndSetUser function is updated only when the userId is changed using the useCallback hook.
function Profile({ userId }) {
const [user, setUser] = useState();
const fetchAndSetUser = useCallback(
async needDetail => {
const data = await fetchUser(userId, needDetail);
setUser(data);
},
[userId]
);
useEffect(() => {
fetchAndSetUser(false);
} , [fetchAndSetUser]);
// ...
However, if the fetchAndSetUser function is updated only when the userId is changed using the useCallback hook, I wonder what the difference is from just putting the userId in the dependency array in the useEffect hook. (There is a code below.)
function Profile({ userId }) {
const [user, setUser] = useState();
const fetchAndSetUser = async ( needDetail ) => {
const data = await fetchUser(userId, needDetail);
setUser(data);
};
useEffect(() => {
fetchAndSetUser(false);
} , [userId]);
// ...
I wonder if the two codes are the same or if they are different.
Even though I think both codes would achieve the same result, there is a small difference.
If you wanted to pass that fetchAndSetUser (with no useCallback) function to the children, any time Profile component updated it would also update the children.
You can always combine all with:
function Profile({ userId }) {
const [user, setUser] = useState();
const fetchAndSetUser = useCallback(async ( needDetail ) => {
const data = await fetchUser(userId, needDetail);
setUser(data);
}, [userId]);
useEffect(() => {
fetchAndSetUser(false);
} , [userId]);

How to avoid triggering useEffect() from another useEffect()?

I'm implementing pagination with React. It generally works well, except one issue.
When I'm changing sorting criteria, I want to reset page to 1st. The problem is, changing the page number triggers data fetch again. So whenever I'm on page 2 or above and change sorting criteria, the data is being fetched twice. Once for the fact of changing the criteria (which trigger resetting the page to 1) and then again, as the page changed to one. Is there any clean way to avoid this clash and make the fetch only happen once?
Here's my simplified code:
import { useState, useEffect } from 'react';
export default function MyComponent() {
const [items, setItems] = useState([]);
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
useEffect(
() => (async () => {
const response = await fetch('...');
const { items } = await response.json();
setItems(items);
})(),
[column, direction, currentPage, perPage]
);
useEffect(
() => setCurrentPage(1), // This triggers the useEffect() above
[column, direction, perPage]
);
return (
// Template code
);
}
How would a React guru do this?
You can add a state like shouldFetch that can be used to conditionally fetch and avoid multiple calls.
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
const [shouldFetch, setShouldFetch] = useState(true);
useEffect(() => {
(async () => {
if (shouldFetch) {
const response = await sleep(1000);
console.log(response);
// prevent fetch as we want to allow it later
setShouldFetch(false);
}
})();
}, [column, direction, currentPage, perPage, shouldFetch]);
useEffect(() => {
setCurrentPage(1);
// allow fetch
setShouldFetch(true);
}, [column, direction, perPage]);
const changeColumn = () => {
setColumn("new-col");
};
const changeCurrentPage = () => {
setCurrentPage(2);
// to fetch when currentPage changes
// this should not be added to other handlers as it is also present in the second useEffect that gets triggered when other params change
setShouldFetch(true);
};
const changePerPage = () => {
setPerPage(20);
};
const changeDirection = () => {
setDirection("descending");
};
Alternative:
To avoid unnecessary fetching and to make sure that items are fetched using the updated state values, you can remove the second useEffect and reset currentPage when you update other params.
This will only trigger the useEffect once because React will perform both state updates (setColumn and setCurrentPage) at once.
const sleep = (ms) => new Promise((res) => setTimeout(() => res("Hi Mom"), ms));
export default function App() {
// const [items, setItems] = useState([]);
const [column, setColumn] = useState();
const [direction, setDirection] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(10);
useEffect(() => {
(async () => {
const response = await sleep(1000);
console.log(response);
})();
}, [column, direction, currentPage, perPage]);
// remove this effect
// useEffect(() => setCurrentPage(1), [column, direction, perPage]);
const changeColumn = () => {
setColumn("new-col");
setCurrentPage(1);
};
const changeCurrentPage = () => {
setCurrentPage(2);
};
const changePerPage = () => {
setPerPage(20);
setCurrentPage(1);
};
const changeDirection = () => {
setDirection("descending");
setCurrentPage(1);
};
return (
<>
<button onClick={changeColumn}>change column</button>
<button onClick={changeDirection}>change direction</button>
<button onClick={changeCurrentPage}>change page</button>
<button onClick={changePerPage}>change perPage</button>
</>
);
}

How to ignore previous async effects when useEffect is called again?

I have a simple component that makes an async request when some state changes:
const MyComp = () => {
const [state, setState] = useState();
const [result, setResult] = useState();
useEffect(() => {
fetchResult(state).then(setResult);
}, [state]);
return (
<div>{result}</div>
);
};
The problem is, sometimes the state changes twice in a short lapse of time, and the fetchResult function can take a very different amount of time to resolve according to the state value, so sometimes this happens:
As you can guess, as state now is state2 and not state1 anymore, I would like result to be result2, ignoring the response received in the then of the -obsolete- first effect call.
Is there any clean way to do so?
I would suggest you setup some kind of request cancellation method in the useEffect cleanup function.
For example with axios, it looks like that:
const MyComp = () => {
const [state, setState] = useState();
const [result, setResult] = useState();
useEffect(() => {
const source = axios.CancelToken.source();
fetchResult({state, cancelToken: source.cancelToken }).then(setResult);
return () => {
source.cancel()
}
}, [state]);
return (
<div>{result}</div>
);
};
You have a similar API with fetch called AbortController
What this will do is it will cancel the stale requests if your state changed so only the last one will resolve (and set result).
I've not tested this... but my initial thought would be if you have the state in the response, you could check if the state fetched matches the current state. If not, then the state has changed since the request and you no longer care about the response so don't set it.
useEffect(() => {
fetchResult(state).then((response) => {
response.state === state ? setResult(response.data) : false;
});
}, [state]);
You might also be able to do it by keeping a record of the fetchedState on each request.. and again discard it if it no longer matches.
useEffect(() => {
let fetchedState = state;
fetchResult(fetchedState).then((response) => {
fetchedState === state ? setResult(response) : false;
});
}, [state]);
I've built something like the below in order to only ever use the last result of the last request sent:
const REQUEST_INTERVAL = 2000
const MyComponent = () => {
const [inputState, setInputState] = useState();
const [result, setResult = useState()
const requestIndex = useRef(0)
useEffect(() => {
const thisEffectsRequestIndex = requestIndex.current + 1
requestIndex.current = thisEffectsRequestIndex
setTimeout(() => {
if(thisEffectsRequestIndex === requestIndex.current) {
fetch('http://example.com/movies.json')
.then((response) => {
if(thisEffectsRequestIndex === requestIndex.current) {
setResult(response.json())
}
})
}
})
, REQUEST_INTERVAL)
}, [inputState])
return <div>{result}</div>
}

how do I handle parameters from OnChange - ReactJS?

I am trying to handle the data sent via onChange in order to make api calls based on the dropdown options , but I want to handle a default value if there is nothing sent to the api call
async function fetchFeed(domain) {
return api.get(`http://localhost:8002/api/v1/xxx/list/?domain=${domain}`);
}
export default function Board() {
const [isModalOpen, setModalIsOpen] = useState(false);
const [users, setUsers] = useState([]);
const [responseData, setResponseData] = useState([]);
// fetches data
const fetchData = (domain) => {
fetchFeed(domain)
.then((response) => {
setResponseData(response.data.results);
})
.catch((error) => {
console.log(error);
});
};
const handleChange = (e) =>{
fetchData(e.target.value);
}
useEffect(() => {
const domain = ??
fetchData(domain);
}, []);

React Native: How can i force a rerender to update my userIngredients Value?

const [constructorHasRun, setConstructorHasRun] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [userIngredients, setUserIngredients] = useState([]);
const [userRecipes, setUserRecipes] = useState([]);
const user = useContext(AuthContext);
const constructor = (userIngredients) => {
if (constructorHasRun) return;
getUserData()
fetchRecipes(userIngredients)
setConstructorHasRun(true);
};
constructor(userIngredients)
async function getUserData() {
var userData = await firebase.firestore().doc("ingredients/" + user.uid).get();
var labels = await userData.data().ingredients.map(({ label }) => label)
setUserIngredients(labels)
setUserRecipes(userData.data().recipes)
}
I want to update my userIngredients so it shows what i fetch but for the fetch i need a value in the userIngredients. setState() is async so i need another render to update the value. how can i force it ?
Based on my analysis of your code, you could either have the fetchRecipes call right after you get the data from firebase:
async function getUserData() {
var userData = await firebase.firestore().doc("ingredients/" + user.uid).get();
var labels = await userData.data().ingredients.map(({ label }) => label)
setUserIngredients(labels)
fetchRecipes(labels) // <-- moved this here
setUserRecipes(userData.data().recipes)
}
or inside useEffect with userIngredients as dependency
useEffect(()=>{
fetchRecipes(userIngredients)
}, [userIngredients])

Resources