Wait For The Result Of Dispatch (React-Redux) - javascript

Lets say I have a function that calls an api and get user data ,
export function submitLogin({email, password})
{
return (dispatch) =>
jwtService.signInWithEmailAndPassword(email, password)
.then((user) => {
debugger;
dispatch(setUserData(user));
return dispatch({
type: LOGIN_SUCCESS
});
}
)
.catch(error => {
debugger;
return dispatch({
type : LOGIN_ERROR,
payload: error
});
});
}
Now this is how I call this
function handleSubmit(model)
{
dispatch(authActions.submitLogin(model));
}
And Form
<Formsy
onValidSubmit={handleSubmit}
onValid={enableButton}
onInvalid={disableButton}
ref={formRef}
className="flex flex-col justify-center w-full"
>
Now please let me know how can I read the type sent by dispatch (as soon it receive response from an Api) inside submitLogin function also I would like to redirect to url if type is LOGIN_SUCCESS

const handleSubmit = async (model) => {
await dispatch(authActions.submitLogin(model));
}
export submitLogin = ({email, password}) => {
return async (dispatch) =>
await jwtService.signInWithEmailAndPassword(email, password)
.then((user) => {
debugger;
dispatch(setUserData(user));
return dispatch({
type: LOGIN_SUCCESS
});
}
)
.catch(error => {
debugger;
return dispatch({
type : LOGIN_ERROR,
payload: error
});
});
}

Try this way
function Example({ navigation }) {
const redirectCallback = () => {
// redirect here
navigation.navigate('Your Route');
}
function handleSubmit(model)
{
dispatch(authActions.submitLogin( model, () => redirectCallback() ) );
}
return(
<Formsy
onValidSubmit={handleSubmit}
onValid={enableButton}
onInvalid={disableButton}
ref={formRef}
className="flex flex-col justify-center w-full"
>
)
}
export function submitLogin({email, password}, callback)
{
return (dispatch) =>
jwtService.signInWithEmailAndPassword(email, password)
.then((user) => {
debugger;
dispatch(setUserData(user));
return dispatch({
type: LOGIN_SUCCESS,
callback: callback; // send callback here
});
}
)
.catch(error => {
debugger;
return dispatch({
type : LOGIN_ERROR,
payload: error
});
});
}
Create reducer and handle type LOGIN_SUCCESS
const reducer = (state = { isLoggedIn: false }, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
action.callback(); call here
return state;
default:
return state;
}
};

Related

Conditional Statement within HTML tags in a Javascript document

