I have a react application that should sign up a user and add the user's info to a collection using the uid. I am using redux and have broken my code into component, reducer and action. This is the add user component:
state = {
name : '',
email : '',
password : '',
position : '',
department : '',
}
handleChange = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
this.props.signUp(this.state)
}
render() {
return(//I have my input fields and submit button here)
}
const mapStateToProps = (state) => {
return {
auth: state.firebase.auth
}
}
const mapDispatchToProps = (dispatch) => {
return {
signUp: (newUser) => dispatch(signUp(newUser))
}
}
export default connect (mapStateToProps, mapDispatchToProps)(AddUser);
I do my auth action in the authAction with:
export const signUp = (newUser) => {
return (dispatch, getState, {getFirebase, getFirestore}) => {
const firebase = getFirebase();
const firestore = getFirestore();
firebase.auth().createUserWithEmailAndPassword(
newUser.email,
newUser.password
).then((resp) =>{
return firestore.collection('users').doc(resp.user.uid).set({
name: newUser.name,
position: newUser.position,
department: newUser.department
})
}).then(() => {
dispatch({ type: 'SIGNUP_SUCCESS' })
}).catch(err => {
dispatch({ type: 'SIGNUP_ERROR', err})
})
}
}
my Sign Up reducer is:
case 'SIGNUP_SUCCESS':
console.log('Signup success');
return {
...state,
authError: null
}
case 'SIGNUP_ERROR':
console.log('signup error');
return {
...state,
authError: action.err.message
}
The issue is that this creates a user and logs the user in but doesn't create a document with the user data in the firestore collection. I do not get any errors on the console. it also doesn't log the success message. The user is created and can log in though.
What am I doing wrong? Please help.
Thanks!
Do I understand right that it is an async process? If yes, you should also use Redux-Saga library for this(I mean it would be better practice).
However, if you are not willing to implement that try adding debugger or console log data in sign up action before return to see if you passing the right data in the right format for firebase. Same goes to catch block, console log error, I may say useful information if the error is in request/response.
Related
I'm new to this and read many tutorial and docs about Redux Thunk and Higher Order Components. I am trying to wire this together but I can't understand what's wrong here.
This image show what's happening: my inner Thunk function save() is not called for some reason.
It works if I change the return withFirebase(save); to return save; but I need the Firebase Context so how can I solve this and why does react-thunk care about the withFirebase?
This is the Action:
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,
});
function saveUser() {
return dispatch => {
function save({ firebase }) {
const userRef = firebase.userDoc(firebase.auth.currentUser.uid);
dispatch(saveUserStart());
firebase.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(firebase.auth.currentUser));
console.log(`Transaction successfully committed role(s): ${newRoles}`);
})
.catch(error => {
dispatch(saveUserFailure(error));
console.log(`Transaction failed committed role(s): ${error}`);
});
}
return withFirebase(save);
};
}
export default saveUser;
I'm fetch some data from my API and it correctly works. But when a double dispatch on the same page the API doesn't work anymore. It's better code to explain it:
Server:
router.get("/", (req, res) => {
let sql = "SELECT * FROM design_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
router.get("/", (req, res) => {
let sql = "SELECT * FROM food_categories";
let query = connection.query(sql, (err, results) => {
if (err) throw err;
res.header("Access-Control-Allow-Origin", "*");
res.send(results);
});
});
They work.
action.js
export const fetchDesignCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/designcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_DESIGN_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
export const fetchFoodCat = () => {
setLoading()
return async dispatch => {
const response = await axios
.get("http://localhost:5000/api/foodcategories")
.then(results => results.data)
try {
await dispatch({ type: FETCH_FOOD_CAT, payload: response })
} catch (error) {
console.log("await error", error)
}
}
}
Both of them work perfectly.
reducer.js
const initalState = {
db: [],
loading: true,
designcat: [],
foodcat: [],
}
export default (state = initalState, action) => {
switch (action.type) {
// different cases
case FETCH_DESIGN_CAT:
return {
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
food: action.payload,
loading: false,
}
}
The reducer updates the states perfectly.
Page settings.js
const Settings = ({ designcat, foodcat, loading }) => {
const dispatch = useDispatch()
// ... code
useEffect(() => {
dispatch(fetchDesignCat()) // imported action
dispatch(fetchFoodCat()) // imported action
// eslint-disable-next-line
}, [])
// ... code that renders
const mapStateToProps = state => ({
designcat: state.appDb.designcat,
foodcat: state.appDb.foodcat,
loading: state.appDb.loading,
})
export default connect(mapStateToProps, { fetchDesignCat, fetchFoodCat })(
Settings
)
Now there's the problem. If I use just one dispatch it's fine I get one or the other. But if I use the both of them look like the if the second overrides the first. This sounds strange to me.
From my ReduxDevTools
For sure I'm mistaking somewhere. Any idea?
Thanks!
Your reducer does not merge the existing state with the new state, which is why each of the actions just replace the previous state. You'll want to copy over the other properties of the state and only replace the ones your specific action should replace. Here I'm using object spread to do a shallow copy of the previous state:
export default (state = initalState, action) => {
switch (action.type) {
case FETCH_DESIGN_CAT:
return {
...state, // <----
designcat: action.payload,
loading: false,
}
case FETCH_FOOD_CAT:
return {
...state, // <----
food: action.payload,
loading: false,
}
}
}
Since the code is abbreviated, I'm assuming you're handling the default case correctly.
As an additional note, since you're using connect with the Settings component, you don't need to useDispatch and can just use the already connected action creators provided via props by connect:
const Settings = ({
designcat,
foodcat,
loading,
fetchDesignCat,
fetchFoodCat,
}) => {
// ... code
useEffect(() => {
fetchDesignCat();
fetchFoodCat();
}, [fetchDesignCat, fetchFoodCat]);
// ... code that renders
};
There's also a race condition in the code which may or may not be a problem to you. Since you start both FETCH_DESIGN_CAT and FETCH_FOOD_CAT at the same time and both of them set loading: false after finishing, when the first of them finishes, loading will be false but the other action will still be loading its data. If this case is known and handled in code (i.e., you don't trust that both items will be present in the state if loading is false) that's fine as well.
The solution to that would be either to combine the fetching of both of these categories into one thunk, or create separate sub-reducers for them with their own loading state properties. Or of course, you could manually set and unset loading.
I am using React Context to store the logged-in users credentials and have created a reducer/action pattern to handle the logic. Right now, I'm able to successfully login with the proper credentials, but I also want to redirect the user to his profile with a dynamic param like so: profile/:id. My problem is, I'm not sure how to access the user's id in the same function as the initial call to Context where I populate it with the user's login credentials. So far, I have:
function Login(props) {
const userContext = useContext(UserContext);
const [credentials, setCredentials] = useState({
username: '',
password: ''
});
async function onSubmit (e) {
e.preventDefault();
const user = await userContext.loginUser(credentials); // Wait for payload...
props.push.history(`/profile${user.id}`); // ...Then use payload (user.id) to redirect to desired endpoint
}
For reference, here is my loginUser action:
const loginUser = async credentials => {
setLoading();
const response = await axios
.post('endpoint here', credentials)
dispatch({
type: LOGIN_USER,
payload: response.data
})
}
And here is my reducer with my initial state below it:
export default (state, action) => {
switch(action.type) {
case SET_LOADING:
return {
...state,
loading: true
}
case LOGIN_USER:
return {
...state,
user: action.payload,
loading: false
}
default:
return state;
}
}
const initialState = {
user: {},
issues: [],
loading: false
}
Hopefully, this is enough for someone to spot my shortcoming. Any help is appreciated.
I'm new in Redux React and creating web app where app interact with Lumen API framework. When a request go to server and return with error code 400, 404, 500 any status code (error) except 200 it shows console error and processed after that in React.
I tried pass value when get error at axois.catch({dispatch}) but value update in state by viewing redux-dev-tool but didn't get value at props.
As from API I passed as like:
if ($validator->fails()) {
return response()->json(['type'=> 'error','message'=> $validator->errors()->all()],400);
}
And in my action file as like:
export const loginRequest = formValues => async dispatch => {
await user.post('/user/create', { ...formValues, device_id: 12345}).then(response => {
dispatch ({type: LOGIN, payload: response.data});
}).catch(error => {
if(error.response) {
dispatch ({ type: ERRORS, payload: error.response.data.message });
}
});
}
and in reducer:
const INTIAL_STATE = {
isSignedIn: null,
accessToken: null,
loginMessage: null,
errorMessage: null
};
export default (state = INTIAL_STATE, action) => {
switch (action.type){
case ERRORS:
return { ...state, isSignedIn: false, accessToken:null , errorMessage: action.payload };
default:
return state;
}
};
as last in my component file:
const mapStateToProps = state => {
return {error: state.auth.errorMessage};
};
and console log in render method:
console.log(this.props);
I'm getting POST http://localhost:8000/user/create 500 (Internal Server Error) in my console but the errorMessage value updated in state as looked in redux-dev-tool and after error no code run.
Use the redux-saga-routines for dispatching actions, make your work easy with this module.
Here its documentation link https://github.com/afitiskin/redux-saga-routines
In my form, I'm trying to check email by using reactive-thunk to determine if the email address was already received. Everything is working properly, except for one thing. I request the api and I'm sending the data to the reducer, but the component I have access to the state is empty. Because the state value in the component is working before the reducer.
Is there any help of how to do that?
Submit.js
onSubmit = data => {
const { saveUser, validateEmail, emailValidate } = this.props;
validateEmail(data.email); // action create for api request
console.log(emailValidate); // fetch data in reducer(This data is empty because code run before reducer set state)
if (emailValidate.statusMessage === 'OK') {
throw new SubmissionError({ email: 'Email already in use', _error: 'Login failed!' });
} else {
}
}
const mapDispatchToProps = (dispatch) => {
validateEmail(email) {
dispatch(validateEmail(email));
},
};
};
const mapStateToProps = (state) => ({
emailValidate: state.registrationFormEmailValidate.data,
});
onSubmit = data => {
const { saveUser, validateEmail, emailValidate } = this.props;
validateEmail(data.email); // action create for api request
}
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.emailValidate.statusMessage !== prevProps.emailValidate.statusMessage) {
console.log(this.props.emailValidate);
if (this.props.emailValidate.statusMessage === 'OK') {
throw new SubmissionError({ email: 'Email already in use', _error: 'Login failed!' });
} else {
}
}
}
If you are using class component, you can use componentDidUpdate()