Redux validation before dispatching axios call - javascript

Im trying to pass a validation test before making an axios call, but not sure how to implement it correctly its either validation or sending request straight away.
In my main container named "main.jsx" i have
const mapDispatchToProps = dispatch => {
return {
sendRequest: (query) => dispatch(request_house(query))
};
};
and then in my actions
import * as actionTypes from './actionTypes'
import serverConnect from "../../axios/connection";
export const set_house = (response) =>{
return {
type: actionTypes.SET_HOUSE,
response,
}
}
export const set_error = () =>{
return {
type: actionTypes.SET_ERROR
}
}
export const validate_request = (query) =>{
return {
type: actionTypes.VALIDATE_REQUEST,
query
}
}
export const request_house = (query) =>{
return dispatch =>{
serverConnect.post(`/searchHouse/${query}`).then(response => {
dispatch(set_house(response))
}).catch(()=>{
dispatch(set_error)
});
}
}
Is there a way to dispatch validation call before making a request? how should i approach this?

Redux doesn't support chaining of actions. That's where Redux-Thunk, Redux-Saga and other libraries come in to picture.
Check this blog, which explain in detail about doing chaining or asynchronous operations with different libraries.

Related

Perform a get request from a service worker based from Angular

Im migrating over from Angular5. In angular5 i was using services, now im trying to use react.
Im trying to call a function from globalService.js, and do something with it in app.js.
so far i got this error:
TypeError: _globalService__WEBPACK_IMPORTED_MODULE_5__.default.PerformRequest is not a function
test data:
app contains:
import doSomething from "./globalService";
console.log("doing a request",doSomething.PerformRequest('test'));
globalservice:
const PerformRequest = function(data) {
console.log("data is:",data);
}
export default PerformRequest;
test data end
Ideally im trying to translate my angular code to react code.
here is my angular old code:
app call:
click() {
this.GlobalService.PerformRequest('/crime').pipe(takeUntil(this._destroyed$)).subscribe((data:any) => {
DO SOMETHING WITH DATA HERE
});
}
globalservice call:
PerformRequest(params) {
console.log(this.baseURL + params);
return this.http.get(this.baseURL + params,this.options)
.pipe(
//catchError(this.handleError)
);
}
Q1: What am i doing wrong in my first example above?
Q2: How would i rewrite my angular data to react language so i can use it to call from a component the same was as im calling it?
Q1: Your import is not correct, it should be
import PerformRequest from './globalService';
if you keep the globalService the way it's currently return. If you do want something like a service you could export an object containing all the methods.
export default {
PerformRequest,
};
Q2:
I'd suggest you look into hooks to reproduce what you already have as a service.
You could do something like that:
import { useEffect, useState } from 'react';
// define a hook
const useFetchUrl = (url) => {
// useState are used to store some state
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState();
// useEffect is done for side effect
useEffect(() => {
fetch(url)
.then(resp => resp.json())
.then(data => setData(data);
}, [url];
return {
isLoading,
data,
};
}
const Component = () => {
const { loading, data } = useFetchUrl('/some/url');
if (!loading) {
return <div>Loading...</div>;
}
return <pre>{JSON.stringify(data)}</pre>;
};
swr is a nice library that provides you with hooks you could use to do the same kind of thing without having to write your own custom hooks and dealing with some more complicated cases.

how to solve this : Actions must be plain objects. Use custom middleware for async actions

I'm trying to clone one of MERN stack project called Emaily But I'm facing an error
Error: Actions must be plain objects. Use custom middleware for async actions.
My Action is given bellow ,
import axios from 'axios';
import { FETCH_USER, FETCH_SURVEYS } from './types';
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res.data });
};
You should remove async keyword from dispatch.
export const loadUsers = () => dispatch => { dispatch({ type: LOAD_USERS_LOADING });

is there a way to call another action from one action in react redux

I have two action in my redux actions... now I have a login form that makes a post request.. now I want to call another action from within the login action.
Now i connected the login function but not the setLoading function
e.g
export const loginUser = ()=>dispatch=>{
setLoading();
}
export const setLoading = ()=>({
type: SET_LOADING
})
You can use the logic of this answer to help you. An example is given in this answer(That's an example of getting products).
React Redux fetching data from backend approach
For your problem, it's best to pay attention to this part of the link
I gave you above, since you can use that logic for your program.
Notice this in the link above
// redux/product/product.actions.js
import { ShopActionTypes } from "./product.types";
import axios from "axios";
export const fetchProductsStart = () => ({
type: ShopActionTypes.FETCH_PRODUCTS_START
});
export const fetchProductsSuccess = products => ({
type: ShopActionTypes.FETCH_PRODUCTS_SUCCESS,
payload: products
});
export const fetchProductsFailure = error => ({
type: ShopActionTypes.FETCH_PRODUCTS_FAILURE,
payload: error
});
export const fetchProductsStartAsync = () => {
return dispatch => {
dispatch(fetchProductsStart());
axios
.get(url)
.then(response => dispatch(fetchProductsSuccess(response.data.data)))
.catch(error => dispatch(fetchProductsFailure(error)));
};
};

Re-rendering react component after clicking Like button (with Redux)

I have the following React component that shows all the users posts through the "renderPosts" method. Below it there's a like/unlike button on whether the currently logged in user has liked the post.
However, when I click on the like button, the component does not re-render in order for the "renderPosts" method to create an unlike button and the "like string" is modified as expected. Only when I go to another component and then come back to this component does the unlike button display and vice versa.
Is there anyway that I could fix this with Redux in my app? I tried this.forceUpdate after the onClick event but still does not work...
Also I tried creating a new Reducer called "likers", according to robinsax which basically get the array of users who like a particular post and imported it as props into the component but got
"this.props.likers.includes(currentUser)" is not a function
When the app first gets to the main page (PostIndex), probably because this.props.likers is still an empty object returned from reducer
Here is the code for my action creator:
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return{
type: LIKE_POST,
payload: post
}
}
export function unlikePost(username,postId){
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/unlikePost`,request);
return{
type: UNLIKE_POST,
payload: post
}
}
And this is my reducer:
import {LIKE_POST,UNLIKE_POST} from '../actions/index.js';
export default function(state = {},action){
switch(action.type){
case LIKE_POST:
const likers = action.payload.data.likedBy;
console.log(likers);
return likers;
case UNLIKE_POST:
const unlikers = action.payload.data.likedBy;
console.log(unlikers);
return unlikers;
default:
return state;
}
}
I would really appreciate any help since I'm a beginner
import { fetchPosts } from "../actions/";
import { likePost } from "../actions/";
import { unlikePost } from "../actions/";
class PostsIndex extends Component {
componentDidMount() {
this.props.fetchPosts();
}
renderPost() {
const currentUser = Object.values(this.props.users)[0].username;
return _.map(this.props.posts, post => {
return (
<li className="list-group-item">
<Link to={`/user/${post.username}`}>
Poster: {post.username}
</Link>
<br />
Created At: {post.createdAt}, near {post.location}
<br />
<Link to={`/posts/${post._id}`}>{post.title}</Link>
<br />
//error here, with this.props.likers being an
//array
{!this.props.likers.includes(currentUser) ? (
<Button
onClick={() => this.props.likePost(currentUser,post._id)}
bsStyle="success"
>
Like
</Button>
) : (
<Button
onClick={() => this.props.unlikePost(currentUser,post._id)}
bsStyle="warning"
>
Unlike
</Button>
)}{" "}
{post.likedBy.length === 1
? `${post.likedBy[0]} likes this`
: `${post.likedBy.length} people like this`}
</li>
);
});
}
function mapStateToProps(state) {
return {
posts: state.posts,
users: state.users,
likers: state.likers
};
}
}
Seems like the like/unlike post functionality isn't causing anything in your state or props to change, so the component doesn't re-render.
You should change the data structure you're storing so that the value of post.likedBy.includes(currentUser) is included in one of those, or forceUpdate() the component after the likePost and unlikePost calls.
Please do it the first way so I can sleep at night. Having a component's render() be affected by things not in its props or state defeats the purpose of using React.
As noted in other answers, you need to use redux-thunk or redux-saga to make async calls that update you reducer. I personally prefer redux-saga. Here's is a basic implementation of React, Redux, and Redux-Saga.
Redux-Saga uses JavaScript generator functions and yield to accomplish the goal of handling async calls.
Below you'll see a lot of familiar React-Redux code, the key parts of Redux-Saga are as follows:
watchRequest - A generator function that maps dispatch actions to generator functions
loadTodo - A generator function called from watchRequest to yield a value from an async call and dispatch an action for the reducer
getTodoAPI - A regular function that makes a fetch request
applyMiddleware - from Redux is used to connect Redux-Saga with createStore
const { applyMiddleware, createStore } = Redux;
const createSagaMiddleware = ReduxSaga.default;
const { put, call } = ReduxSaga.effects;
const { takeLatest } = ReduxSaga;
const { connect, Provider } = ReactRedux;
// API Call
const getTodoAPI = () => {
return fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => {
return response.json()
.then(response => response);
})
.catch(error => {
throw error;
})
};
// Reducer
const userReducer = (state = {}, action) => {
switch (action.type) {
case 'LOAD_TODO_SUCCESS':
return action.todo;
default:
return state;
}
};
// Sagas, which are generator functions
// Note: the asterix
function* loadTodo() {
try {
const todo = yield call(getTodoAPI);
yield put({type: 'LOAD_TODO_SUCCESS', todo});
} catch (error) {
throw error;
}
}
// Redux-Saga uses generator functions,
// which are basically watchers to wait for an action
function* watchRequest() {
yield* takeLatest('LOAD_TODO_REQUEST', loadTodo);
}
class App extends React.Component {
render() {
const { data } = this.props;
return (
<div>
<button onClick={() => this.props.getTodo()}>Load Data</button>
{data ?
<p>data: {JSON.stringify(data)}</p>
: null
}
</div>
)
}
}
// Setup React-Redux and Connect Redux-Saga
const sagaMiddleware = createSagaMiddleware();
const store = createStore(userReducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchRequest);
// Your regular React-Redux stuff
const mapStateToProps = (state) => ({ data: state }); // Map the store's state to component's props
const mapDispatchToProps = (dispatch) => ({ getTodo: () => dispatch({type: 'LOAD_TODO_REQUEST'}) }) // wrap action creator with dispatch method
const RootComponent = connect(mapStateToProps, mapDispatchToProps)(App);
ReactDOM.render(
<Provider store={store}>
<RootComponent />
</Provider>,
document.getElementById('root')
);
<script src="https://npmcdn.com/babel-regenerator-runtime#6.3.13/runtime.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/6.0.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/0.16.2/redux-saga.min.js"></script>
<div id="root"></div>
You need to use redux-thunk middleware in order to use async actions.
First, add redux-thunk while creating store like
import thunk from 'redux-thunk';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
then change your method like this
export function likePost(username,postId) {
return function(dispatch) {
// body...
const request = {
username,
postId
}
axios.post(`${ROOT_URL}/likePost`,request)
.then(res => {
dispatch({
type: LIKE_POST,
payload: res
});
});
}
}
and now in your component after mapStateToProps, define mapDispatchToProps,
const mapDispatchToProps = dispatch => {
return {
likePost: (currentUser,postId) => dispatch(likePost(currentUser, postId)),
// same goes for "unlike" function
}
}
export default connect(mapStateToProps, mapDispatchToProps)(PostsIndex);
The problem is in your action creator.
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
// this is an async call
const post = axios.post(`${ROOT_URL}/likePost`,request);
// next line will execute before the above async call is returned
return{
type: LIKE_POST,
payload: post
}
}
Because of that your state is likely never updated and stays in the initial value.
You would need to use either redux-thunk or redux-saga to work with async actions.
As they say use redux-thunk or redux-saga. If your new to redux I prefer redux-thunk because it's easy to learn than redux-saga. You can rewrite your code like this
export function likePost(username,postId) {
// body...
const request = {
username,
postId
}
const post = axios.post(`${ROOT_URL}/likePost`,request);
return dispatch => {
post.then(res => {
dispatch(anotherAction) //it can be the action to update state
});
}
}

Thunk Action Body not being Invoked

My thunk action doesn't seem to be running through its core logic. I tall the thunk action from componentDidMount but it doesn't in turn cause this to run: const response = await findOne(id).
Also, I thought I didn't need to explicitely pass dispatch as a prop to mapDispatchToProps if using redux-thunk, I thought that the way I have my thunk setup is that dispatch is available already to the thunk? And I've used other actions like this and it's worked fine, why not this one?
Thunk Action
export function fetchCompany(id) {
return async (dispatch) => {
try {
const response = await findOne(id)
if(response && response.body) {
const company = response.body
dispatch(companyReceived(company))
}
} catch(err) {
console.log("failed request in authenticate thunk action")
console.log(`error details: ${err.status} /n ${err}`)
}
}
}
Container
......
import { fetchCompany } from '../../client/actions/company/CompanyAsyncActions'
class InterviewContainer extends Component {
async componentDidMount() {
await fetchCompany(this.props.params.companyId)
}
render(){
return (this.props.company && <Interview className='ft-interview' company={this.props.company} />)
}
}
const mapStateToProps = state => ({
company: state.company.company
})
const mapDispatchToProps = {
fetchCompany: fetchCompany
}
export default connect(mapStateToProps, mapDispatchToProps)(InterviewContainer)
In the past, I haven't passed (dispatch) as a prop to mapDispatchToProps and it worked fine. But I see everyone else is doing so. How was my code working in the past if I wasn't doing that? And why isn't this working this time around in the example above?
Taking a look at another async action thunk container and call example, this is working completely fine, and I'm calling it the same way in another container
container
class HomePageContainer extends Component {
constructor(){
super()
}
async componentDidMount() {
await this.props.fetchFeaturedCompanies()
await this.props.fetchCompanies()
await this.props.fetchCountries()
}
render(){
return (<HomePage className='ft-homepage'
featuredCompanies={this.props.featuredCompanies}
countries={this.props.countries}
companies={this.props.companies}
/>)
}
}
const mapStateToProps = state => ({
countries: state.country.countries,
companies: state.company.companies,
featuredCompanies: state.company.featuredCompanies
})
const mapDispatchToProps = {
fetchCountries: fetchCountries,
fetchCompanies: fetchCompanies,
fetchFeaturedCompanies: fetchFeaturedCompanies
}
export default connect(mapStateToProps, mapDispatchToProps)(HomePageContainer)
thunk action
export function fetchCompanies() {
return async (dispatch, getState) => {
const response = await find()
if(response && response.body) {
const companies = response.body
dispatch(companiesReceived(companies))
}
}
}
In componentDidMount of InterviewContainer you're accidentally calling the imported fetchCompany, instead of this.props.fetchCompany.

Categories