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
}
}
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.
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 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?.
I'm working with React Redux, and I'm trying to call a function inside another function on my actions file. The problem is It only work if I take the dispatch part of the function.
I wan't to call updateToken() from askForUser() but it will only work if I take the dispatch part from updateToken(). Also when i call askForUser() from askForUser() it won't work either.
const updateToken = (token) => dispatch => {
console.log(token)
dispatch({
type: UPDATE_TOKEN,
payload: token
})
}
const askForUser = (token) => dispatch => {
dispatch({ type: ASKING_FOR_DATA })
axios.get(API.GET_USER, {
params: {
token
}
}).then((response) => {
if (response.data.status === 1) {
dispatch({ type: ASK_FOR_USER, payload: response.data.user })
} else if (response.data.status === 2) {
updateToken(response.data.token)
//console.log(hola2())
} else {
NotificationManager.error('Error consiguiendo usuario')
dispatch({ type: ERROR_HANDLER })
}
}).catch((err) => {
console.log(err)
dispatch({ type: ERROR_HANDLER })
})
}
you'll have to dispatch it :
else if (response.data.status === 2) {
dispatch(updateToken(response.data.token)); // dispatch the function here
}