After action is happened component gets an old state anyway. I'm not mutating the state btw. The changes appears only after I refresh the page
My component TodoList.tsx
Here's I'm trying to get state via useEffect hook
export const TodoList: FC = () => {
const {
todoList, isLoading, isError, message
} = useTypedSelector(
(state) => state.todo
)
console.log(todoList)
const { getTodo, reset } = useActions()
useEffect(() => {
getTodo()
console.log("render")
if (isError) {
console.log(message)
}
return () => {
reset()
}
}, [])
const statusHandler = (event: React.ChangeEvent<HTMLParagraphElement>) => {
const status = event.target.innerText
if (status === "completed") {
getTodo({ complete: true })
} else if (status === "uncompleted") {
getTodo({ complete: false })
} else if (status === "all") {
getTodo()
}
}
return (
<>
...
</>
)
}
my Reducer
I'm not mutating the state also.
const initialState: TodoState = {
todoList: [],
isError: false,
isSuccess: false,
isLoading: false,
message: ""
}
export const todoReducer = (state = initialState, { type, payload }: TodoAction): TodoState => {
switch (type) {
case "GET_TODOS":
return { ...state, isLoading: true }
case "GET_TODOS_SUCCESS":
return { ...state, isLoading: false, isSuccess: true, todoList: payload }
case "GET_TODOS_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "CREATE_TODO":
return { ...state, isLoading: false }
case "CREATE_TODO_SUCCESS":
return { ...state, isLoading: false, isSuccess: true, todoList: [...state.todoList, payload]}
case "CREATE_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "TOGGLE_TODO":
return { ...state, isLoading: true }
case "TOGGLE_TODO_SUCCESS":
return {
...state, isLoading: false, isSuccess: true, todoList: state.todoList.map((todo: ITodoItem) => {
if (todo._id === payload.id) {
console.log(payload)
return {
...todo,
complete: !todo.complete
}
}
return todo
})
}
case "TOGGLE_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "DELETE_TODO":
return { ...state, isLoading: true }
case "DELETE_TODO_SUCCESS":
return {
...state, isLoading: false, isSuccess: true, todoList: state.todoList.filter(
(todo: ITodoItem) => todo._id !== payload.id
)
}
case "DELETE_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "EDIT_TODO":
return { ...state, isLoading: true }
case "EDIT_TODO_SUCCESS":
return {
...state, isLoading: false, isSuccess: true, todoList: state.todoList.map((todo: ITodoItem) => {
if (todo._id === payload.id) {
return {
...todo,
text: payload.text
}
}
return todo
})
}
case "EDIT_TODO_ERROR":
return { ...state, isLoading: false, isError: true, message: payload.error }
case "RESET_TODOS":
return { ...state, todoList: state.todoList }
default:
return state
}
}
my action creator
Here I'm making a request from my own api.
...
export const getTodo = (data?: GetTodos) => {
return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
try {
dispatch({ type: "GET_TODOS", payload: {} })
let response
if (data) {
response = await todoService.getTodo((<any>data).complete)
}
response = await todoService.getTodo()
console.log(response)
dispatch({ type: "GET_TODOS_SUCCESS", payload: response })
} catch (err) {
dispatch({
type: "GET_TODOS_ERROR",
payload: { error: "Cannot fetch the todos. Please try again later" }
})
}
}
}
export const createTodo = (payload: CreateTodo) => {
return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
try {
dispatch({ type: "CREATE_TODO", payload: payload })
const response = await todoService.createTodo(payload)
dispatch({ type: "CREATE_TODO_SUCCESS", payload: response })
} catch (err) {
dispatch({
type: "CREATE_TODO_ERROR",
payload: { error: "Cannot create todo. Please try again later" }
})
}
}
}
export const toggleTodo = (payload: ToggleTodo) => {
return async (dispatch: Dispatch<TodoAction>): Promise<void> => {
try {
const response = await todoService.updateTodo(payload.id, { complete: payload.complete })
dispatch({ type: "TOGGLE_TODO_SUCCESS", payload: response })
} catch (err) {
dispatch({
type: "TOGGLE_TODO_ERROR",
payload: { error: `${err}` }
})
}
}
}
...
}
all code is here: https://github.com/maridoroshuk/todos-ts
the issue
useEffect(callback, dependencies) triggers only when data in dependencies changes. In your code, you put useEffect( ... , []), meaning you don't have any dependencies, so the callback function only runs once when the component mounts.
If you want useEffect to run on every update, you need to not put any dependencies: useEffect(...).
Related
I am struggling to figure the issue here.
There is a subscribe and unsubscribe to a podcast.
When I hit either the database gets updated (firebase) but it doesn't trigger a re-render via the useSelector. If I hit the button again, it'll re-render and the useSelector values will update.
Appreciate any help
Here is the component
const PodcastDetailsScreen = ({ route }) => {
const podcastDetails = route.params;
const [podcastEpisodeDetails, setPodcastEpisodeDetails] = useState("");
const {
data: userInfo,
data: { subscriptions: userSubscriptions },
} = useSelector((state) => state.userLogin);
console.log("userInfo111", userInfo);
console.log(userSubscriptions);
useEffect(() => {
getPodcastDetailsLN(podcastDetails.id, setPodcastEpisodeDetails);
}, [podcastDetails.id]);
const timeAgo = new TimeAgo("en-US");
const dispatch = useDispatch();
const subscribeHandler = async () => {
console.log("subscribeHandler");
dispatch(addUserSubscription(userInfo.uid, podcastDetails.id));
};
const unsubscribeHandler = async () => {
console.log("unsubscribeHandler");
dispatch(removeUserSubscription(userInfo.uid, podcastDetails.id));
};
return (
Here are the two actions
// Add user subscription to firestore
export const addUserSubscription = (userId, podcastId) => async (dispatch) => {
try {
dispatch({ type: USER_SUBSCRIPTIONS_REQUEST });
const docRef = doc(db, "users", userId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const subscriptions = docSnap.data().subscriptions;
subscriptions.push(podcastId);
await setDoc(docRef, { subscriptions }, { merge: true });
dispatch({ type: USER_SUBSCRIPTIONS_SUCCESS, payload: subscriptions });
} else {
console.log("2");
dispatch({ type: USER_SUBSCRIPTIONS_FAILURE, payload: "ERROR: No user exists?" });
}
} catch (error) {
dispatch({ type: USER_SUBSCRIPTIONS_FAILURE, payload: error.message });
}
};
// Remove user subscription from firestore
export const removeUserSubscription = (userId, podcastId) => async (dispatch) => {
try {
dispatch({ type: USER_SUBSCRIPTIONS_REQUEST });
const docRef = doc(db, "users", userId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const subscriptions = docSnap.data().subscriptions;
const newSubscriptions = subscriptions.filter((sub) => sub !== podcastId);
await setDoc(docRef, { subscriptions: newSubscriptions }, { merge: true });
dispatch({ type: USER_SUBSCRIPTIONS_SUCCESS, payload: newSubscriptions });
} else {
dispatch({
type: USER_SUBSCRIPTIONS_FAILURE,
payload: "ERROR: no sublist or couldn't find it",
});
}
} catch (error) {
dispatch({ type: USER_SUBSCRIPTIONS_FAILURE, payload: error.message });
}
};
And here are the reducers
// Load user info into state after signup or login
export const userLoginReducer = (state = {}, action) => {
switch (action.type) {
case USER_LOGIN_REQUEST:
return {
...state,
loading: true,
};
case USER_LOGIN_SUCCESS:
return {
...state,
loading: false,
data: action.payload,
};
case USER_LOGIN_FAILURE:
return {
...state,
loading: false,
error: action.payload,
};
// Add user subscription
case USER_ADD_SUBSCRIPTION_REQUEST:
return { ...state, loading: true };
case USER_ADD_SUBSCRIPTION_SUCCESS:
return {
...state,
loading: false,
success: true,
data: { subscriptions: action.payload },
};
case USER_ADD_SUBSCRIPTION_FAILURE:
return { ...state, loading: false, success: false, error: action.payload };
// --------------------------------------------------
// Remove user subscription
case USER_REMOVE_SUBSCRIPTION_REQUEST:
return { ...state, loading: true };
case USER_REMOVE_SUBSCRIPTION_SUCCESS:
return {
...state,
loading: false,
success: true,
data: { subscriptions: action.payload },
};
case USER_REMOVE_SUBSCRIPTION_FAILURE:
return { ...state, loading: false, success: false, error: action.payload };
// --------------------------------------------------
default:
return state;
}
};
I'm trying to delete an user, but the req.body in the backend is an empty object.
In the backend I have the following code:
const deleteUser = async (req, res) => {
console.log(req.body);
console.log(req.config);
const user = await User.findById(req.body.userId);
if (user) {
const deleteUser = await user.remove();
res.send({ message: "User Deleted", user: deleteUser });
} else {
res.status(404).send({ message: "User Not Found" });
}
};
Here the console log is an empty object, I must that the other functions work perfectly.
In the frontend, I'm using redux, I think I'm doing something wrong in the actions, but I can't find out what, I will post all my code for reference.
action.js:
export const deleteUser = (userId) => async (dispatch, getState) => {
dispatch({ type: USER_DELETE_REQUEST, payload: userId });
try {
const { data } = await Axios.delete(
"http://localhost:3030/v1/user/userProfile/deleteUser",
{
userId: userId,
}
);
dispatch({ type: USER_DELETE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: USER_DELETE_FAIL, payload: message });
}
};
In the reducer:
export const userDeleteReducer = (state = {}, action) => {
switch (action.type) {
case USER_DELETE_REQUEST:
return { loading: true };
case USER_DELETE_SUCCESS:
return { loading: false, success: true };
case USER_DELETE_FAIL:
return { loading: false, error: action.payload };
default:
return state;
}
};
And I'm calling the action like that:
const userSignin = useSelector((state) => state.userSignin);
const { userInfo, loading, error } = userSignin;
<button
onClick={() => {
console.log(userInfo._id);
dispatch(deleteUser(userInfo._id));
props.onClose();
}}
className='deleteAccountModalButton'
>
Delete account!
</button>
I tried everything, but I can't find where the problem, can somebody tell me why the req.body is empty in the backend?
EDIT:
I managed to make it work by modifying the order of parameters in actions:
export const deleteUser = (userId) => async (dispatch, getState) => {
dispatch({ type: USER_DELETE_REQUEST, payload: userId });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.delete(
"http://localhost:3030/v1/user/userProfile/deleteUser",
{
data: {
headers: { Authorization: `Bearer ${userInfo.token}` },
userId,
},
}
);
dispatch({ type: USER_DELETE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: USER_DELETE_FAIL, payload: message });
}
};
I will leave this here in case somebody else will have this problem.
I try to get the user information from the API and check that if it is isAuthenticated === true go to the profile page, otherwise to the login page,But since JavaScript does not wait for a response, it executes the code one after the other and checks the condition with the same initialization as false.
What should I do to wait for a response from the API and change the value of isAuthenticated and userInfo and then check the following condition?
const userLoad = useSelector(state => state.userLoad)
const {error, loading, isAuthenticated, userInfo} = userLoad
useEffect(() => {
if (isAuthenticated) {
dispatch(getUserDetails('profile'))
} else {
history.push('/login')
}
}, [dispatch, history, isAuthenticated, userInfo])
I created a userLoadReducer which is as follows:
export const userLoadReducer = (state = {}, action) => {
const {type, payload} = action;
switch (type) {
case USER_LOADED_REQUEST:
return {
...state,
loading: true
}
case USER_LOADED_SUCCESS:
return {
...state,
loading: false,
isAuthenticated: true,
userInfo: payload.user,
}
case USER_LOADED_FAIL:
return {
...state,
loading: false,
isAuthenticated: false,
userInfo: null
}
default:
return state
}
}
And its load_user action is as follows:
export const load_user = () => async dispatch => {
const config = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
};
try {
dispatch({type: USER_LOADED_REQUEST});
const response = await axios.get(`/auth/user/`, config);
if (response.status === 200) {
dispatch({
type: USER_LOADED_SUCCESS,
payload: response.data
});
} else {
dispatch({
type: USER_LOADED_FAIL
});
}
} catch (err) {
dispatch({
type: USER_LOADED_FAIL
});
}
};
inspect: console.log()
| Order | Result
| -------- | ---------------------------------------------------- |
| First | {loading: false, isAuthenticated: false, userInfo: null} |
| Second | {loading: true, isAuthenticated: false, userInfo: null} |
| third | {loading: false, isAuthenticated: true, userInfo: {…}} |
I am dispatching an image to firebase. Everything is successful and I can upload the image to firebase storage. But after I got success, my codes was suddenly dispatched an error one more time.By the way I logged some numbers to see what's going on and I got "1,2,3,4 then success and then failed error" on my console. How can I achive to upload image without getting failed error? I could upload but I also get error now.. Thank you in advance..
The error message says: "Invalid attempt to spread non-iterable instance".
export const uploadPhoto = (uri, contentType = 'image/jpeg') => {
return async dispatch => {
try {
console.log('1');
const userId = firebase.auth().currentUser.uid;
const photoId = await uniqueIdGenerator();
dispatch({ type: UPLOAD_START });
console.log('2');
const snapshot = await firebase.storage().ref()
.child(`/photos/${userId}`)
.child(photoId)
.put(uri, { contentType });
console.log('3');
await firebase.firestore()
.collection('users').doc(userId)
.collection('photos').doc(photoId)
.set({ url: snapshot.downloadURL });
console.log(snapshot.downloadURL);
console.log('4');
await dispatch({ type: UPLOAD_SUCCESS, payload: snapshot.downloadURL });
console.log('5');
Actions.pop();
console.log('6');
}
catch (error) {
dispatch({ type: UPLOAD_FAILED });
console.log(error.message);
}
}
}
here is my reducer:
const INITIAL_STATE = {
data: [],
loading: false
}
export default (state = INITIAL_STATE, { type, payload }) => {
console.log('reducerState', state);
console.log('type,payload', type, payload);
switch (type) {
case UPLOAD_START:
return { ...state, data: payload, loading: true }
case UPLOAD_SUCCESS:
console.log('succesa girdi');
return { ...state, data: [...state.data, payload], loading: false }
case UPLOAD_FAILED:
return { ...state, data: payload, loading: false }
case GET_PHOTOS_START:
return { ...state, data: payload, loading: true }
case GET_PHOTOS_SUCCESS:
return { ...state, data: payload, loading: false }
case GET_PHOTOS_FAILED:
return { ...state, data: payload, loading: false }
default:
return state;
}
}
EDIT
I have figured out the problem by remove all the dumy datas from my firebase storage. I use an emulator to test my app and I used same photos again and again. Most probably same photos causes a problem because I have not changed anything else in my app but now is working well.
Try to change your reducer
case UPLOAD_SUCCESS:
console.log('succesa girdi');
return { ...state, data: [...state.data, payload], loading: false }
to
case UPLOAD_SUCCESS:
console.log('succesa girdi');
return { ...state, data: payload, loading: false }
I think there is what the error is trying to warn you about. I'm not sure why you are trying to spread the state twice
UPDATE
in that case try this
case UPLOAD_SUCCESS:
console.log('succesa girdi');
let urls= state.data
urls.push(payload)
return { ...state, data: urls, loading: false }
i wanted to show loader for each and every request individually depending on what request made,Suppose in dashboard i have muiltple widget and they all have different api call, i wanted to show different loader for each request made,
one way is to make adding isLoading flag for every request made,which i think is not the good solution as the application grows,and i am finding solution that can handle multiple request from one flag
so how should i do to make dynamic individual loader based on every request
below is my reducer and action
reducer
export const intialstate = {
isAuth: false,
isLoading: false,
btnDisable: false
};
export default function(state = intialstate, action) {
switch (action.type) {
case API_REQUEST:
return {
...state,
isLoading: true,
};
case API_SUCCESS:
return {
...state,
isLoading: false,
isError: null
};
case API_FAILURE:
return {
...state,
isError: action.payload,
isLoading: false,
};
// no default
}
return state;
}
action.js
export const AnyAPIRequest = () => {
return (dispatch) => {
dispatch({
type: API_REQUEST
});
API.anygetcall()
.then((res) => {
dispatch({
type: API_SUCCESS
});
dispatch({ type: GETLIST, payload: res });
})
.catch((err) => {
dispatch({
type: API_FAILURE,
payload: err
});
});
};
};
Please help,how to implement dynamic loader based on different request and let me know any thing to update in current workflow
Two ways:
Have an integer count of API calls loading. IsLoading: IsLoading + 1 and then show the loading indicator if IsLoading > 1
Name each of your IsLoading differently to show different loading indicators. For example if you had a call to get students and a call to get teachers, you would have IsLoadingStudents and IsLoadingTeachers and have separate loading indicators for each component in the app
If you don't want to add a new isLoadingXXX for each new API request, you can use a collection and give each API request a string ID. Something like the following:
Reducer:
export const intialstate = {
isAuth: false,
isLoadingRequestIds: [],
btnDisable: false
};
export default function(state = intialstate, action) {
switch (action.type) {
case API_REQUEST:
return {
...state,
isLoadingRequestIds: [...state.isLoadingRequestIds, action.requestId],
};
case API_SUCCESS:
return {
...state,
isLoadingRequestIds:
state.isLoadingIds.splice(state.isLoadingRequestIds.indexOf(action.requestId)).slice(),
isError: null
};
case API_FAILURE:
return {
...state,
isError: action.payload,
isLoadingRequestIds:
state.isLoadingIds.splice(state.isLoadingRequestIds.indexOf(action.requestId)).slice(),
};
// no default
}
return state;
}
Actions:
export const AnyAPIRequest = (requestId) => {
return (dispatch) => {
dispatch({
requestId,
type: API_REQUEST
});
API.anygetcall()
.then((res) => {
dispatch({
requestId,
type: API_SUCCESS
});
dispatch({ type: GETLIST, payload: res });
})
.catch((err) => {
dispatch({
requestId,
type: API_FAILURE,
payload: err
});
});
};
};
export const StudentAPIRequest = () => AnyAPIRequest('student');
export const TeacherAPIRequest = () => AnyAPIRequest('teacher');