I have an onSubmit function with an axios post that allows to register a user.
I would like to know if it is possible if when the user create his account, at the time of the submission a open modal with a timeout ?
I already have the component of the modal created with redux but I do not know how to integrate it in this Axios
Axios Post
const onSubmit = async function onSubmit(values) {
axios({
method: 'POST',
url: 'http://localhost:4242/registerUser',
data: values,
headers: { 'Content-Type': 'application/json' },
})
.then((res) => {
localStorage.setItem("token", res.headers["x-access-token"])
})
.catch(function (erreur) {
console.log(erreur);
})
}
Modal Reducer
export const registerModal = id => ({
type: "REGISTER_MODAL",
id
});
export const showModal = id => ({
type: "SHOW_MODAL",
id
});
export const hideModal = id => ({
type: "HIDE_MODAL",
id
});
const initialState = {
// modals: []
modals: {}
};
const modals = (state = initialState, action) => {
switch (action.type) {
case "REGISTER_MODAL":
const newModal = {
id: action.id,
visible: false
};
return {
...state,
modals: { ...state.modals, [action.id]: newModal }
};
case "SHOW_MODAL":
return {
...state,
modals: {
...state.modals,
[action.id]: { ...state.modals[action.id], visible: true }
}
};
case "HIDE_MODAL":
return {
...state,
modals: {
...state.modals,
[action.id]: { ...state.modals[action.id], visible: false }
}
};
default:
return state;
}
};
export default combineReducers({
modals
});
Ok if i get it right you want to open the modal after the post was received..
So I would try to put the action of the open modal inside the then method o request promise:
const onSubmit = async function onSubmit(values) {
axios({
method: 'POST',
url: 'http://localhost:4242/registerUser',
data: values,
headers: { 'Content-Type': 'application/json' },
})
.then((res) => {
localStorage.setItem("token", res.headers["x-access-token"])
// Here you are sure that your post was successfull I think...
// The issue here will be to get the res and the dispatcher function, this will vary for the pattern that you are following
modalReducer.showModal( res.id );
})
.catch(function (erreur) {
console.log(erreur);
})
}
A thing that i cant solve is how are you using your reducer inside the Component that is handling your post.You are passing the id? or the id already exist in the component?.
Related
I wanna passing the selectedCategory (it is State hook) to the Child Item,
First of all, I use the getServiceCatoriesAsync API (redux toolkit) and pass props.serviceCategories[0]?._id to State to fetch initialState (ID of Category).
In Child Component, I receive selectedCategory with the value: undefined
How to fix this.
const ServicesScreen = (props) => {
//! props: navigation, route,
//! props Redux: serviceCategories, getServiceCategoriesAsync
const nCount = React.useRef(0);
console.log(`ServicesScreen - render `, (nCount.current += 1));
const [selectedCategory, setSelectedCategory] = React.useState(props.serviceCategories[0]?._id);
React.useEffect(() => {
let isSubscribed = true;
if (isSubscribed) {
props.getServiceCategoriesAsync();
}
return () => {
isSubscribed = false; //! Cancel the subscription
};
}, [selectedCategory]);
return (
<View style={styles.container}>
<PanelServiceCategory
theme={theme}
style={styles.containerPanelCategory}
setSelectedCategory={setSelectedCategory}
selectedCategory={selectedCategory}
serviceCategories={props.serviceCategories}
{...props}
/>
<PanelServices style={styles.containerPanelService} />
</View>
);
};
servicesSlice
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
import { PlatformBaseUrl } from '../../../utils';
//! GET ServiceCategory
export const getServiceCategoriesAsync = createAsyncThunk('services/getServiceCategoriesAsync', async () => {
const response = await fetch(PlatformBaseUrl.baseApiUrl('/api/services'));
if (response.ok) {
const { serviceCategories } = await response.json();
return serviceCategories; // payload Action
}
});
//! CREATE ServiceCategory
export const addServiceCategoryAsync = createAsyncThunk('services/addServiceCategoryAsync', async (payload) => {
const response = await fetch(PlatformBaseUrl.baseApiUrl('/api/services'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: payload.name }),
});
if (response.ok) {
const { serviceCategory } = await response.json();
return serviceCategory; //! return Action 1 Array
}
});
//! CREATE Service
export const addServiceAsync = createAsyncThunk('services/addServiceAsync', async (payload, { getState }) => {
const { serviceCategoryId } = getState().modal.modalProps; //! OK
const response = await fetch(PlatformBaseUrl.baseApiUrl(`/api/services/${serviceCategoryId}`), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: payload.name, price: payload.price, description: payload.description }),
});
if (response.ok) {
const { service } = await response.json();
return service;
}
});
//! DELETE Service
export const removeServiceAsync = createAsyncThunk('services/removeServiceAsync', async (payload, { getState }) => {
const { serviceCategoryId, serviceId } = getState().modal.modalProps;
const response = await fetch(PlatformBaseUrl.baseApiUrl(`/api/services/${serviceCategoryId}/${serviceId}`), {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ serviceId }),
});
if (response.ok) {
const { service } = await response.json();
return service;
}
});
//! UPDATE Service
export const updateServiceAsync = createAsyncThunk('services/updateServiceAsync', async (payload, { getState }) => {
const { serviceCategoryId, serviceId } = getState().modal.modalProps;
const { name, price, description } = payload;
const response = await fetch(PlatformBaseUrl.baseApiUrl(`/api/services/${serviceCategoryId}/${serviceId}`), {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, price, description }),
});
if (response.ok) {
const { updatedService } = await response.json();
return updatedService; //! return a Object
}
});
const initialState = {
isLoading: false,
error: false,
serviceCategories: [],
};
const servicesSlice = createSlice({
name: 'services',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(getServiceCategoriesAsync.pending, (state, action) => {
console.log('getServiceCategoriesAsync pending');
});
builder.addCase(getServiceCategoriesAsync.fulfilled, (state, action) => {
console.log('getServiceCategoriesAsync fulfilled');
state.serviceCategories = action.payload;
});
builder.addCase(addServiceCategoryAsync.fulfilled, (state, action) => {
console.log('addServiceCategoryAsync fulfilled');
state.serviceCategories.push(action.payload);
});
builder.addCase(addServiceAsync.pending, (state, action) => {
console.log('addServiceAsync pending');
});
builder.addCase(addServiceAsync.fulfilled, (state, action) => {
console.log('addServiceAsync fulfilled');
let categories = [...state.serviceCategories];
let catIndex = categories.findIndex((item) => item._id === action.payload.category);
if (catIndex != -1) categories[catIndex].services.push(action.payload);
state.serviceCategories = categories;
});
builder.addCase(removeServiceAsync.pending, (state, action) => {
console.log('removeServiceAsync pending');
});
builder.addCase(removeServiceAsync.fulfilled, (state, action) => {
console.log('removeServiceAsync fulfilled');
let categories = state.serviceCategories;
let catIndex = categories.findIndex((item) => item._id === action.payload.category);
let updatedServices = categories[catIndex].services.filter((service) => service._id !== action.payload._id);
if (catIndex != -1) state.serviceCategories[catIndex].services = updatedServices;
});
builder.addCase(updateServiceAsync.pending, (state, action) => {
console.log('updateServiceAsync pending');
});
builder.addCase(updateServiceAsync.fulfilled, (state, action) => {
console.log('updateServiceAsync fulfilled');
let categories = state.serviceCategories;
let catIndex = categories.findIndex((item) => item._id === action.payload.category);
let updatedServices = categories[catIndex].services.map((service) => (service._id === action.payload._id ? action.payload : service));
if (catIndex != -1) state.serviceCategories[catIndex].services = updatedServices;
});
},
});
//! exp Actions
export const {} = servicesSlice.actions;
//! exp Reducer
export default servicesSlice.reducer;
I wish I could comment this under your post but my rep is too low so oh well.
The problem may be caused due to several reasons. To debug you need to show the parent file which gives props.serviceCategories[0]?._id to ServicesScreen. And also show how it calls the redux store to gain access to said data.
Also show the slice that handles the state for serviceCategories. It might be the case that you are mutating the state and hence the store is not causing a re-render.
EDIT
Alright so basically you are mutating some states that Immer cannot handle in redux-toolkit.
the cases are wherever this has been done:
state.serviceCategories[catIndex].services = updatedServices;
According to the docs arrays are mutable in nature and changing them in such fashion means Immer cannot apply a copy to the state change (Although it is able to do so inside the createReducer() method). Therefore a better approach would be:
// inside updateServiceAsync.fulfilled and removeServiceAsync.fulfilled
let elementForInsertion = {...state.serviceCategories[catIndex], services: updatedServices}
if (catIndex != -1) state.seviceCategories = [...state.serviceCategories.slice(0,catIndex), elementForInsertion, ...state.serviceCategories.slice(catIndex+1)]
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.
So I have created these contexts to handle logging users in and retrieving the logged user to any component that might need it.
Here they are:
context.js
import React, { useReducer } from "react";
import { AuthReducer, initialState } from "./reducers";
const AuthStateContext = React.createContext();
const AuthDispatchContext = React.createContext();
export function useAuthState() {
const context = React.useContext(AuthStateContext);
if (context === undefined) {
throw new Error("useAuthState must be used within a AuthProvider");
}
return context;
}
export function useAuthDispatch() {
const context = React.useContext(AuthDispatchContext);
if (context === undefined) {
throw new Error("useAuthDispatch must be used within a AuthProvider");
}
return context;
}
export const AuthProvider = ({ children }) => {
const [user, dispatch] = useReducer(AuthReducer, initialState);
return (
<AuthStateContext.Provider value={user}>
<AuthDispatchContext.Provider value={dispatch}>
{children}
</AuthDispatchContext.Provider>
</AuthStateContext.Provider>
);
}
reducers.js
let user = localStorage.getItem("currentUser")
? JSON.parse(localStorage.getItem("currentUser")).user
: "";
let token = localStorage.getItem("currentUser")
? JSON.parse(localStorage.getItem("currentUser")).token
: "";
export const initialState = {
userDetails: user || "",
token: token || "",
loading: false,
errorMessage: null,
};
export const AuthReducer = (initialState, action) => {
switch (action.type) {
case "REQUEST_LOGIN":
return {
...initialState,
loading: true,
};
case "LOGIN_SUCCESS":
return {
...initialState,
userDetails: action.payload.user,
token: action.payload.token,
loading: false,
};
case "LOGOUT":
return {
...initialState,
userDetails: "",
token: "",
};
case "LOGIN_ERROR":
return {
...initialState,
loading: false,
errorMessage: action.error,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};
actions.js
const ROOT_URL = process.env.REACT_APP_API_HOST_URL;
export async function loginUser(dispatch, loginPayload) {
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(loginPayload),
};
try {
dispatch({ type: "REQUEST_LOGIN" });
let response = await fetch(`${ROOT_URL}/auth/login`, requestOptions);
let data = await response.json();
if (data.user) {
dispatch({ type: "LOGIN_SUCCESS", payload: data });
localStorage.setItem("currentUser", JSON.stringify(data));
return data;
}
dispatch({ type: "LOGIN_ERROR", error: data.errors[0] });
return;
} catch (error) {
dispatch({ type: "LOGIN_ERROR", error: error });
}
}
export async function logout(dispatch) {
dispatch({ type: "LOGOUT" });
localStorage.removeItem("currentUser");
localStorage.removeItem("token");
}
my question is how to expand this to check whether the JWT has expired or not every time the useAuthState() hook is called (if this is even the best way to go about things)? and then log the user out or perhaps refresh the token from the server without having to log the user out if possible.
Thanks in advance.
With JWT, you can decrypt your own token in a browser without a secret key. This way you can check if the JWT token is about or already expired. The secret key is only needed for the authenticity of where it's signed off. This is demonstrated well in JWT website.
If you wanted to be able to regenerate the key from expired JWT you can just set ignoreExpiration to true in jsonwebtoken's verify() function at your server, but then why even bother setting expiration time in the first place? It's best to only allow regenerating JWT when it's about to expire.
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
}
}
I am using React with react-redux, redux and redux-actions.
I have one action that takes the current token stored in localStorage and ensures that it is not expired, like so:
export const verifyLogin = () => {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: `${
localStorage.getItem("token")
? localStorage.getItem("token")
: "not_valid_token"
}`
},
onSuccess: verifiedLogin,
onFailure: failedLogin
});
};
function verifiedLogin(data) {
const user = {
...data.user
}
setUser(user);
return {
type: IS_LOGGED_IN,
payload: true
};
}
function failedLogin(data) {
return {
type: IS_LOGGED_IN,
payload: false
};
}
When it verifies the token it returns a response like so:
{
token: "token_data",
user: {
username: "this",
is_staff: true,
(etc...)
}
}
As you can see in verifiedLogin(), it is calling another function (in this case an action creator) to set the user to the user object returned by my API. the setUser is defined like this:
const setUser = createAction(SET_USER);
which should create an Action like this:
{
type: SET_USER,
payload: {
userdata...
}
}
The reducer is defined like this:
import { handleActions } from "redux-actions";
import { SET_USER } from "../constants/actionTypes";
export default handleActions(
{
[SET_USER]: (state, action) => action.payload
},
{}
);
I know the action creator is correct, as I have verified by console.log(setUser(user)); but all that is in the state is an empty object for users. I am unable to determine why it is not working successfully. I am new to React and Redux so it may be something I misunderstood.
Edit:
This is apiPayloadCreator:
const noOp = () => ({ type: "NO_OP" });
export const apiPayloadCreator = ({
url = "/",
method = "GET",
onSuccess = noOp,
onFailure = noOp,
label = "",
data = null
}) => {
console.log(url, method, onSuccess, onFailure, label, data);
return {
url,
method,
onSuccess,
onFailure,
data,
label
};
};
Even though you are calling setUser, it is not being dispatched by Redux, which is what ultimately executes a reducer and updates the store. Action creators like setUser are not automatically wired up to be dispatched; that is done in the connect HOC. You will need a Redux middleware such as redux-thunk to dispatch async / multiple actions. Your code can then be something like the example below (using redux-thunk):
export const verifyLogin = () => (dispatch) => {
return verifyLoginAC({
url: "/verify/",
method: "POST",
data: {
token: `${
localStorage.getItem("token")
? localStorage.getItem("token")
: "not_valid_token"
}`
},
onSuccess: (result) => verifiedLogin(dispatch, result),
onFailure: (result) => diapatch(failedLogin(result))
});
};
const verifiedLogin = (dispatch, data) => {
const user = {
...data.user
};
dispatch(setUser(user));
dispatch({
type: IS_LOGGED_IN,
payload: true
});
};
You're going to need to use something like redux-thunk in order to do async actions. See the documentation on how this is done.