I'm pretty new to javascript, and I'm trying to get only the products from a specific category (category is an integer from 1-3) to appear on the product screen. When I tried to create a conditional statement (if product.category < 2){}) to encompass the return(); portion of my Product.js document I get an internal server error (500 error). Now, I'm wondering if it is possible to call this conditional statement within ProductScreen.js? Specifically, I was wondering if it is possible to call this conditional statement within <Product></Product> as product.category is not defined in the rest of the document? I would really appreciate any help or guidance. Thank you!
ProductScreen.js
import React, { useEffect } from 'react';
import Product from '../components/Product';
import LoadingBox from '../components/LoadingBox';
import MessageBox from '../components/MessageBox';
import { useDispatch, useSelector } from 'react-redux';
import { listProducts } from '../actions/productActions';
export default function ProductScreen() {
const dispatch = useDispatch();
const productList = useSelector((state) => state.productList);
const { loading, error, products } = productList;
useEffect(() =>{
dispatch(listProducts());
}, [dispatch]);
return (
<div>
{loading ? (
<LoadingBox></LoadingBox>
) : error ? (
<MessageBox variant="danger">{error}</MessageBox>
) : (
<div className="row center">
{products.map((product ) => (
<Product key={product._id} product={product}></Product>
))}
</div>
)}
</div>
);
}
Product.js
import React from 'react';
import { Link } from 'react-router-dom';
import Rating from './Rating';
export default function Product(props) {
const { product } = props;
return (
<div key={product._id} className="card">
<Link to={`/product/${product._id}`}>
<img className="medium" src={product.image} alt={product.name} />
</Link>
<div className="card-body">
<Link to={`/product/${product._id}`}>
<h2>{product.name}</h2>
</Link>
<Rating
rating={product.rating}
numReviews={product.numReviews}
></Rating>
<div className="price">${product.price}</div>
<div className="category">
{product.category}
</div>
</div>
</div>
);
};
productAction.js
import Axios from 'axios';
import {
PRODUCT_CREATE_FAIL,
PRODUCT_CREATE_REQUEST,
PRODUCT_CREATE_SUCCESS,
PRODUCT_DETAILS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_UPDATE_REQUEST,
PRODUCT_UPDATE_SUCCESS,
PRODUCT_UPDATE_FAIL,
PRODUCT_DELETE_REQUEST,
PRODUCT_DELETE_FAIL,
PRODUCT_DELETE_SUCCESS,
PRODUCT_REVIEW_CREATE_REQUEST,
PRODUCT_REVIEW_CREATE_SUCCESS,
PRODUCT_REVIEW_CREATE_FAIL,
} from '../constants/productConstants';
export const listProducts = () => async (dispatch) => {
dispatch({
type: PRODUCT_LIST_REQUEST,
});
try {
const { data } = await Axios.get('/api/products');
dispatch({ type: PRODUCT_LIST_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: PRODUCT_LIST_FAIL, payload: error.message });
}
};
export const detailsProduct = (productId) => async (dispatch) => {
dispatch({ type: PRODUCT_DETAILS_REQUEST, payload: productId });
try {
const { data } = await Axios.get(`/api/products/${productId}`);
dispatch({ type: PRODUCT_DETAILS_SUCCESS, payload: data });
} catch (error) {
dispatch({
type: PRODUCT_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message,
});
}
};
export const createProduct = () => async (dispatch, getState) => {
dispatch({ type: PRODUCT_CREATE_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.post(
'/api/products',
{},
{
headers: { Authorization: `Bearer ${userInfo.token}` },
}
);
dispatch({
type: PRODUCT_CREATE_SUCCESS,
payload: data.product,
});
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_CREATE_FAIL, payload: message });
}
};
export const updateProduct = (product) => async (dispatch, getState) => {
dispatch({ type: PRODUCT_UPDATE_REQUEST, payload: product });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.put(`/api/products/${product._id}`, product, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: PRODUCT_UPDATE_SUCCESS, payload: data });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_UPDATE_FAIL, error: message });
}
};
export const deleteProduct = (productId) => async (dispatch, getState) => {
dispatch({ type: PRODUCT_DELETE_REQUEST, payload: productId });
const {
userSignin: { userInfo },
} = getState();
try {
const {data} = Axios.delete(`/api/products/${productId}`, {
headers: { Authorization: `Bearer ${userInfo.token}` },
});
dispatch({ type: PRODUCT_DELETE_SUCCESS });
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_DELETE_FAIL, payload: message });
}
};
export const createReview = (productId, review) => async (
dispatch,
getState
) => {
dispatch({ type: PRODUCT_REVIEW_CREATE_REQUEST });
const {
userSignin: { userInfo },
} = getState();
try {
const { data } = await Axios.post(
`/api/products/${productId}/reviews`,
review,
{
headers: { Authorization: `Bearer ${userInfo.token}` },
}
);
dispatch({
type: PRODUCT_REVIEW_CREATE_SUCCESS,
payload: data.review,
});
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message;
dispatch({ type: PRODUCT_REVIEW_CREATE_FAIL, payload: message });
}
};

got undefined to passing data react-redux

