The result I want is my component to not render unless all the async function have dispatched. I'm using this as a wrapper to make sure everything has dispatched. I've tried two ways:
call everything in componentWillMount and use setState to set loaded = true. I can then render the component based on my state's loaded key.
ajax = async () => {
try{
const { dispatch } = this.props;
dispatch(loadPack('ars'));
dispatch(loadPack('acr'));
await dispatch(loadProds());
await dispatch(loadRepls());
await dispatch(checkEligibile());
}catch (e) { console.log(e)}
}
componentWillMount() {
this.ajax().then(() => {
this.setState({ loaded: true });
});
}
render() {
const { loaded } = this.state;
return loaded ? <Component/> : null;
}
This gets the desired results, except I see this error:
ExceptionsManager.js:71 Warning: Can only update a mounted or mounting
component. This usually means you called setState, replaceState, or
forceUpdate on an unmounted component. This is a no-op.
I tried dispatching in mapDispatchToProps. Ideally loaded should return true and I should see this.props.loaded = true to load my component. However, I'm receiving a Promise and not the result.
I'm feeling stuck here and not sure what else to try. Any suggestions?
const loadAsync = async dispatch => {
dispatch(loadPack('ars'));
dispatch(loadPack('acr'));
await dispatch(loadProds());
await dispatch(loadRepls());
await dispatch(checkEligibile());
return true
};
export const mapDispatchToProps = dispatch => ({
loaded: loadAsync(dispatch),
});
Since you are using redux, you have a global redux state. So after all dispatch, dispatch one more action that toogle a reducer state to true which indicate that all the actions has been dispatched.
In component, use dispatchStateToProps method to convert reducer state into props and then use that prop to check weather all the actions has been dispatched or not. It should roughly look something like this
ajax = async () => {
try{
const { dispatch } = this.props;
dispatch(loadPack('ars'));
dispatch(loadPack('acr'));
await dispatch(loadProds());
await dispatch(loadRepls());
await dispatch(checkEligibile());
// Add one more action here
await dispatch(everythingDispatched());
}catch (e) { console.log(e)}
}
Then a reducer state that keep track of that
dispatchTrackerReducer.js
switch(action.type){
case "everythingDispatched" :
everythingDispatched: true
break;
}
Then in component use mapStateToProps like this
render() {
const { everythingDispatched } = this.props;
return everythingDispatched ? <Component/> : null;
}
function mapStateToProps(state){
return {
everythingDispatched:state.dispatchTrackerReducer.everythingDispatche
}
}
Related
I've a simple app built on redux-toolkit. I am dispatching createProduct actions which is working fine. I want to navigate to /products/ page form /products/new page after createProduct action. How can I use navigate (react-router-dom) to do this.
I tried this inside action but failes
[createProduct.fulfilled]: (state, { payload }) => {
toast.success('Product Created Successfully!');
const navigate = useNavigate()
navigate('/products')
return {
...state,
loading: false,
products: state.products ? [...state.products, payload.product] : [payload.product]
};
},
I also tried passing navigate to payload but I encountered this error :
You may not call store.getState() while the reducer is executing. The reducer has already received the state as an argument. Pass it down from the top reducer instead of reading it from the store.
I am dispatching createProduct like this
const handleSubmit = async () => {
console.log('formik.values', formik.values);
dispatch(
createProduct({
...formik.values,
category: formik.values.category._id,
subCategory: formik.values.subCategory._id
})
)
};
Reducer functions are pure functions, you can't issue the navigation action from the reducer, but you can from the asynchronous action or in the calling component. React hooks are also only valid in React functions or custom hooks.
Asynchronous actions return a Promise. You can chain from the resolved Promise, or await it, and issue the imperative navigation.
const navigate = useNavigate();
Using Promise chain:
const handleSubmit = () => {
dispatch(createProduct({
...formik.values,
category: formik.values.category._id,
subCategory: formik.values.subCategory._id
}))
.then(() => {
navigate('/products');
});
};
or async/await:
const handleSubmit = async () => {
try {
await dispatch(createProduct({
...formik.values,
category: formik.values.category._id,
subCategory: formik.values.subCategory._id
}));
navigate('/products');
} catch(error) {
// handle any rejections/errors
}
};
On the outside it seems not an issue, but when I open the DevTools and then go to network tab. It shows that there are 500 requests made. So how can I refactor the code so this will not happens?
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
"https://raw.githubusercontent.com/XiteTV/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
console.log('input');
} catch (err) {
console.log(err);
}
};
fetchData();
}, [dispatch]);
with redux first create a function which will push your request data into redux state like that outside your react component
export const getMyData = () => {
return async (dispatch) => {
const response = await axios.get(
"https://raw.githubusercontent.com/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
}
}
create a function that will extract data from redux. state is redux current state, ownProps is your component props, like <Component customProp={1}/>
const mapStateToProps = (state: any, ownProps: any) => {
return {
...ownProps,
myProps: state.user
};
};
In your connect Function pass the function which will store your data in redux state
export default connect(mapStateToProps, { getMyData })(MyReactComponent);
that way you'll be able to access your data via your component props and also access your getMyData function and also the redux state you mapped to props
props.getMyData();
I've learned of 2 methods to redirect to a different page after executing an async action with Redux Thunk:
1-Method: Passing "history" object to the async action as argument.
In your component you define the "history" object with "useHistory" hook and pass it to your async action:
function Register(){
const history = useHistory()
const dispatch = useDispatch()
function registerHandler(){
dispatch(registerAsync(registerForm, history))\
}
return (
// JSX Code
<button onClick={registerHandler}>Register</button>
)
}
Then in your async action, you can use "history.push()" to redirect:
export function registerAsync(data, history){
return async function (dispatch) {
try {
const response = await Axios.Post('api/register/', data)
history.push('/register_success')
} catch (e) {
dispatch(registerError(e))
}
}
}
2-Method: Using the < Redirect > component that gets conditionally rendered depending on a Redux store value:
In the component you return conditionally if a store value is true:
function Register(){
const dispatch = useDispatch()
const registerSuccess = useSelector((store) => store.auth.registerSuccess)
function registerHandler(){
dispatch(registerAsync(registerForm, history))\
}
if (registerSuccess) {
return <Redirect push to="/register_success"/>
}
return (
// JSX Code
<button onClick={registerHandler}>Register</button>
)
}
And inside our Async action we dispatch an action that sets the "registerSuccess" to true:
export function registerAsync(data){
return async function (dispatch) {
try {
const response = await Axios.Post('api/register/', data)
dispatch(registerSuccess())
} catch (e) {
dispatch(registerError(e))
}
}
}
Reducer:
case actionTypes.REGISTER_SUCCESS:
newState.registerSuccess = true
return newState
Does anyone know which of the 2 methods is correct and why?
Many thanks!
I have a React / Redux / Meteor app in which I dispatch an action, that calls a method to get a value from the server, and the method has a callback in which I dispatch an action to save the returned value in the Redux store.
I'm also using Redux thunk.
Although my original action is only dispatched once, it runs twice. It seems that dispatching an action from inside a method callback, is causing the original action to be dispatched again.
In my React component:
class MyComponent extends Component {
....
render() {
...
}
}
function mapStateToProps(state, ownProps) {
return { value: state.myPartialState.value }
}
const Tracker = withTracker(({dispatch}) => {
const state = store.getState();
const isLoading = getIsLoading(state);
...
const handle = Meteor.subscribe('myData'), {
onReady: () => {
'onReady': () => {
secondaryPatternSubscriptions(patterns);
},
});
if (isLoading && handle.ready()) {
console.log('about to dispatch original action');
dispatch(getValue());
dispatch(setIsLoading(false));
} else if (!isLoading && !handle.ready()) {
dispatch(setIsLoading(true));
}
return { ... }
)(MyComponent);
export default connect(mapStateToProps)(Tracker);
In my actions file:
export const SET_VALUE = 'SET_VALUE';
export function setValue(value) {
return {
'type': 'SET_VALUE',
'payload': value,
};
}
export const getValue = () => (dispatch, getState) => {
console.log('about to call');
Meteor.call('getValue', (error, result) => {
console.log('about to dispatch second action');
dispatch(setValue(result)); // this causes the action to be dispatched again
});
// dispatch(setValue(10)); // this only runs once
};
const initialState = {
value: 0,
}
export default function myPartialState(state = initialState, action) {
switch (action.type) {
case SET_VALUE: {
return updeep({ 'value': action.payload }, state);
}
}
}
On the server, the method is like this:
Meteor.methods({
'getValue': function () {
...
return value;
},
})
I can see from the console logs that getValue is only dispatched once, but runs twice. I have checked this again and again, and I'm pretty near 100% sure that getValue is not dispatched twice.
I think it's something to do with calling an action from inside the method callback; if I comment out dispatch(setValue(result)); and replace it with a dispatch outside the method call, then getValue only runs once.
If I dispatch a different action instead of setValue, or change the setValue action so that it doesn't alter the 'value' property in the store, then again getValue only runs once. But I can't see why changing 'value' would cause the action to be run twice, when it is only dispatched once...
I've searched online and haven't found anything about this issue.
Can anybody think why my action is running twice, and a way to have it run only once? Thanks!
I've fired an action in componentDidMount :
componentDidMount() {
this.props.getQuests();
}
and would like to fire another action once it's completed, I've used the lifecycle method componentWillReceiveProps
componentWillReceiveProps = async nextProps => {
if (nextProps.questObject.length !== 0) {
const authToken = await AsyncStorage.getItem("authToken");
this.props.getProfile(authToken);
}
};
However only this.props.getQuests(); is fired and the 2nd action this.props.getProfile(authToken) is not being fired.
const mapStateToProps = ({ quest, profile }) => {
const { error, loading, questObject } = quest;
const { profileObj } = profile;
return { error, loading, questObject, profileObj };
};
export default connect(
mapStateToProps,
{ getQuests, getProfile }
)(HomeScreen);
Currently it returns the following error:
Warning: Can't call setState (or forceUpdate) 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
the componentWillUnmount method.
You are trying to update the DOM before it is mounted.when you are using componentWillReceiveProps,make sure you compare the this.props and the latest props i.e., nextProps.Otherwise,componentWillReceiveProps will render the component unnecessarily so that might lead to infinite loop.
componentWillReceiveProps = nextProps => {
if (this.props.questObject !== nextProps.questObject &&
nextProps.questObject.length !== 0) {
try {
const authToken = await AsyncStorage.getItem("authToken");
this.props.getProfile(authToken);
}
catch(e) {
console.log(e);
}
}
};