I have question about dispatch action. I do not know why my dispatch redux run infinitely.
Below is my ListUser component
import { ListUsersAction } from "../actions/ListUsersAction";
const ListUsers = props => {
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
if (props.listUsersReducer.thanhvien.length > 0) {
const listUsersReducer = props.listUsersReducer;
const propToSend = {
currentPage: listUsersReducer.currentPage,
pages: listUsersReducer.pages,
resPerPage: listUsersReducer.resPerPage
};
return (
<Fragment>
<Pagination pageProp={propToSend} />
</Fragment>
);
} else {
return null;
}
};
const mapStateToProp = state => ({
listUsersReducer: state.listUsersReducer
});
export default connect(mapStateToProp, { ListUsersAction })(ListUsers);
and here is ListUserAction
export const ListUsersAction = (resPerPage, currentPage) => async dispatch => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get('/api/admin/users/page/:page', {
params: {
page: currentPage,
resPerPage: resPerPage
}
});
dispatch({
type: LOADUSERS,
payload: res.data
});
}catch(err){
console.log(err);
dispatch({
type: STOPLOADUSERS
})
}
}
You can see the action always render
Can you tell me why and how to fix it?
You are calling your action every time your Component re renders, and calling your action is causing your Component to re render, creating an infinite loop.
Put your action inside a useEffect to prevent this and only call it once on component mount or whenever you want based on the dependency array:
useEffect(() => {
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
},[])
const ListUsers = props => {
React.useEffect(()=>{
var resPerPage = configList.users.resPerPage;
props.ListUsersAction(resPerPage, 1);
},[])
// your code
};
try this
functional component render every times,
thats why it happend
check hooks API useEffect
Related
Description
I have component which shows data that get from server and display it on the table using the state, tableData and it must be set when Redux action is dispatched.
I've use action listener library which uses Redux middleware which consisting of 63 lines of code. redux-listeners-qkreltms.
For example when I register a function on analysisListIsReady({}).type which is ANALYSISLIST_IS_READY then when the action is dispatched, the function is called.
Issue
The issue is that react throws sometimes the error: Can't update react state... for setTableData so response data is ignored to be set. I want to figure it out when it happens.
I've assumed that it's because of unmounting of component, so I printed some logs, but none of logs are printed and also ComponentA is not disappeared.
It's not throing any error when I delete getAnalysisJsonPathApi and getResource, so I tried to reporuduce it, but failed... link
It's not throing any error when I delete listenMiddleware.addListener see: #2
#1
// ComponentA
const [tableData, setTableData] = useState([])
useEffect(() => {
return () => {
console.log("unmounted1")
}}, [])
useEffect(() => {
listenMiddleware.addListener(analysisListIsReady({}).type, (_) => {
try {
getAnalysisJsonPathApi().then((res) => {
//...
getResource(volumeUrl)
.then((data: any) => {
// ...
setTableData(data)
})
})
} catch (error) {
warn(error.message)
}
})
return () => {
console.log("unmounted2")
}
}, [])
export const getAnalysisJsonPathApi = () => {
return api
.post('/segment/volume')
.then(({ data }) => data)
export const getResource = async (src: string, isImage?: boolean): Promise<ArrayBuffer> =>
api
.get(src)
.then(({ data }) => data)
#2
// ComponentA
const [tableData, setTableData] = useState([])
useEffect(() => {
return () => {
console.log("unmounted1")
}}, [])
useEffect(() => {
if (steps.step2a) {
try {
getAnalysisJsonPathApi().then((res) => {
//...
getResource(volumeUrl)
.then((data: any) => {
// ...
setTableData(data)
})
})
} catch (error) {
warn(error.message)
}
}
return () => {
console.log("unmounted2")
}
}, [steps.step2a])
Well, its as you said:
because of unmounting of component
In your UseEffect() function, you need to check if the componenet is mounted or not, in other words, you need to do the componentDidMount & componentDidUpdate (if needed) logics:
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
// do componentDidMount logic
console.log('componentDidMount');
mounted.current = true;
} else {
// do componentDidUpdate logic
console.log('componentDidUpdate');
}
});
i didn't go to your question code detail, but my hint might help you, usually this error happens in fetchData function,
suppose you have a fetchData function like below:
fetchData(){
...
let call = await service.getData();
...
--->setState(newItems)//Here
}
so when api call end and state want to be updated, if component been unmounted, there is no state to be set,
you can use a bool variable and set it false when component will unmount:
let stillActive= true;
fetchData(){
active = true;
...
let call = await service.getData();
...
if(stillActive)
setState(newItems)//Here
}
}
componentWillUnmount(){
active = false;
}
I've found out it's because of redux-listeners-qkreltms, Redux middleware.
It keeps function when component is mounted into listener, but never changes its functions even component is unmounted.
middleware.addListener = (type, listener) => {
for (let i = 0; i < listeners.length; i += 1) {
if (listeners[i].type === type) {
return;
}
}
listeners.push(createListener(type, listener));
};
When I dispatch an action from a child component that triggers a change in the Redux store, the whole tree gets re-render including the child component itself. How can I stop that?
For example ...
1- I'm listening for when a user clicks on a child component to expand it.
const [expanded, setExpanded] = useState(false);
<span onClick={() => setExpanded(!expanded)}>Expand</span>
2- Then if expanded is true I dispatch an action in useEffect ...
useEffect(() => {
if (expanded) {
dispatch(asyncGetItems())
}
}, [expanded]);
3- Then inside the async dispatch it changes the redux store based on the returned data ...
export const getItems = (items: {}): AppActions => {
return {
type: GET_ITEMS,
payload: items,
};
};
export const asyncGetItems = () => {
return async (dispatch: any, getState: () => AppState) => {
try {
const { data } = await apiGetItems();
dispatch(getItems(data));
} catch (error) {
handleResponseError(error);
}
};
};
Now, when I do that and change the data in the store the component gets re-rendered because its parents get re-rendered, how to stop that?
I tried to use memo like this, but it didn't work ...
export default memo(MyComponent);
And when I don't change anything in the store and just return the data like this ...
export const asyncGetItems = () => {
return async (dispatch: any, getState: () => AppState) => {
try {
const { data } = await apiGetItems();
return data;
} catch (error) {
handleResponseError(error);
}
};
};
The component doesn't get re-rendered.
I have a simple useEffect that I'm not sure how to stop from invoking endlessly. It keeps firing the first if conditional endlessly. I've been reading a lot about hooks and I assume (maybe erroneously) that each render of the component results in a new invocation of my useAuth() and useUser() hooks. Since they have new references in memory it's triggering the useEffect's deps since technically it's a new function that exists in the scope of this new component render?
Thats my thought at least, no clue how to fix that if that's indeed that case.
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser } = useAuth(); // imported
const { fetchUser } = useUser(); // imported
const router = useRouter();
useEffect(() => {
// authStatus();
const unsubscribe = firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log(1);
return fetchUser(user.uid); // async function that fetches from db and updates redux
}
console.log(2);
return logoutUser(); // clears userData in redux
});
return () => unsubscribe();
}, [fetchUser, logoutUser]);
...
}
fetchUser
const fetchUser = async (uid) => {
try {
// find user doc with matching id
const response = await firebaseFirestore
.collection('users')
.doc(uid)
.get();
const user = response.data();
// update redux with user
if (response) {
return dispatch({
type: FETCH_USER,
payload: user,
});
}
console.log('no user found');
} catch (error) {
console.error(error);
}
};
logoutUser
const logoutUser = async () => {
try {
// logout from firebase
await firebaseAuth.signOut();
// reset user state in redux
resetUser();
return;
} catch (error) {
console.error(error);
}
};
when I refresh the page with this useEffect on this is output to the console:
useEffect(() => {
function onAuthStateChange() {
return firebaseAuth.onAuthStateChanged((user) => {
if (user) {
fetchUser(user.uid);
} else {
resetUser();
}
});
}
const unsubscribe = onAuthStateChange();
return () => {
unsubscribe();
};
}, [fetchUser, resetUser]);
Keeping everything the same && wrapping fetchUser and resetUser with a useCallback, this solution seems to be working correctly. I'm not entirely sure why at the moment.
I have a React component using hooks like this:
const myComponent = (props) => {
useEffect(() => {
FetchData()
.then(data => {
setState({data: data});
}
// some other code
}, []);
//some other code and render method...
}
fetchData is in charge to use axios and get the data from an API:
const FetchData = async () => {
try {
res = await myApiClient.get('/myEndpoint);
} catch (err) {
console.log('error in FetchData');
res = err.response
}
}
and finally myApiClient is defined externally. I had to use this setup in order to be able to use different APIs...
import axios from "axios";
axios.defaults.headers.post["Content-Type"] = "application/json";
const myApiClient = axios.create({
baseURL: process.env.REACT_APP_API1_BASEURL
});
const anotherApiClient = axios.create({
baseURL: process.env.REACT_APP_API2_BASEURL
});
export {
myApiClient,
anotherApiClient
};
with this setup I am getting the warning
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
I googled a bit and I saw some suggestions on how to clean up requests from useEffect, like this, but my axios is defined externally. So how can I send the cancellation using this setup?
Also, the application is using redux, not sure if it is in some way involved.
Any other suggestion to avoid the error is welcome.
You can use defer from rxjs for this:
const FetchData = () => {
try {
return myApiClient.get("/myEndpoint");
} catch (err) {
console.log("error in FetchData");
return err.response;
}
};
const myComponent = (props) => {
useEffect(() => {
const subscription = defer(FetchData()).subscribe({
next: ({
data
}) => {
setState({
data: data
});
},
error: () => {
// error handling
},
complete: () => {
// cancel loading state etc
}
});
return () => subscription.unsubscribe();
}, []);
}
Alway check if you are dealing with fetch or any long operations.
let _isMounted = false;
const HooksFunction = props => {
const [data, setData] = useState({}); // data supposed to be object
const fetchData = async ()=> {
const res = await myApiClient.get('/myEndpoint');
if(_isMounted) setData(res.data); // res.data supposed to return an object
}
useEffect(()=> {
_isMounted = true;
return ()=> {
_isMounted = false;
}
},[]);
return (
<div>
{/*....*/}
<div/>
);
}
I'm trying to set a variable with useState after an API call, but it doesn't work. Debugging by reactotron, he makes the API call, but he doesn't set the variable.
export default function Forecast({ navigation }) {
const [cityData, setCityData] = useState([]);
const idNavigation = navigation.state.params.cityData.woeid;
async function loadCityData(cityID) {
const response = await api.get(`${cityID}`);
setCityData([response]);
console.tron.log(cityData);
}
useEffect(() => {
if (idNavigation) {
loadCityData(idNavigation);
}
return () => {};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [idNavigation]);
return <Text>Forecast Weather</Text>;
}
Forecast.propTypes = {
navigation: PropTypes.shape({
state: PropTypes.object,
}).isRequired,
};
Setting state in React is async for the most part and the changes to the state might not be visible if you try to console.log them right away. The recommended way to do this with hooks, is to check for the updated state in useEffect:
async function loadCityData(cityID) {
const response = await api.get(`${cityID}`);
setCityData([response]);
}
// Track cityData and log the changes to it
useEffect(() => {
console.log(cityData);
}, [cityData]);
// Rest of the code
// ...
Umm, I think it maybe reactotron or api problem.
Just try
const [cityData, setCityData] = useState('foo');
...
return <Text>{JSON.stringify(cityData)}</Text>;
If your plobrem came from reactron, then you can see the response from API.
because useState is asynchronous function.
setCityData([response]); // asynchronous function so not set the new data to state yet.
console.tron.log(cityData);// so you get the old data.
See this Example
const Forecast = ({ idNavigation }) => {
const [cityData, setCityData] = React.useState([]);
function loadCityData(cityID) {
setTimeout(() => {
setCityData([1,2,3,4,5]);
console.log("this is old data", cityID, cityData); // because useState is asynchronous function
}, 2000);
}
React.useEffect(() => {
if (idNavigation) {
console.log("load ", idNavigation);
loadCityData(idNavigation);
}
}, [idNavigation]);
React.useEffect(() => {
if(cityData.length > 0) {
console.log("this is new data", cityData);
}
}, [cityData]); // when cityData changed and component mounted, this function called.
return <div>Forecast Weather</div>;
}
class App extends React.Component {
state = {
id: 1,
}
componentDidMount() {
setTimeout(() => {
this.setState({id: 2});
}, 2000);
}
render() {
return (
<div>
<Forecast idNavigation={this.state.id}/>
</div>
);
}
}
ReactDOM.render( <App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Solution
if you use Class Component you can use callback of setState() function.
but if you use Functional Component you can't use a callback.
so you should use useEffect() to solve this problem.