I'm trying to get user info from database. In component I'm getting decoded id from service, then call the action which takes the id as parameter. It returns the user, there is response in network tab. The state property 'currentUser' is null all the time until it should change to response, then it disappears.
export interface State {
loading: boolean;
loggedIn: boolean;
currentUser: User;
}
const initialState: State = {
loading: false,
currentUser: null,
loggedIn: localStorage.getItem("token") ? true : false
};
case AuthActions.GET_USER_SUCCESS:
{
return {
...state,
loading: false,
loggedIn: true,
currentUser: action.user
};
}
#Effect()
getUserInfo$: Observable < Action > = this.actions$
.ofType(fromActions.GET_USER)
.pipe(map((action: fromActions.GetUser) => action.id),
concatMap(id => {
return this.authService.getUser(id);
})
)
.pipe(map((res: User) => ({
type: fromActions.GET_USER_SUCCESS,
payload: res
})));
}
Try it like this:
#Effect()
getUserInfo$: Observable<Action> = this.actions$
.ofType(fromActions.GET_USER)
.pipe(
map((action: fromActions.GetUser) => action.id),
concatMap(id =>
this.authService.getUser(id).pipe(
map((res: User) => ({
type: fromActions.GET_USER_SUCCESS,
payload: res
}))
)
)
);
What is the shape of your action class? I can see you dispatch an action in the shape of
{
type: fromActions.GET_USER_SUCCESS,
payload: res
}
but in your reducer you expect it to have a user property on it
case AuthActions.GET_USER_SUCCESS:
{
return {
...state,
loading: false,
loggedIn: true,
currentUser: action.user // <- try action.payload or action.payload.user,
// depending on what you get from the API
};
}
Also, try to shape your effect more like this:
#Effect()
getUserInfo$: Observable <Action> = this.actions$
.ofType(fromActions.GET_USER)
.pipe(
switchMap(({ id }) => this.authService.getUser(id)
.pipe(
map((res: User) => ({ type: fromActions.GET_USER_SUCCESS, payload: res }),
// Handling errors in an inner Observable will not terminate your Effect observable
// when there actually is an error
catchError(err => ({ type: fromActions.GET_USER_ERROR, payload: err })
)
)
);
Hope this helps a little :)
Related
Using react.js & firebase
The code below represents a simple button which increases/decreases +1/-1 whenever its clicked. It also updates one of the documents on the backend (using firebase). Everything seems to work fine on the surface but not on firebase. When you click on the button, it'll show +1 on the UI and console.log but not on firebase. In other words when plusCount state is at 0, it shows +1 on firebase and when plusCount state is at +1, it shows 0 on firebase. How can I fix this to make sure it shows the same number on the frontend and the backend? I also added the useFirestore hook component below, there may be a mistake that I'm unaware of in there somewhere.
Thank you for any help.
Button component:
import React, { useState } from 'react';
import { useFirestore } from "../../hooks/useFirestore"
export default function Testing({ doc }) {
const { updateDocument } = useFirestore('projects')
const [plusActive, setPlusActive] = useState(false)
const [plusCount, setPlusCount] = useState(0)
function p() {
setPlusActive(prevState => !prevState);
plusActive ? setPlusCount(plusCount - 1) : setPlusCount(plusCount + 1)
}
const handlePlus = (e) => {
e.preventDefault();
p();
updateDocument(doc.id, {
votes: plusCount
})
}
console.log(plusCount)
return (
<div>
<button onClick={handlePlus}>like | {plusCount}</button>
</div>
)
}
useFirestore hook component:
import { projectFirestore, timestamp } from "../firebase/config"
let initialState = {
document: null,
isPending: false,
error: null,
success: null,
}
const firestoreReducer = (state, action) => {
switch (action.type) {
case 'IS_PENDING':
return { isPending: true, document: null, success: false, error: null }
case 'ADDED_DOCUMENT':
return { isPending: false, document: action.payload, success: true, error: null }
case 'DELETED_DOCUMENT':
return { isPending: false, document: null, success: true, error: null }
case 'ERROR':
return { isPending: false, document: null, success: false, error: action.payload }
case "UPDATED_DOCUMENT":
return { isPending: false, document: action.payload, success: true, error: null }
default:
return state
}
}
export const useFirestore = (collection) => {
const [response, dispatch] = useReducer(firestoreReducer, initialState)
const [isCancelled, setIsCancelled] = useState(false)
// collection ref
const ref = projectFirestore.collection(collection)
// only dispatch if not cancelled
const dispatchIfNotCancelled = (action) => {
if (!isCancelled) {
dispatch(action)
}
}
// add a document
const addDocument = async (doc) => {
dispatch({ type: 'IS_PENDING' })
try {
const createdAt = timestamp.fromDate(new Date())
const addedDocument = await ref.add({ ...doc, createdAt })
dispatchIfNotCancelled({ type: 'ADDED_DOCUMENT', payload: addedDocument })
}
catch (err) {
dispatchIfNotCancelled({ type: 'ERROR', payload: err.message })
}
}
// delete a document
const deleteDocument = async (id) => {
dispatch({ type: 'IS_PENDING' })
try {
await ref.doc(id).delete()
dispatchIfNotCancelled({ type: 'DELETED_DOCUMENT' })
}
catch (err) {
dispatchIfNotCancelled({ type: 'ERROR', payload: 'could not delete' })
}
}
// update a document
const updateDocument = async (id, updates) => {
dispatch({ type: "IS_PENDING" })
try {
const updatedDocument = await ref.doc(id).update(updates)
dispatchIfNotCancelled({ type: "UPDATED_DOCUMENT", payload: updatedDocument })
return updatedDocument
}
catch (error) {
dispatchIfNotCancelled({ type: "ERROR", payload: error })
return null
}
}
useEffect(() => {
return () => setIsCancelled(true)
}, [])
return { addDocument, deleteDocument, updateDocument, response }
}```
For your use-case, you should useEffect() to listen the changes for plusCount. See code below:
useEffect(() => {
updateDocument('test', {
votes: plusCount
})
}, [plusCount]);
const handlePlus = (e) => {
e.preventDefault();
setPlusActive(prevState => !prevState);
plusActive ? setPlusCount(plusCount - 1) : setPlusCount(plusCount + 1)
}
Everytime you click the button it will listen to the changes of plusCount which then the updateDocument will also be triggered together with the updated state. See below screenshot for the result:
As you can see, the frontend and backend is now aligned.
You can find more information by checking out this documentation.
When I send request I got my data back in json and I can see it when console.log().
But when I try to change my state, it's not changing. Can you guys please help me to understand why? Don't judge too hard I am still learning. Thank you
Here is my code
export const login = createAsyncThunk(
'auth/login',
async (user) => {
try {
const response = await loginUser(user);
console.log(response.data) /// data present
return response.data
} catch (error) {
console.log(error)
}
}
)
export const authSlice = createSlice({
name: 'auth',
initialState: { user: {}, status: '', message: '', success: false, error: '' },
reducers: {
[login.pending]: (state, action) => (
state.status = 'loading'
),
[login.fulfilled]: (state, { payload }) => {
state.user = payload.user
state.status = 'success'
state.message = payload.message
state.success = true
state.error = ''
},
[login.rejected]: (state, { payload }) => {
state.user = payload
state.status = 'failed'
state.success = false
state.error = payload
}
}
})
You need use the extraReducers with builder, something like that :
reducers: {
//something, we don't care it
},
extraReducers: (builder) => {
builder.addCase(login.pending, (state) => {
state.status = what you want
});
builder.addCase(login.fulfilled, (state) => {
blabla
});
... and again and again
},
see https://redux-toolkit.js.org/api/createAsyncThunk (all is on the doc, no better source for RDK)
I have an authentication action on an react native app which must during the authentication go to perform another action but it is never executed (dispatch(getMobiles())). I do not understand why. Do you have an idea ?
If my authentication went well, I immediately want to retrieve data on my new users, so I want to execute getMobiles () which is another action.
thanks in advance :)
auth actions
export const authentication = (
username: String,
password: String,
label: String,
synchro: Boolean,
url: String,
) => {
return dispatch => {
dispatch({type: LOGIN.PENDING, payload: ''});
const type = UDA_URL_LIST.map(uda => {
if (uda.url === url) {
return uda.name;
}
})
.join()
.replace(/[, ]+/g, ' ')
.trim();
fetchUser(url, username.trim(), password.trim())
.then(response => {
if (!response.err) {
const newUser = {
...response,
label,
type,
synchro,
};
dispatch({type: LOGIN.SUCCESS, payload: newUser});
// not dispatched !
return dispatch(getMobiles(url, response.key, newUser.userId));
}
})
.catch(err => dispatch({type: LOGIN.ERROR, payload: err}));
};
};
getMobiles
export const getMobiles = (
url: String | null = null,
token: String,
userId: String,
) => {
return dispatch => {
dispatch({type: MOBILES.PENDING, payload: ''});
fetchMobiles(url, token)
.then(mobilesList => {
dispatch({
type: MOBILES.SUCCESS,
payload: mobilesList.data,
meta: {userId},
});
})
.catch(err => alert(err));
};
};
};
Your code in getMobiles require second call with parametr dispatch,
try to use getMobiles(url, response.key, newUser.userId)(dispatch)
Bit of a weird one. In my component I am getting a "task" object from my "taskDetails" reducer. Then I have a useEffect function that checks if the task.title is not set, then to call the action to get the task.
However when the page loads I get an error cannot read property 'title' of null which is strange because when I look in my Redux dev tools, the task is there, all its data inside it, yet it can't be retrieved.
Here is the relevant code:
const taskId = useParams<{id: string}>().id;
const dispatch = useDispatch();
const history = useHistory();
const [deleting, setDeleting] = useState(false)
const taskDetails = useSelector((state: RootStateOrAny) => state.taskDetails);
const { loading, error, task } = taskDetails;
const successDelete = true;
const deleteTaskHandler = () => {
}
useEffect(() => {
if(!successDelete) {
history.push('/home/admin/tasks')
} else {
if(!task.title || task._id !== taskId) {
dispatch(getTaskDetails(taskId))
}
}
},[dispatch, task, taskId, history, successDelete])
REDUCER
export const taskDetailsReducer = (state = { task: {} }, action) => {
switch(action.type) {
case TASK_DETAILS_REQUEST:
return { loading: true }
case TASK_DETAILS_SUCCESS:
return { loading: false, success: true, task: action.payload }
case TASK_DETAILS_FAIL:
return { loading: false, error: action.payload }
case TASK_DETAILS_RESET:
return { task: {} }
default:
return state
}
}
ACTION
export const getTaskDetails = id => async (dispatch) => {
try {
dispatch({
type: TASK_DETAILS_REQUEST
})
const { data } = await axios.get(`http://localhost:5000/api/tasks/${id}`)
dispatch({
type: TASK_DETAILS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: TASK_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}
}
In my reducer in the TASK_DETAILS_REQUEST case, I just had loading: false.
I had failed to specify the original content of the state, I did this by adding ...state.
export const taskDetailsReducer = (state = { task: {} }, action) => {
switch(action.type) {
case TASK_DETAILS_REQUEST:
return { ...state, loading: true }
case TASK_DETAILS_SUCCESS:
return { loading: false, success: true, task: action.payload }
case TASK_DETAILS_FAIL:
return { loading: false, error: action.payload }
case TASK_DETAILS_RESET:
return { task: {} }
default:
return state
}
}
I have a modal to add a todo item that resets after submission but it also resets if the submission fails, How do I make it so my modal stays open and user can see the errors they made?
//modal component
onSubmit = e => {
e.preventDefault();
const newTask = {
task: this.state.task
};
this.props.addTask(newTask)
// sudo code below
if(this.props.addTask(newTask === 200 status success or something){
this.setState({task: "" })
//Close modal
this.toggle();
}
}
// action file
export const addTask = (task) => dispatch =>{
axios.post('/api/users/newtask', task )
.then(res =>
dispatch({
type: ADD_TASK,
payload: res.data
})
).catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Not sure if it helps but I'm using axios for the api calls
You have 2 ways of doing this:
A callback that you can pass into your dispatch action:
//modal component
onSubmit = e => {
e.preventDefault();
const newTask = {
task: this.state.task
};
this.props.addTask(newTask, () => {
this.setState({task: "" })
//Close modal
this.toggle();
});
}
// action file
export const addTask = (task, successCallback) => dispatch =>{
axios.post('/api/users/newtask', task )
.then(res => {
dispatch({
type: ADD_TASK,
payload: res.data
});
if (typeof successCallback === 'function') {
successCallback(res.data);
}
)
).catch(err =>
dispatch({
type: GET_ERRORS,
payload: err.response.data
})
);
}
Ideally, you should be doing this via your redux actions/reducers:
//modal component (Or don't render the modal at all at the parent component)
...
render() {
if (!this.props.showModal) {
return null;
}
}
// Action:
dispatch({
type: ADD_TASK,
payload: res.data
});
//Reducer
function reducer(state = initialState, action) {
switch (action.type) {
case ADD_TASK:
return Object.assign({}, state, {
tasks: [...state.tasks, action.task],
showModal: false, // <== Set show modal to false when the task is done.
})
default:
return state
}
}