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;
}
};
Related
I have a simple auth stack as follow
export default () => {
const { state } = useContext(AuthContext);
return (
<AuthProvider>
<NavigationContainer>
{state.token ? <MainNavigator /> : <AuthNavigator />}
</NavigationContainer>
</AuthProvider>
);
};
The initial state of token is defined as null in the AuthContext folder, code below. But when running the program i get the following error TypeError: undefined is not an object (evaluating '_useContext.state')
const authReducer = (state, action) => {
switch (action.type) {
case "error":
return { ...state, errorMessage: action.payload };
case "signin":
return { errorMessage: "", token: action.payload };
default:
return state;
}
};
const tokencheck = (dispatch) => async () => {
const token = await AsyncStorage.getItem("token");
if (token) {
dispatch({ type: signin, payload: token });
navigate("Home");
} else {
navigate("SignIn");
}
};
const signup =
(dispatch) =>
async ({ username, password }) => {
try {
const response = await tracker({
method: "post",
url: "/user",
data: qs.stringify({
username: username,
password: password,
}),
headers: {
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
await AsyncStorage.setItem("token", response.data.email);
// dispatch({ type: "signin", payload: response.data.access_token });
navigate("SignIn");
} catch (err) {
dispatch({
type: "error",
payload: "Something's not write, plz try again",
});
console.log(err);
}
};
const signin =
(dispatch) =>
async ({ username, password }) => {
try {
const response = await tracker({
method: "post",
url: "/login",
data: qs.stringify({
username: username,
password: password,
}),
headers: {
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
await AsyncStorage.setItem("token", response.data.access_token);
dispatch({ type: "signin", payload: response.data.access_token });
navigate("Home");
} catch (err) {
console.log(err);
dispatch({
type: "error",
payload: "Start debuggin",
});
}
};
const signout = (dispatch) => {
return () => {};
};
export const { Provider, Context } = creatingContext(
authReducer,
{ signin, signout, signup, tokencheck },
{ token: null, errorMessage: "" }
);
The ternary logic is sound and I have defined the initial state then why is this error persisting.
While I was looking for state in token what i should look for is value.
The problem is simply solved with
{state.token != null : <nav/>?<auth/>}
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(...).
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'm trying to update my user profile data from the client-side. The user has to upload a file and the component will catch the actual user-loaded ID, store it into a state, and then use this state to find the user in the database to update the value I need. But I can't figure out how to pass the state to filter the user; in the way you see below the It gives me a PUT: http://localhost:3000/api/users/upgrade/undefined 404 (Not Found). Someone could help me?
Here's my server router:
//SERVER ROUTER
router.put("/upgrade/:id", upgrade.single("userPlus_doc"), (req, res) => {
User.findById(req.params.id)
.then((user) => {
user.userPlus = true;
user.userPlus_doc = req.file.originalname;
user
.save()
.then(() => res.json("User Upgraded!"))
.catch((err) => res.status(404).json({ success: false }));
})
.catch((err) => res.status(404).json({ success: false }));
});
My action and reducer:
//ACTION
export const upgradeUser = (formData, id) => (dispatch, getState) => {
axios
.put(`/api/users/upgrade/${id}`, formData, tokenConfig(getState))
.then((res) =>
dispatch({
type: USER_UPGRADE,
payload: res.data,
})
)
.catch((err) =>
dispatch(returnErrors(err.response.data, err.response.status))
);
};
export const setUserUpgradeID = (user_id) => (dispatch) => {
dispatch({
type: SET_USER_UPGRADE_ID,
payload: user_id,
});
};
//REDUCER
const initialState = {
user_id: "",
};
export default function foo(state = initialState, action) {
switch (action.type) {
...
case SET_USER_UPGRADE_ID:
return {
...state,
user_id: action.payload,
};
case USER_UPGRADE:
return {
...state,
user: state.user.filter((user) => user._id !== action.payload),
};
default:
return state;
}
}
And how I pass the information client-side:
class ProfileUpgrade extends Component {
state = {
userPlus_doc: "",
user_id: "",
};
onFileChange = (e) => {
this.setState({
userPlus_doc: e.target.files[0],
});
this.props.setUserUpgradeID({
user_id: this.props.auth.user._id,
});
};
/* onChange = () => {
this.props.setUserUpgradeID({
user_id: this.props.auth.user._id,
});
console.log(this.props.setUserUpgradeID);
};
*/
onSubmit = (e, user_id) => {
e.preventDefault();
const formData = new FormData();
/* formData.append("userPlus", this.state.userPlus); */
formData.append("userPlus_doc", this.state.userPlus_doc);
this.props.upgradeUser(formData, this.props.user_id);
};
render() {
return ( ... )
}
}
ProfileUpgrade.propTypes = {
auth: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
user: state.user,
auth: state.auth,
user_id: state.user_id,
});
export default connect(mapStateToProps, { upgradeUser, setUserUpgradeID })(
ProfileUpgrade
);
The only things I need from the client-side are the doc title and the user ID to update the correct user object. To update the boolean userPlus I set the backend to set the value to true and save it.
Am I missing something in my action to make the component pass filter the user ID?
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');