i always got undefined when i click the submit button on modalAction
if I do console.log (projectData) in my action modal to see all the data, how do I pass project data from in modalAction to projectAction?
/src/view/projectList.js
const ProjectList = () => {
const dispatch = useDispatch()
const project = useSelector(state => state.project)
const modal = useSelector(state => state.modal)
const category = useSelector(state => state.category)
// Open New Project Modal
const handleOpenProjectModal = () => {
dispatch(openModal(category.categories, project.data))
}
return (
<Button
label="Add Project"
onClick={()=> handleOpenProjectModal()}
primary={true}
/>
)
/src/store/action/modalAction.js
export const openModal = (categoryList, projectData) => dispatch => {
dispatch({
<Button
label='Submit'
onClick={() => dispatch(createProject(projectData))}
primary={true}
/>
})
/src/store/action/projectAction.js
export const createProject = (data) => dispatch => {
try {
instance.post('/project', data)
.then(res => {
dispatch({
type: CREATE_PROJECT,
message: res.data.message
})
instance.get('/project')
.then(res => {
dispatch({
type: GET_PROJECTS,
payload: res.data.data
})
})
dispatch({type: CLOSE_MODAL})
})
} catch (error) {
dispatch({
type: PROJECTS_ERROR,
payload: error,
})
}
}

Why are my action creators being called in the wrong order when using thunk?

I was experimenting with redux-thunk and action creators and noticed some strange behavior that I don't understand. When I call the action creator functions they don't get called in the order I want them to. This is my App.js component
class App extends Component {
handleSave = () => {
this.props.postData({
name:this.props.activity,
type_name: this.props.type
})
this.props.fetchList()
this.props.fetchData()
}
handleClick = () => {
this.props.fetchData()
}
componentDidMount() {
this.props.fetchData()
this.props.fetchList()
}
render() {
return (
<div className="App">
<Router>
<Nav />
<Route exact path='/' render={props => <Home {...props} clickProp={this.handleClick} saveProp={this.handleSave}/>} />
<Route exact path='/activities' render={props => <ListContainer {...props} numItems={this.props.list.length} listProp={this.props.list}/>} />
</Router>
</div>
);
}
}
const mapStateToProps = state => {
return {
activity: state.activity,
type: state.type,
list: state.list
}
}
const actions = {fetchData, fetchList, postData}
export default connect(mapStateToProps, actions)(App);
When I click on a button in the Home child component the handleSave function gets called which is then supposed to post an item on to my list and then fetch the updated list so that it can be shown in my list. When I do this, this.props.fetchList() gets called first even though it is the second function being called. I have placed a console.log(action) inside my reducer and this is what is printed.
{type: "FETCH_LIST", payload: Array(86)}
{type: "POST_DATA", payload: {…}}
{type: "FETCH_DATA", payload: {…}}
The only way that I can get the FETCH_LIST to happen after the POST_DATA is if I call fetch_data() a second time like so
handleSave = () => {
// something weird going on here
this.props.fetchList()
this.props.postData({
name:this.props.activity,
type_name: this.props.type
})
this.props.fetchList()
this.props.fetchData()
}
I really want to be able to get my code to work without having to call the same function twice if it is possible. Finally, this is what my action creators look like.
export default function fetchData() {
return (dispatch) => {
const url = 'http://www.boredapi.com/api/activity/'
fetch(url)
.then(res => res.json())
.then(activity => { dispatch({type: "FETCH_DATA", payload: activity})})
}
}
export default function fetchList() {
return (dispatch) => {
const url = 'http://localhost:3001/activities'
fetch(url)
.then(res => res.json())
.then(list => { dispatch({type: "FETCH_LIST", payload: list})})
}
}
export default function postData(activity) {
return (dispatch) => {
const url = 'http://localhost:3001/activities'
const config = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({activity})
}
fetch(url, config)
.then(r => r.json())
.then(activity => {dispatch({type: "POST_DATA", payload: activity})})
}
}
My only guess is that this is happening because these actions are asynchronous. So I have also tried to change the order in which these functions are called and no matter what FETCH_LIST always happens before POST_DATA.
You can convert those action creators to async versions and then you can await them so they execute in order.
https://medium.com/#gaurav5430/async-await-with-redux-thunk-fff59d7be093
For example on your fetchData
function fetchData() {
return async (dispatch) => {
const url = 'http://www.boredapi.com/api/activity/'
try{
const res = await fetch(url)
const activity = await res.json();
dispatch({type: "FETCH_DATA", payload: activity})
}
catch (error) {
console.log(error);
}
}
Once they are async you can await them in your handleSave (once you convert it to async) This will ensure they get called in order.
handleSave = async () => {
await this.props.postData({
name:this.props.activity,
type_name: this.props.type
})
await this.props.fetchList()
await this.props.fetchData()
}

redux dispatch not work when use in axios interceptors

I want to show snackbar when axios return error.
I use redux for config snackbar.
axiosAgent.interceptors.response.use(
function(response) {
return response;
},
function(error) {
console.log('object')
const dispatch = useDispatch();
dispatch({
type: ActionsTypes.CHANGE_SNACKBAR_CONFIG,
payload: { variant: "error", message: Strings.errors.problem }
});
dispatch({ type: ActionsTypes.CHANGE_SNACKBAR_SHOW, payload: true });
return Promise.reject(error);
}
);
and here is my snackbar component:
export default function Snackbar() {
const open = useSelector(state => state.Snackbar.showSnackbar);
const config = useSelector(state => state.Snackbar.snackbarConfig);
const dispatch = useDispatch();
const handleClose = (event, reason) => {
if (reason === "clickaway") {
return;
}
dispatch({ type: ActionsTypes.CHANGE_SNACKBAR_SHOW, payload: false });
};
useEffect(() => {
console.log(config);
}, [config]);
return (
<SB open={open} autoHideDuration={6000} onClose={handleClose}>
<Alert onClose={handleClose} severity={config.variant}>
{config.message}
</Alert>
</SB>
);
}
but doesn't work.
When I dispatch it from component it works but here not works.
Hooks, such as useDispatch(), can only be used in the body of a function component. You cannot use useDispatch in an interceptor. If you need to dispatch an action in an interceptor, you will need to have a reference to the store object, and call store.dispatch(/* action */)

Converting functions from pure react to redux react

In pure react, I have written a function that I call in componentDidMount ():
getTasks = (userId, query, statusTask, pageNumber) => {
let check = {};
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(res => {
check = res.data;
if (res.data) {
this.setState({
checkRunning: res.data,
checkRunningId: res.data.id
});
this.utilizeTimes(res.data.task_id);
}
})
.catch(error => {
console.log(error);
})
.then(() => {
const params = {
sort: 'name'
};
if (query) {
params['filter[qwp]'] = query;
if (this.state.tasks[0]) {
this.setState({
selectedId: this.state.tasks[0].id,
selectedTabId: this.state.tasks[0].id
});
}
}
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(res => {
if (res.status === 200 && res.data) {
this.setState({
tasks: res.data,
lengthArrayTasks: parseInt(res.headers['x-pagination-total-count'])
});
if (!check && res.data && res.data[0]) {
this.setState({
selectedTabId: res.data[0].id,
});
this.load(res.data[0].id);
}
let myArrayTasks = [];
myArrayTasks = res.data;
let findObject = myArrayTasks.find(task => task.id === this.state.runningTimerTask.id);
if (
!findObject &&
this.state.runningTimerTask &&
this.state.runningTimerTask.id &&
this.state.query === ''
) {
this.setState({
tasks: [this.state.runningTimerTask, ...myArrayTasks]
});
}
}
})
.catch(error => {
console.log(error);
});
});
};
I am trying to rewrite it to redux, but with poor results. First it makes one request / api / v1 / beta / $ {userId}, writes the answer in the variable check. check passes to the nextthen. In the next then carries out the request '/ api / v1 // tasks' Can somebody help me? I am asking for some tips. Is this somehow complicated?
So far, I've managed to create something like this:
store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
const store = createStore(rootReducer, applyMiddleware(thunk));
export default store;
actions
export const RUNNING_TIMER = 'RUNNING_TIMER';
export const GET_TASKS = 'GET_TASKS';
export const FETCH_FAILURE = 'FETCH_FAILURE';
export const runningTimer = (userId, query, statusTask, pageNumber) => dispatch => {
console.log(userId);
axios({
url: `/api/v1/beta/${userId}`,
method: 'GET'
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
})
.then(() => {
const params = {
sort: 'name'
};
axios({
url: '/api/v1//tasks',
method: 'GET',
params
})
.then(({ data }) => {
dispatch({
type: GET_TASKS,
payload: data
});
})
.catch(error => {
console.log(error);
});
});
};
reducer
import { RUNNING_TIMER, GET_TASKS } from '../actions';
const isRunningTimer = (state = {}, action) => {
const { type, payload } = action;
switch (type) {
case RUNNING_TIMER:
return {
checkRunningTimer: payload,
checkRunningTimerId: payload && payload.id ? payload.id : null
};
break;
case GET_TASKS:
return {
tasks: payload,
lengthArrayTasks: parseInt(action.headers['x-pagination-total-count'])
};
default:
return state;
}
};
const rootReducer = combineReducers({ isRunningTimer });
export default rootReducer;
App
class App extends Component {
constructor() {
super();
this.state = {
name: 'React'
};
}
componentDidMount() {
this.props.runningTimer();
}
render() {
return (
<div>
</div>
);
}
}
const mapStateToProps = state => {
const { isRunningTimer } = state;
return {
isRunningTimer
};
};
const mapDispatchToProps = dispatch => ({
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Number 1 Consider your state design.
I find it useful to consider what the state object would look like at a given point in time.
Here is an example of initialState used in an application of mine.
const initialState = {
grocers: null,
coords: {
latitude: 37.785,
longitude: -122.406
}
};
This is injected at the createStore.
Breaking down your application state object/properties, should assist you in making your actions simpler as well.
Number 2
Consider breaking down your actions.
My thoughts, decouple the action code, at the .then at the second .then .(Consider saving the results somewhere in a user: object)
.then(response => {
const data = response.data.user;
setUsers(data);})
.catch(error => {
console.log('There has been a problem with your fetch operation: ' + error.message);
})
function setUsers(data){
dispatch({
type: FETCH_USERS,
payload: data
});
}
This refers to the S in SOLID design principles. Single Responsibility Principle.
https://devopedia.org/solid-design-principles
Number 3
Consider this, if the 'getUser' info fetch fails.
Having the process/response separated will allow the application to be debugged more cleanly. In example, the user api failed or the getTask api failed, etc.
More resources on redux.
https://redux.js.org/introduction/learning-resources#thinking-in-redux
Extending previous answer from #Cullen, this is what I did:
Since you already have a action to GET_TODOS, just make the action creator for runningTimer to do one and only one thing - make API call to /api/v1/beta/<userId> and dispatch respective actions.
export const runningTimer = (
userId,
query,
statusTask,
pageNumber
) => dispatch => {
return axios({
url: `/api/v1/beta/${userId}`,
method: "GET"
})
.then(({ data }) => {
dispatch({
type: RUNNING_TIMER,
payload: data
});
})
.catch(error => {
console.log(error);
dispatch({ type: FETCH_FAILURE });
});
};
Update props of your app component to read store data.
...
const mapStateToProps = state => {
const { isRunningTimer, todos, todo } = state;
return {
todos,
todo,
isRunningTimer,
};
};
const mapDispatchToProps = dispatch => ({
getTodos: () => dispatch(getTodos()),
getTodo: id => dispatch(getTodo(id)),
runningTimer: (userId, query, statusTask, pageNumber) => dispatch(runningTimer(userId)),
});
...
Update the implementation of componentDidMount to dispatch isRunningTimer -
componentDidMount() {
...
// call with userId 1
this.props.runningTimer(1).then(() => {
console.log(this.props);
// additional params for getTasks
const params = {
sort: 'name'
};
// another call for getTodos with names sorted
this.props.getTodos(params);
});
...
Note: You need to update your getTodos action to take in an optional params arguments (which is initialized to empty object if not passed).
Hope this helps you.
Live sandbox for this is present here - https://stackblitz.com/edit/react-redux-more-actions
Check out React-boilerplate. Great boilerplate for react and redux. They use redux-saga and redux-hooks as well.

Categories

Resources