I learn react and try to get Redux to work so I use the Redux-logger. When dispatching two actions from App.js it works as the top image show "ALBUME_DATA_LOADED".
Then I make a dispatch from from another place and get this output:
I'm not sure I sent that "object, object" action I place breakpoint and console log and it's strange the react-logger it catching an action that I dont think I sent..
Any idea?
Here is the action types I use in the below code as userActionTypes:
File user.types.js:
export const userActionTypes = {
SAVE_USER_START: 'SAVE_USER_START',
SAVE_USER_SUCCESS: 'SAVE_USER_SUCCESS',
SAVE_USER_FAILURE: 'SAVE_USER_FAILURE',
};
Here is the action:
File user.actions.js;
import { userActionTypes } from './user.types';
import { withFirebase } from '../../firebase';
import * as ROLES from '../../constants/roles';
const saveUserStart = () => ({
type: userActionTypes.SAVE_USER_START,
});
const saveUserSuccess = user => ({
type: userActionTypes.SAVE_USER_SUCCESS,
payload: user,
});
const saveUserFailure = errMsg => ({
type: userActionTypes.SAVE_USER_FAILURE,
payload: errMsg,
});
const asyncSaveUser = ({ firestore }) => {
return async dispatch => {
const userRef = firestore.userDoc(firestore.auth.currentUser.uid);
dispatch(saveUserStart());
firestore.db
.runTransaction(transaction => {
// This code may get re-run multiple times if there are conflicts.
return transaction.get(userRef).then(doc => {
if (!doc.exists) {
return Promise.reject('Transaction failed: User dont exist!');
}
const newRoles = doc.data().roles;
// new roll
newRoles.push(ROLES.USER);
// remove roll
newRoles.splice(newRoles.indexOf('ANONYMOUS'), 1);
// save it back
transaction.update(userRef, { roles: newRoles });
return newRoles;
});
})
.then(newRoles => {
dispatch(saveUserSuccess());
console.log(`Transaction successfully committed role(s): ${newRoles}`);
})
.catch(error => {
dispatch(saveUserFailure(error));
console.log(error);
});
};
};
export default withFirebase(asyncSaveUser);
in dispatch saveUserSuccess(), you can't pass newRoles.
dispatch(saveUserSuccess(newRoles));
The reason for this is your mapDispatchToProps.
const mapDispatchToProps = dispatch => ({
saveUser: () => dispatch(asyncSaveUser())
});
asyncSaveUser() is not an action creator.
Related
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'm learning redux and i'm reading a react/redux source-code. in actions/Video.js file it has this code:
export const startDownloading = url => dispatch => {
ipcRenderer.send("video:url", { url });
dispatch({
type: START_DOWNLOADING
});
};
export const getVideoInfo = info => {
return { type: GET_VIDEO_INFO, payload: info };
};
So what's the difference between:
export const startDownloading = url => dispatch => {
ipcRenderer.send("video:url", { url });
dispatch({
type: START_DOWNLOADING
});
};
and
export const startDownloading = url => {
ipcRenderer.send("video:url", { url });
return {
type: START_DOWNLOADING
};
};
I mean, when we should return an object and when we should call dispatch function in an action file?
the difference relies on how to fire a change in your store,
by default calling an action will not fire the event that is going to be handle by the reducer
so you call dispatch either and object or a function
function
export const someFunction = () => ({type: 'actioncreator'})
dispatch(someFunction())
object
dispatch({type: 'actioncreator'}
you can dispatch actions inside a component or inside a actions folder
Im trying to make my own Blog App but it doesn't seem to work as expected. Here are my codes for my "startSetMyBlogs":
export const startSetMyBlogs = () => {
return (dispatch, getState) => {
const myBlogs = [];
const blogRef = database.ref('blogs/')
const uid = getState().auth.uid;
//join table to get blogs match with logging in user
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
// Dispatch another action to set redux state.
dispatch(setUserBlogs(myBlogs));
})
}
}
my "setUserBlogs" action:
export const setUserBlogs = (myBlogs) => ({
type: 'SET_USER_BLOGS',
myBlogs
})
So how can I wait for "setUserBlogs" action to finish before passing props to my BlogList component ?
All you need to do is to store and pass on a loading state until your data is ready
export const startSetMyBlogs = () => {
return (dispatch, getState) => {
const myBlogs = [];
const blogRef = database.ref('blogs/')
const uid = getState().auth.uid;
//join table to get blogs match with logging in user
dispatch({type: 'SET_LOADING', payload: true});
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
// Dispatch another action to set redux state.
dispatch(setUserBlogs(myBlogs));
dispatch({type: 'SET_LOADING', payload: false});
})
}
}
Once you do that you can use this loading state in the component to render a loader
const mapStateToProps = state => {
blogs: state.blogs,
isLoading: state.isLoading
}
Use it as below: Dispatch an action once you get the as you want it from Firebase.
return database.ref(`author-blog`)
.once('value', snap => snap.val())
.then(childSnapshot => {
childSnapshot.forEach((blog) => {
if (blog.val() == uid) {
blogRef.child(blog.key).once('value').then(blogSnapshot => {
myBlogs.push({
id: blogSnapshot.key,
...blogSnapshot.val()
})
})
}
})
dispatch(setUserBlogs(myBlogs));
})
NOTE: API call is async so you have to wait for the data to assign it to state.
Hope this helps.
Guys i am having some trouble or quite doubtful.
am having one component and one reducer.
Reducer.js
import {
ASSET_POPUP_GET_ENDPOINT,
} from 'apiCollection';
import { performGet } from 'services/rest-service/rest-service';
export const GET_ASSETS_LIST = 'stories/GET_ASSETS_LIST';
const initialState = {
imgGroup: [],
isLoading: false,
};
const modalUploadReducer = (state = initialState, action) => {
switch (action.type) {
case GET_ASSETS_LIST: {
return {
...state,
ImageJson:action.payload.imageGroup,
};
}
case GET_ASSETS_LIST_ERROR: {
return {
...state,
isLoading:false,
};
}
default:
return state;
}
};
export const getModalClose = () => (dispatch) => {
dispatch({ type: CLOSE_MODAL });
}
export const getListActionDispactcher = () => (dispatch) => {
performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
export default modalUploadReducer;
and my component look like
it do have mapStateToProps and mapDispatchToProps
and one of the function
const mapDispatchToProps = dispatch => ({
getCollection: () => dispatch(getListActionDispactcher()),
});
addDocumentClick = () =>{
this.props.getAssetsCollection();
}
and is it possible to have some setState/manipulation of response after api response got from reducer in the component
based on the response i need to do some changes in addDocumentClick.
Means something like this
addDocumentClick = () =>{
this.props.getAssetsCollection().then(...based on response;
}
The correct way for solving this is setting a global loading flag and in your componentDidUpdate() method, checking for the value to determine that the action has just succeeded. You already seem to have the isLoading flag. Just set it when the action's dispatched, and unset it after it succeeds/fails. And in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.isLoading && !this.props.isLoading) {
// do something
}
}
Of course, you need to connect() your loading flag to your component to achieve this.
If all you care about is whether the assets list has changed, you can simply check for the change of that prop in componentDidUpdate():
function componentDidUpdate(prevProps) {
if (prevProps.ImageJson !== this.props.ImageJson) {
// do something
}
}
Another solution is sending a callback to your action dispatcher, which makes your code more tightly coupled and I don't recommend, but it does work too. So, when you connect(), you can:
getCollection: (onSuccess) => dispatch(getListActionDispactcher(onSuccess)),
In your action dispatcher:
export const getListActionDispactcher = (onSuccess) => (dispatch) => {
// ...once API finished/failed
onSuccess(someData);
}
Finally, in your component:
this.props.getCollection((result) => {
console.log('succeeded!', result);
// hide modal, etc..
}
You are using redux-thunk, and calling thunk will return a promise which will resolve in whatever you return in your thunk. Therefore, all you need to do is to add return value to getListActionDispactcher
export const getListActionDispactcher = () => (dispatch) => {
// return this promise
return performGet(`${ASSET_POPUP_GET_ENDPOINT}`)
.then((response) => {
const payload = response.data;
dispatch({ type: GET_ASSETS_LIST,
payload: {
...payload,
data: payload.results,
} });
// return whatever you want from promise
return payload
})
.catch((err) => {
dispatch({ type: GET_ASSETS_LIST_ERROR, payload: err });
throw err;
});
};
.
addDocumentClick = () => {
this.props.getAssetsCollection().then(payload => console.log(payload))
}
You should, however, look for ways to avoid this pattern to have your components decoupled from actions as much as possible for the sake of modularity
My app uses React, Redux and Thunk.
Before my app renders I wish to dispatch some data to the store.
How can I make sure the ReactDOM.render() is run after all dispatches has finished?
See my code below
index.js
const setInitialStore = () => {
return dispatch => Promise.all([
dispatch(startSubscribeUser()),
dispatch(startSubscribeNotes()),
]).then(() => {
console.log('initialLoad DONE')
return Promise.resolve(true)
})
}
store.dispatch(setInitialStore()).then(()=>{
console.log('Render App')
ReactDOM.render(jsx, document.getElementById('app'))
})
Actions
export const setUser = (user) => ({
type: SET_USER,
user
})
export const startSubscribeUser = () => {
return (dispatch, getState) => {
const uid = getState().auth.id
database.ref(`users/${uid}`)
.on('value', (snapshot) => {
const data = snapshot.val()
const user = {
...data
}
console.log('user.on()')
dispatch(setUser(user))
})
}
}
export const setNote = (note) => ({
type: SET_NOTE,
note
})
export const startSubscribeNotes = () => {
return (dispatch, getState) => {
database.ref('notes')
.on('value', (snapshot) => {
const data = snapshot.val()
const note = {
...data
}
console.log('note.on()')
dispatch(setNote(note))
})
}
}
My log shows
"initialLoad DONE"
"Render App"
...
"user.on()"
"note.on()"
What I expect is for user.on() and note.on() to be logged before initialLoad DONE and Render App
Many thanks! /K
I'm pretty sure this is because startSubscribeUser and startSubscribeNotes don't return a function returning a promise.
Then, what happens in this case, is that the database.ref is not waited to be completed before executing what's in the next then.
I don't know exactly what that database variable is, but this should work :
return new Promise(resolve => {
database.ref(`users/${uid}`)
.on('value', (snapshot) => {
const data = snapshot.val()
const user = {
...data
}
console.log('user.on()')
dispatch(setUser(user))
resolve()
})
})