Component not accessing context - javascript

i'm new to react please forgive me if i'm asking a dumb question.
The idea is to access the tweets array from context, find the matching tweet and then set it in the component's state to access the data.
However, the tweets array results empty even though i'm sure it's populated with tweets
const { tweets } = useContext(TweeetterContext)
const [tweet, setTweet] = useState({})
useEffect(() => {
loadData(match.params.id, tweets)
}, [])
const loadData = (id, tweets) => {
return tweets.filter(tweet => tweet.id == id)
}
return (stuff)
}

You are accessing context perfectly fine, and it would be good if you could share a code where you set tweets.
Independent of that, potential problem I might spot here is related to the useEffect function. You are using variables from external context (match.params.id and tweets), but you are not setting them as dependencies. Because of that your useEffect would be run only once at the initial creation of component.
The actual problem might be that tweets are set after this initial creation (there is some delay for setting correct value to the tweets, for example because of the network request).
Try using it like this, and see if it fixes the issue:
useEffect(() => {
loadData(match.params.id, tweets)
}, [match.params.id, tweets])
Also, not sure what your useEffect is actually doing, as it's not assigning the result anywhere, but I'm going to assume it's just removed for code snippet clarity.

Related

Filtering Table data with useEffect Hook causing component to re-render infinitely

I'm trying to use a search bar component to dynamically filter the content of a table that's being populated by API requests, however when I use this implementation the component re-renders infinitely and repeatedly sends the same API requests.
The useEffect() Hook:
React.useEffect(() => {
const filteredRows = rows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
});
if (filteredRows !== rows){
setRows(filteredRows);
}
}, [rows, search]);
Is there something I've missed in this implementation that would cause this to re-render infinitely?
Edit 1:
For further context, adding in relevant segments of code from that reference this component which might cause the same behaviour.
Function inside the parent component that renders the table which calls my API through a webHelpers library I wrote to ease API request use.
function fetchUsers() {
webHelpers.get('/api/workers', environment, "api", token, (data: any) => {
if (data == undefined || data == null || data.status != undefined) {
console.log('bad fetch call');
}
else {
setLoaded(true);
setUsers(data);
console.log(users);
}
});
}
fetchUsers();
Edit 2:
Steps taken so far to attempt to fix this issue, edited the hook according to comments:
React.useEffect(() => {
setRows((oldRows) => oldRows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
}));
}, [search]);
Edit 3:
Solution found, I've marked the answer by #Dharmik pointing out how Effect calls are managed as this caused me to investigate the parent components and find out what was causing the component to re-render repeatedly. As it turns out, there was a useEffect hook running repeatedly by a parent element which re-rendered the page and caused a loop of renders and API calls. My solution was to remove this hook and the sub-components continued rendering as they should without loops.
It is happening because you've added rows to useEffect dependency array and when someone enters something into search bar, The rows get filtered and rows are constantly updating.
And because of that useEffect is getting called again and again. Remove rows from the useEffect dependency array and it should work fine.
I would like to complement Dharmik answer. Dependencies should stay exhaustive (React team recomendation). I think a mistake is that filteredRows !== rows uses reference equality. But rows.filter(...) returns a new reference. So you can use some kind of deep equality check or in my opinion better somethink like:
React.useEffect(() => {
setRows((oldRows) => oldRows.filter((row) => {
return row.name.toLowerCase().includes(search.toLowerCase());
}));
}, [search]);

React force update

