Reputation: 6481
I have my useEffect
in the Profile
component like this:
const [mappedPosts, setMappedPosts] = useState(null);
useEffect(() => {
fetchMedia();
if (userPosts) {
console.log("This keeps entering!");
const mapped = userPosts.map((post, index) => (
<View key={index}>
<Text>{post.title}</Text>
</View>
));
setMappedPosts(mapped);
}
}, []);
And my Profile
component returns,
return mappedPosts ? (
<View>
{mappedPosts}
</View>
) : (
<Spinner />
);
My problem is the following:
The Spinner
component is rendered when I have:
useEffect(() => {
fetchMedia();
if (userPosts) {...};
setMappedPosts(mapped);
}
}, []);
or
useEffect(() => {
fetchMedia();
if (userPosts) {...};
setMappedPosts(mapped);
}
}, [mappedPosts]);
And the appropriate data is rendered when I have:
useEffect(() => {
fetchMedia();
if (userPosts) {...};
setMappedPosts(mapped);
}
}, [userPosts]);
or
useEffect(() => {
fetchMedia();
if (userPosts) {...};
setMappedPosts(mapped);
}
});
The problem with the last two is that the line console.log("This keeps entering!");
is printed over and over again, indicating that the component keeps rerendering. I'm not sure what I'm doing wrong here. The below variation keeps rerendering the component as well:
const [mappedPosts, setMappedPosts] = useState(null);
useEffect(() => {
fetchMedia();
if (userPosts && mappedPosts === null) {
console.log("entered!");
const mapped = userPosts.map((post, index) => (
<View key={index}>
<Text>{post.title}</Text>
</View>
));
setMappedPosts(mapped);
}
}, [userPosts]);
console.log("am I being re-rendered?");
return mappedPosts ? (
<View>
{mappedPosts}
</View>
) : (
<Spinner />
);
I know this because console.log("am I being re-rendered?");
keeps printing nonstop... even though the data is already rendered and displayed appropriately on the screen (I'm using react-native).
The Profile
component gets userPosts
from the redux store:
const Profile = ({
navigation,
posts: { userPosts, loading },
auth: { isAuthenticated },
fetchMedia,
checkAuth
}) => {
const [mappedPosts, setMappedPosts] = useState(null);
useEffect(() => {
fetchMedia();
.......
The fetchMedia
:
export const fetchMedia = () => async dispatch => {
dispatch({
type: FETCH_MEDIA
});
try {
const response = await axios.get(`http://${GATEWAY}:5000/api/posts/user`);
dispatch({
type: MEDIA_SUCCESS,
payload: response.data
});
} catch (err) {
dispatch({
type: MEDIA_FAIL
});
}
};
The reducer:
const initialState = {
userPosts: null,
loading: false
};
export default function(state = initialState, action) {
const { type, payload } = action;
switch (type) {
case FETCH_MEDIA:
return {
...state,
loading: true
};
case MEDIA_SUCCESS:
return {
...state,
userPosts: payload,
loading: false
};
case MEDIA_FAIL:
return {
...state,
loading: false
};
default:
return state;
}
}
Upvotes: 3
Views: 1695
Reputation: 39250
Your answer will probably work because data will only be fetched once and userPost
never changes reference.
Usually putting REST data in state is a bit more complicated than what you do. If you need data then the container will select the data something like this:
const NOT_REQUESTED = {requested:false}
state => state.data.someData || NOT_REQUESTED
Then the component can request the data if it was not requested.
useEffect(()=>requested || fetchData(),[requested])
The reducer will set requested and loading as a response to requested action:
{
...state,
data: {
...state.data,
someData: { requested: true, loading: true },
},
};
Now when your component will re render but fetchData
is not called because requested is true.
When request succeeds the reducer can write the result on data success action:
{
...state,
data: {
...state.data,
someData: {
...state.data.someData,
loading: false,
data: action.data,
},
},
};
This is not with server side filtering, sorting, stale data or error handling but I hope you get the idea.
If you want component(s) to dispatch actions to load data then your containers/selectors need to produce props that indicate if the data has been requested or not.
Upvotes: 2
Reputation: 6481
So I figured it out. The problem was that fetchMedia was being called every single time the component changed. So once userPosts
were fetched.. they were fetched again. And again. And again. That's why the component kept rerendering. Here's the change I made:
useEffect(() => {
if (userPosts) {
console.log("entered!");
const mapped = userPosts.map((post, index) => (
<View key={index}>
<Text>{post.title}</Text>
</View>
));
setMappedPosts(mapped);
} else {
fetchMedia();
}
}, [userPosts]);
Upvotes: 2