I want to write a CRUD in react-redux, and have a problem with multiple dispatch. I think my dispatch is not returning a promise?
my error is "Uncaught TypeError: dispatch(...).then is not a function", on this line:
fetchPost: (id) => {
dispatch(fetchPost(id))
.then((result) => ...
Action
export function fetchPost(id) {
const request = axios.get(`${ROOT_URL}/posts/details/${id}`);
console.log(request);
return {
type: "FETCH_POST",
payload: request
}
}
export function fetchPostSuccess(post) {
return{
type: "FETCH_POST_SUCCESS",
payload: post
}
}
export function fetchPostError(error) {
return{
type: "FETCH_POST_ERROR",
payload: error
}
}
Reducer
case "FETCH_POST":
return {
...state,
loading: true,
activePost: state.activePost
}
case "FETCH_POST_SUCCESS":
return {
...state,
activePost: action.payload
}
case "FETCH_POST_ERROR":
return {
...state,
activePost: []
}
Component
class Details extends React.Component {
constructor(props){
super(props);
}
componentDidMount() {
this.props.fetchPost(this.props.detail_id.id);
}
render() {
return (
<div>
Details page
<ul>
<li >
{this.props.detail_id.id}
</li>
</ul>
</div>
)
}
}
Container
const mapStateToProps = (state, ownProps) => ({
posts: state.posts,
});
const mapDispatchToProps = dispatch => {
return {
fetchPost: (id) => {
dispatch(fetchPost(id))
.then((result) => {
if (result.payload.response && result.payload.response.status !== 200){
dispatch(fetchPostError(result.payload.response.data));
} else {
dispatch(fetchPostSuccess(result.payload.data));
}
})
},
resetMe: () => {
console.log('reset me');
}
};
};
const GetDetails = connect(
mapStateToProps,
mapDispatchToProps
)(Details)
I just want to pick post from postlist and show details on enother page...Hope someone help me to fix this issue
Saga
export function* fetchProducts() {
try {
console.log('saga')
const posts = yield call(api_fetchPost);
console.log(posts);
yield put({ type: "FETCH_SUCCESS", posts});
} catch (e) {
yield put({ type: "FETCH_FAILD", e});
return;
}
}
export function* watchFetchProducts() {
yield takeEvery("FETCH_POSTS", fetchProducts)
}
According to the Redux documentation, dispatch() returns the dispatched action, i.e. it's argument. The dispatched action is just a plain object which describes an action.
The promise is returned by Axios' get() method which is just an alias for axios() method.
Both call of the asynchronous method and the promise resolving should be done in Redux action. There is Redux Thunk middleware to handle such asynchronous actions. With Thunk middleware, you may return a function from your action. The function takes single argument dispatch, which is Redux's dispatch() function which you can call from functions resolving a promise.
With Redux Thunk middleware, your action fetchPost() will take the following view:
export function fetchPost(id) {
return function(dispatch) {
dispatch({type: 'FETCH_POST'})
axios.get(`${ROOT_URL}/posts/details/${id}`)
.then(function(response) {
if (response.status === 200){
dispatch({type: 'FETCH_POST_SUCCESS', payload: response.data})
} else {
dispatch({type: 'FETCH_POST_ERROR', payload: respose.data})
}
})
.catch(function(error) {
dispatch({type: 'FETCH_POST_ERROR', payload: error})
})
}
}
Your actions fetchPostSuccess() and fetchPostError() are unnecessary.
Related
I'm using Redux in my React App, and I need to put data to object (using action).
I have such reducer and action:
case GET_USER_DATA: {
return {
...state,
user: action.value // user - is object
}
}
//my action
export const getUserData = (value) => ({type: 'GET_USER_DATA',value})
And then I need to put data to my Redux state using action, but it not works, can anybody explaine me how to do this?
func:
authListener() {
fire.auth().onAuthStateChanged((user) => {
console.log(user);
if (user) {
getUserData(user) // I need to put it here
localStorage.setItem('user', user.uid);
} else {
getUserData(null)
localStorage.removeItem('user');
}
});
}
Have you added this function in mapDispatchToProps so that it can get the dispatch function and reducer can be called?
const mapStateToProps = (state: any) => {
return {
//any data from state
};
};
function mapDispatchToProps(dispatch: any) {
return bindActionCreators({
getUserData
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(YOUR_COMPONENT_NAME);
You can now call the function as
props.getUserData(user);
I want to know when my action has finished the request so I can treat data ( Kind of a promise ).
I'm using thunk to dispatch function.
Here is my action
export function addUser(nom,url) {
return (dispatch) =>{
axios.post('/', {
nom: nom,
url: url
})
.then(function (response) {
dispatch(()=>{//somthing for the reducer})
console.log(response);
})
}
and in my component I want to perform something like this
addUser('test','test')
.then() // do something after the addUser is executed
The way we do this is in redux is by dispatching an action on success like this:
const addUserSuccess = () => {
return {
type: 'ADD_USER_SUCCESS',
}
}
export function addUser(nom,url) {
return (dispatch) =>{
axios.post('/', {
nom: nom,
url: url
})
.then(function (response) {
dispatch(addUserSuccess());
console.log(response);
})
}
Now in your reducer to something like this:
const initialState = { addedUser: false };
export default (state = initialState, action) => {
switch (action.type) {
case 'ADD_USER_SUCCESS:
return {
...state,
addedUser: true
};
default:
return state;
}
}
Last but not least, connect your component to the store.
class ExampleComponent extends React.Component {
componentDidMount() {
addUser();
}
render() {
if (props.addedUser) {
// do something after the addUser is executed
}
return <div>Example</div>;
}
}
const mapStateToProps = (state) => ({
addedUser: state.user.addedUser
});
// connect is a react-redux HOC
const ConnectedComponent = connect(mapStateToProps)(ExampleComponent);
I know this is a lot of boilerplate but this is just a very basic overview. Find out more at Async Actions in the redux docs.
Update:
If you what to work with the promise instead, you could do the following:
export function addUser(nom, url) {
return (dispatch) =>{
return axios.post('/', {
nom: nom,
url: url
})
.then(function (response) {
dispatch(addUserSuccess());
console.log(response);
})
}
Then you could use it in the component.
addUser()().then();
Just make sure to call it twice, because addUser() is a function that returns a function that returns a promise
In the last couple of days I have been working on my Redux api call. I am actually having a problem getting the data back to the view component. Currently I'm able to see the data in the in the action generator, so I know at least I'm able to get it. However, nothing is showing in the view. I imagine it may have something to do with when it's loading. This is why I tried to load it when the component is rendering.
https://djangoandreact.herokuapp.com/user/1 is what is not loading.
codesandbox: https://codesandbox.io/s/zlor60q3jm?from-embed
Should be able to go to /user/1 at the end similar to going to /1 brings up an article(Tough Hope)
Heres the view component:
import React from "react";
import { connect } from "react-redux";
import { fetchUser } from "../store/actions/userActions";
class UserDetailView extends React.Component {
componentDidMount() {
const userID = this.props.match.params.userID;
fetchUser(userID); //fixed
}
render() {
const { user } = this.props.user;
console.log(user);
return (
<div>
<h3>{user.username}</h3>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.user
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchUser: dispatch(fetchUser(ownProps.match.params.userID))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserDetailView);
Action generator
import axios from "axios";
import { thunk } from "react-redux";
export function fetchUser(userID) {
console.log(userID);
return dispatch => {
return axios.get(`/api/user/${userID}`).then(res => {
dispatch(fetchUserSuccess(res.data));
console.log(res.data); // loads data
});
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const FETCH_USER_BEGIN = "FETCH_USER_BEGIN";
export const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
export const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";
export const fetchUserBegin = () => ({
type: FETCH_USER_BEGIN
});
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: { user }
});
export const fetchUserFailure = error => ({
type: FETCH_USER_FAILURE,
payload: { error }
});
Reducers(which are probably fine):
import {
FETCH_USER_BEGIN,
FETCH_USER_SUCCESS,
FETCH_USER_FAILURE
} from "../actions/actionTypes";
const initialState = {
user: {},
loading: false,
error: null
};
export default function userReducer(state = initialState, action) {
switch (action.type) {
case FETCH_USER_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
case FETCH_USER_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
user: {}
};
default:
return state;
}
}
folks. I found it.
case FETCH_USER_SUCCESS:
return {
...state,
loading: false,
user: action.payload.user
};
user is supposed to be user:action.payload
Also, the user action was supposed to be
export const fetchUserSuccess = user => ({
type: FETCH_USER_SUCCESS,
payload: user
})
WOOOOW. But, honestly, I learned so much about Redux in the last two sleepless nights, it was worth the pain. Really was. Now, instead of copy pasta, I know what an action generator is and does, and reducer (obvi)
Redux newbie here
So I have this code that dispatches an async action and it uses promise to handle the request
So here is mapDispatchToProps
const mapDispatchToProps = (dispatch) => {
return {
fetchUserList: () => {
console.log('---In fetchUserList---');
dispatch({
type: FETCH_USER_LIST,
payload: new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
console.log('xhr: ', xhr);
xhr.open('GET', 'http://localhost:5000/data/fetch/users');
xhr.onload = () => {
console.log(' --- in onload function ---');
console.log(xhr.responseText);
resolve(JSON.parse(xhr.responseText));
}
xhr.onerror = () => {
console.log(' --- in onerror function ---');
reject(xhr.statusText);
}
xhr.send();
})
});
}
what it does is it fetches array of json objects from the server which get map to
mapStateToProps
const mapStateToProps = (state) => {
return {
userlist: state.list_reducer,
};
};
Now here is the component I used both of above:
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
listRender: false
};
}
render() {
console.log('In render: ');
console.log(this.props.userlist);
return (
// basic list rendering template
);
}
componentDidMount() {
this.props.fetchUserList();
this.setState({
listRender: true
});
}
Now It can see from the above that I use fetchUserList() in componentDidMount() to fetch data from the server and data does get send to the reducer
Here is the list_reducer.js:
export default function list_reducer(state = {
operation: '',
userlist: []
}, action) {
switch (action.type) {
case "FETCH_USER_FULFILLED":
console.log("FETCH_USER_FULFILLED");
console.log(action.payload);
return {
operation: 'success',
userlist: action.payload
}
case "UPDATE_USER_DETAILS_FULFILLED":
return {
operation: 'success',
userlist: action.payload
}
case "DELETE_USER_FULFILLED":
return {
operation: 'success',
userlist: action.payload
}
case "REGISTER_USER_FULFILLED":
return {
operation: 'success',
//userlist:
}
default:
return { ...state
}
}
};
Now instead of receiving newly fetched userlist I get the default data that is passed to reducer in this.props.userlist (mapStateToProps)
So the question is how do I get the newly fetched list instead of the default state data that is given to the reducer.
When making async calls with Redux I use Thunks
By default, Redux action creators don’t support asynchronous actions
like fetching data, so here’s where we utilise Redux Thunk. Thunk
allows you to write action creators that return a function instead of
an action. The inner function can receive the store methods dispatch
and getState as parameters.
So, if you wanted to make an API call, our action would look like this.
Actions.js (or your equivalent)
import axios from "axios";
export const receiveUserLift = json = {
return {
type: "RECEIVE_USERS",
payload: json
}
}
export function fetchUserList() {
return dispatch => {
return axios
.get('http://localhost:5000/data/fetch/users')
.then(response => {
dispatch(receiveUserLift(response))
})
.catch(error => console.log("FetchUserList Axios Error", error))
}
}
Reducer would include the following
case "RECEIVE_USERS":
return Object.assign({}, currentState, {
userList: action.payload
});
Component would include the following
import { fetchUserList } from "WHERE WE DECLARED OUR ARE ACTION";
class Example extends React.Component {
componentDidMount() {
// You could use it anywhere, but this is how you would call the action
this.props.fetch()
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.users === prevState.users) return null;
return {
users: nextProps.users
};
}
}
const mapStateToProps = (state, ownProps) => {
return {
users: state.userList
};
};
const mapDispatchToProps = (dispatch, ownProps) => ({
fetch: () => dispatch(fetchUserList())
});
Sidenote
Receiving the data in our component asynchronously requires that we make use of component lifecycle methods. You will see a lot of guides around the internet advising to use componentWillReceiveProps to do this, but this is going to be removed in future versions of React and replaced with static getDerivedStateFromProps you can read more about it here
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.users === prevState.users) return null;
return {
users: nextProps.users
};
}
The above code serves as a rough guide, but it is the general pattern that I've followed when dealing with Async API calls and rendering with Redux
In my store.js i have the following code:
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
const reducer = (state, action) => {
console.log(action.type)
if (action.type === 'LOAD_USERS') {
return {
...state,
users: action.users['users']
}
} else if (action.type === 'LOAD_CHATROOMS') {
return {
...state,
chatRooms: action.chatRooms['chatRooms']
}
}
return state;
}
export default createStore(reducer, {users:[], chatRooms:[]}, applyMiddleware(thunk));
the code inside the action.type === 'LOAD_CHATROOMS' is never accessed for some reason, this is the action file where i set the action type for the reducer:
import axios from 'axios'
axios.defaults.withCredentials = true
const loadUsers = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_USERS',
users: response.data
});
});
};
};
const logIn = user => {
return axios.post('http://localhost:3000/session', {
user_id: user.id
})
.then(response => {
//TODO do something more relevant
console.log('loged in');
});
};
const loadChatRooms = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_CHATROOMS',
chatRooms: response.data
});
});
};
};
const enterChatRoom = chatrom => {
};
export { loadUsers, logIn, enterChatRoom, loadChatRooms};
The 'Load methods' get the data that i use to populate both components (one for users list and the other one for chatrooms list ), both components are called at the same level in the app.js file.
Basically the output that i'm getting is the first component (users) as expected with the correct list, and the chatrooms component is also rendered but the data is not loaded (since it's corresponding reducer block is not accessed).
Thanks a lot for reading :)