I always run into situations where I need to force rerender, while I'm still in the execution of some function, so I developed my solution to this and I need to know if this is right or there is a simpler way to achieve the same goal.
I rely on the state variable my_force_update, then I change it to a random value when I want to enforce a change. like:
const [my_force_update, setMyForceUpdate] = useState(0);
useEffect(()=>{}, [my_force_update]);
const handleSubmit = async () =>{
await prm1();
stMyForceUpdate(Math.random()); // enforcing the effect
await prom2();
....
}
so I have been able to enforce re-render (by enforcing the effect) while I'm still in the handleSubmit execution.
is there a simpler way? or, did I mistakenly understand the concepts of React?
update
The issue is that I have a checkout form, and I need it to be a signup form at the same time, and there is also a login component on the page.
so I need to populate the form fields with the account if information in case of login and in case of sign up.
The steps are as follow:
if user login => populate form (per fill it with user info) => move to payment.
if user fill out the form manually:
create an account.
authenticate the new user.
update the user account.
repopulate form (with data from user account).
move to payment.
so I have this function that needs to listen to the login and signup:
const token = useSelector(_token);
const loggedIn = useSelector(_loggedIn);
const profile = useSelector(_profile);
useEffect(() => {
/**
* Pre-fill the form inputs
*/
(async () => {
const r = await dispatch(fetchUserInfo());
setFormProfile(profile); // address is not updated yet
setFormAddress(r?.result?.address);
})();
}, [loggedIn, forceUpdate]);
now, there are no issues with the login process, the only problem is with the signup:
at step 2, when authenticating the user, its account is empty.
so the loggedIn changes to true when the profile is empty so I got empty form.
after updating the profile, loggedIn will not change, so I need another variable to trigger the effect again.
I tried to listen to profile here, but I got an infinite loop.
and here is the checkout flow related to the signup:
...
if (!loggedIn) {
const signupResponse = await dispatch(signupUser(params));
loginResponse = await dispatch(login(formProfile?.email, password));
}
const updateProfileResponse = await saveChangesToProfile();
// update user profile with the information in the checkout form.
...
then save changes to the profile:
const saveChangesToProfile = async () => {
const r = await dispatch(fetchUserInfo());
const addressID = r?.result?.address_id;
const res1 = await dispatch(updateUserAddress(addressID, { ID: addressID, ...formAddress }));
const res = await dispatch(UpdateUser(r?.result?.ID, formProfile));
setForceUpdate(Math.random()); // force re-render to re-populate the form.
setSuccess("Information saved to your profile!");
return res;
};
Update 2
The question is general, I solved the issue in another way days ago (involving changes to the server routes). and I'm asking the question in a general way to get some knowledge, not for others to do the work for me.
In general, you should avoid having to force an update in React but instead use existing React features to accomplish your goal. That being said, there are simple ways to force a re-render in react. You mentioned in the second update that you are looking for more general solutions - so I will provide them here.
However, please bear in mind that this topic has been discussed extensively in other stack overflow questions (I will provide links).
Forcing Re-Render using component.forceUpdate(callback)
The react docs actually list a simple way to force a component to reload (provided you maintain a reference to it). You can find more information here, but essentially it forces your component to re-render and then makes a call to the callback argument.
Forcing Re-Render using hooks
There are multiple stack overflow questions that provide simple code snipets that can force a react component to re-render by using hooks. This answer for example by #Qwerty demonstrates 2 simple code snipets to force a re-render:
const forceUpdate = React.useState()[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
You should check out his answer for a more detailed explanation.
Other sources include this answer to the same stack overflow question that references the official FAQ.
It solves the problem by doing:
const [ignored, forceUpdate] = useReducer(x => x + 1, 0);
Solving Your Specific Problem
I saw that you were able to solve your problem by using the useEffect hook - a great start for a potential solution. You also mentioned that you got an infinite loop while listening to a variable change in your hook - a common problem and one with some common solutions. In general, you should always run a check inside the useEffect hook before changing any of its dependencies. For example, run a check to see if the profile is unset before trying to update its value.
I however would recomend that you use a progress varible that would indicate your status, something like this:
const STATUS_START = 0;
const STATUS_LOGED_IN = 1;
const STATUS_SIGNING_UP = 2;
const [progress, setProgress] = useState(STATUS_START);
Then, you can simply listen to changes made to the progress variable in your useEffect hook (by passing it as your only dependent). This should automatically condition you to write the necessary logic to check for state inside of the useEffect function as I described previously.
This solution would work by initially setting the progress to either signing up or logging in, but only filling the form data if you are logged in (and after the signup progress is done calling setProgress(STATUS_LOGED_IN))

How can I have react batch multiple setStates for fetched data from useEffect?

I am still trying to wrap my head around how react handles renders and this particular behavior has had me scratching my head all day. When i run this code, I get 3 console logs. The first is a null, as expected since useEffect didn't run yet. Next, I get the fetched worldData array from my api call as expected. However, I then get a third console log with the same said array, which leads me to believe my component is being rerendered. If I add another set state and another api call, I see yet another console log.
function App() {
const [worldData, setWorldData] = useState(null)
const [countriesData, setCountriesData] = useState(null)
useEffect(() => {
const fetchData = async () => {
const [world, countries] = await Promise.all([
fetch("https://disease.sh/v3/covid-19/countries?yesterday=true").then(res => res.json()),
fetch("https://disease.sh/v3/covid-19/all?yesterday=true").then(res => res.json())
])
.catch((err) => {
console.log(err)
})
setWorldData(world)
setCountriesData(countries)
}
fetchData();
}, []);
console.log(worldData);
It seems like react is rendering every time I set a State, which is what I assume it's designed to do. However, I've read elsewhere that react batches multiple set states together when set together in useEffect. So why is my set states not being batched then? Is it because of the asynchronous code? If so, is there a way I can fetch multiple endpoints and set all the retrieved data simultaneously so that my component only needs to rerender once?
A couple bits
(1) If you're ONLY concerned about having a single state update then you can defined a single state and update that as such
const [fetched_data, updateFetchedData] = useState({world:null, countries:null});
...
// where *world* and *countries* are variables containing your fetched data
updateFetchedData({world, countries})
...
fetched_data.world // using the fetched data somewhere
(2) HOWEVER, your code should not care about receiving multiple updates and should cater for it as such...
Your useState should define what an empty result would look like, for example instead of
const [worldData, setWorldData] = useState(null); // probably shouldn't look like this
how about
const [worldData, setWorldData] = useState([]); // this is what an empty result set looks like
And inside whatever component that uses both world and countries data ( I assume one references the other which is why you want both updates at once), just put a conditional statement that if it's not found then put some string 'Loading' or 'NA' or whatever until it's loaded.

Returning a value from an Async Function. AWS/React

I'm trying to build a component that retrieves a full list of users from Amazon AWS/Amplify, and displays said results in a table via a map function. All good so far.
However, for the 4th column, I need to call a second function to check if the user is part of any groups. I've tested the function as a button/onClick event - and it works (console.logging the output). But calling it directly when rendering the table data doesn't return anything.
Here is what I've included in my return statement (within the map function)
<td>={getUserGroups(user.email)}</td>
Which then calls this function:
const getUserGroups = async (user) => {
const userGroup = await cognitoIdentityServiceProvider.adminListGroupsForUser(
{
UserPoolId: '**Removed**',
Username: user,
},
(err, data) => {
if (!data.Groups.length) {
return 'No';
} else {
return 'Yes';
}
}
);
};
Can anyone advise? Many thanks in advance if so!
Because you should never do that! Check this React doc for better understanding of how and where you should make AJAX calls.
There are multiple ways, how you can solve your issue. For instance, add user groups (or whatever you need to get from the backend) as a state, and then call the backend and then update that state with a response and then React will re-render your component accordingly.
Example with hooks, but it's just to explain the idea:
const [groups, setGroups] = useState(null); // here you will keep what "await cognitoIdentityServiceProvider.adminListGroupsForUser()" returns
useEffect(() => {}, [
// here you will call the backend and when you have the response
// you set it as a state for this component
setGroups(/* data from response */);
]);
And your component (column, whatever) should use groups:
<td>{/* here you will do whatever you need to do with groups */}</td>
For class components you will use lifecycle methods to achieve this (it's all in the documentation - link above).

React setState() not setting state on certain items

I'm new to React and have some pretty simple code that is acting strange (I think).
The main app fetches a list of blog posts from a server, then passes them through props to a child component which spits the list out. By default, I'm trying to make the posts only show a preview like a title, and each post will have a state attached to it so I can keep track of which ones are fully shown or previewed.
I have the states set up like this:
const [posts, setPosts] = useState([])
const [postFullView, setPostFullView] = useState([])
The list initially is rendered as an empty list so nothing gets returned. When the data fetch finishes, it re-renders with all the posts.
I use useEffect for this in the child component:
useEffect(() => {
console.log('render') //Just to verify this got called
setPosts(props.posts) //Logs empty array 3 lines down,
//setPosts([4,5,6]) //Works fine, gets logged as [4,5,6]
console.log(props.posts) //Logs an array of 32 objects - so props is clearly not empty
console.log(posts) //Logs empty array as if setPosts did nothing, but logs [4,5,6] if I comment out setPosts(props.post) and use setPosts([4,5,6])
setPostFullView(posts.map(post => {return {id: post.id, view: false}}))
console.log(postFullView) //Will be empty since posts is empty
}, [props])
Hopefully, I explained clearly what I'm confused about - I can setState using a hard-coded array, but passing in props.posts does not do anything, even though it has content.
There is nothing wrong about your code, and the reason console.log(posts) spits empty array, it because setPosts(props.posts) is async call and not executed immediately, but tells react it should render again with new value for state.
Sometimes, like in your hardcoded array case, the code will work "fine", but it not guaranteed, for sure in production when code executed faster
yea its nothing wrong because the setState api is async did not show the changes in log but code is work properly and also if you need to check your state you can use React developer Tools extension for browser too see the state status
https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
Actually, your first issue is about understanding the state changing and the re-rendering on ReactJS. why you do not use the first initial state in the first render just like below:
const YourComponent = ({ posts: initialPosts }) => {
const [posts, setPosts] = useState(initialPosts);
Also, there is no need to have the first line you can use it exactly on the second line initializing, like this:
const YourComponent = ({ posts: initialPosts }) => {
const [postFullView, setPostFullView] = useState(
({ id }) => ({ id, view: false }) // es6 arrow function with using restructuring assignment and returning the object
);
After all, when you are using a useEffect or other hooks APIs, please add the specific internal state or prop name to dependencies, no put all the props in the array of dependencies, it caused bad costs and make your project slow to run.

Categories