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: {…}} |
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;
}
};
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(...).
In the following reducer file, when a user logs in the state is updated with the payload coming from the actions file. In my state, I have a user object in which I want to store the payload. However, in my redux dev tool, I noticed that the payload is appearing outside of the user object instead of within it as a result user object shows as the default value of null. Cannot figure out what I am doing wrong.
//Login Reducer Fille
const initialState = {
token: localStorage.getItem('token'),
isAuthenticated: false,
isLoading: false,
user: null
};
export default function(state = initialState, action){
switch(action.type){
case LOGIN_SUCCESS:
localStorage.setItem('token', action.payload.token);
return{
...state,
...action.payload,
isAuthenticated: true,
isLoading: false
};
default:
return state;
}
}
//Login Action File
export const login = ({email, password, history}) => dispatch =>{
const config = {
headers:{
"Content-Type": "application/json"
}
};
const body = JSON.stringify({email, password});
axios.post('/api/user/login/', body, config)
.then(res => {
console.log(res.data)
dispatch({
type: LOGIN_SUCCESS,
payload: res.data
})
history.push('/dashboard')
})
.catch(err =>{
dispatch(returnErrors(err.response.data, err.response.status, 'LOGIN_FAIL'));
dispatch({
type: LOGIN_FAIL
});
});
};
You need to change the reducer return method in the LOGIN_SUCCESS case block to this one
return {
...state,
user:action.payload,
isAuthenticated: true,
isLoading: false
};
I am building a project in which users can upload videos in bulk, My code works fine but I want to show the users a progress bar when their videos are being uploaded to the server, By doing some research on the internet I found that the onUploadProgress event of Axios can be used, but I have no idea on how to send the upload progress of each video file individually.
Below is my code
reducers.js file-
import * as actionTypes from "../actions/types";
const intialState = {
isLoading: false,
uploadProgress: 0,
error: null
};
export default function(state = intialState, action) {
switch (action.type) {
case actionTypes.ADD_VIDEO_START:
case actionTypes.ADD_VIDEO_SUCCESS:
case actionTypes.ADD_VIDEO_ERROR:
return {
...state,
isLoading: action.isLoading,
error: action.error
};
case actionTypes.VIDEO_UPLOAD_PROGRESS:
return {
...state,
uploadProgress: action.percentage
};
default:
return state;
}
}
actions.js
import * as actionTypes from "./types";
import { getToken, VIDEO_URL } from "./Utils";
import axios from "axios";
export const addVideos = video => dispatch => {
if (getToken()) {
dispatch({
type: actionTypes.ADD_VIDEO_START,
isLoading: true,
error: null,
});
const config = {
headers: {
Accept: "application/json",
"Content-Type": "multipart/form-data",
Authorization: `Token ${getToken()}`
},
onUploadProgress: progressEvent => {
const { loaded, total } = progressEvent;
let percentage = Math.floor((loaded * 100) / total);
dispatch({
type: actionTypes.VIDEO_UPLOAD_PROGRESS,
percentage: percentage
});
}
};
let form_data = new FormData();
form_data.append("video", video);
axios
.post(COURSE_CONTENT_URL, form_data, config)
.then(() =>
dispatch({
type: actionTypes.ADD_VIDEO_SUCCESS,
isLoading: false,
error: null
})
)
.catch(error =>
dispatch({
type: actionTypes.ADD_VIDEO_ERROR,
isLoading: false,
error: error,
})
);
} else {
console.log("You are not authenticated");
}
};
videoComponent.js
...
const courseContent = {
video: [], // <- All the videos to be uploded are kept here
};
//submit function
const handleSubmit = () => {
courseContent.video.map(file => props.addVideos(file));
};
...
views.py
...
#api_view(['POST'])
#parser_classes([FormParser, MultiPartParser])
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
def add_content(request):
models.Videos.objects.create(
user=request.user,
video=request.FILES.get('video')
)
return Response(status=status.HTTP_201_CREATED)
...
Any help on this would be much appreciated.
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');