Async api fetch with redux thunk - javascript

I'm having trouble fetching a list of users from an api. I think issue might be in my mapDispatchToProps function but I'm not sure. Everything else seems fine to me. I'm new to redux and I'm kinda having a hard time wrapping my head around it so any help is appreciated
The list with the users would ideally be displayed as soon as the component mounts. I did the same thing without redux store and it was working just fine, I'm just not really sure how to integrate redux
Actions
export const startLoading = () => {
return {
type: START_LOADING
}
}
export const updateUserData = payload => {
return {
type: UPDATE_USER_DATA,
payload
}
}
export const updateUserError = payload => {
return {
type: UPDATE_USER_ERROR,
payload: payload
}
}
export function fetchUsers() {
return dispatch => {
dispatch(startLoading());
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => {
data = data.filter(user => user.id < 4);
data.forEach(user => {
user.isGoldClient = false;
user.salary = '4000';
user.photo = userThumbnail;
})
.then(data => {
dispatch(updateUserData(data));
}).catch(error => {
dispatch(updateUserError(error));
})
});
};
};
Reducers
const initialState = {
loading: false,
users: [],
error: null
};
export function userReducer(state=initialState, action){
switch(action.type){
case START_LOADING:
return {
...state,
loading: true
}
case UPDATE_USER_DATA:
return {
...state,
loading: false,
users: action.payload,
error: null
}
case UPDATE_USER_ERROR:
return {
...state,
error: action.payload,
loading: false,
users: []
};
default:
return state;
};
};
Component
class Home extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [],
usersAreDisplayed: true
};
}
componentDidMount() {
fetchUsers();
}
render(){
return (
<UserList users={this.state.users} />
)
}
}
function mapStateToProps(state){
return { users: state.users }
}
function mapDispatchToProps(dispatch){
return {
fetchUsers: payload => dispatch(updateUserData(payload)),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Home);

Looks like you are not calling the actual fetchUsers at all.
Change the component code like this
function mapStateToProps(state){
return { users: state.users }
}
// remove this function
// function mapDispatchToProps(dispatch){
// return {
// fetchUsers: payload => dispatch(updateUserData(payload)),
// }
// }
export default connect(mapStateToProps, {fetchUsers})(Home); //<---- destructure it here. Also import the function (action)
1a. fetchUsers function needs to be accessed using this.props
componentDidMount() {
this.props.fetchUsers();
}
There is an extra then block after forEach.
Remove it.
export function fetchUsers() {
return (dispatch) => {
dispatch(startLoading());
fetch("https://jsonplaceholder.typicode.com/users")
.then((res) => res.json())
.then((data) => {
data = data.filter((user) => user.id < 4);
data.forEach((user) => {
user.isGoldClient = false;
user.salary = "4000";
user.photo = userThumbnail;
});
dispatch(updateUserData(data)); // <------ no extra .then is required
})
.catch((error) => {
dispatch(updateUserError(error));
});
};
}
Also <UserList users={this.state.users} /> needs to be <UserList users={this.props.users} /> As already mentioned by #Nsevens

You are mapping redux state into your component's props.
So you should load the users from the component's props and not it's state:
render(){
return (
<UserList users={this.props.users} />
)
}

Related

React class Component not re render after props change from redux action

The component does not re render after successfully update state in redux
i have tried to do some condition in componentShouldUpdate end up with loading true without change
reducer.js
import * as types from "./actionsType";
const INITIAL_STATE = {
slide_data: [],
error: null,
loading: false,
};
const updateObject = (oldObject, updatedProperties) => {
return {
...oldObject,
...updatedProperties,
};
};
const slideReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.SLIDES_FETCH_START:
return updateObject(state, {
error: null,
loading: true,
});
case types.SLIDES_FETCH_SUCSSES:
return updateObject(state, {
slide_data: action.payload,
error: null,
loading: false,
});
case types.SLIDES_FETCH_FAIL:
return updateObject(state, {
error: action.error,
loading: false,
});
default:
return state;
}
};
export default slideReducer;
actions.js
import * as types from "./actionsType";
import axios from "axios";
import { selectSlides } from "./slides.selectors";
export const slidesStart = () => {
return {
type: types.SLIDES_FETCH_START,
};
};
export const slidesSucces = (slides) => {
return {
type: types.SLIDES_FETCH_SUCSSES,
payload: slides,
};
};
export const slidesFail = (error) => {
return {
type: types.SLIDES_FETCH_FAIL,
error: error,
};
};
export const fetchSlides = () => {
return (dispatch) => {
console.log("fetch Start");
dispatch(slidesStart());
axios
.get("http://127.0.0.1:8000/slides/", {
headers: {
"Content-Type": "application/json",
},
})
.then((res) => {
dispatch(slidesSucces(res.data));
})
.catch((err) => dispatch(slidesFail(err)));
};
};
component
class IntroPage extends Component {
constructor(props) {
super(props);
this.tlitRef = React.createRef();
this.titlelRef = React.createRef();
this.subTitleRef = React.createRef();
this.showcase = React.createRef();
}
componentDidMount() {
this.props.fetchSlides();
}
render() {
const { slides, loading } = this.props;
if (loading) {
return <h1>Loading</h1>;
}
return (
<div className="intro">
<div className="wrapper">
{slides.map((data) => (
<SwiperSlides data={data} key={data.name} />
))}
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
loading: state.slides.loading,
error: state.slides.error,
slides: state.slides.slide_data,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchSlides: () => dispatch(fetchSlides()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(IntroPage);
Register the redux-logger correctly. The data was returned but nothing changes when I do redux-persist and try to reload data come through
update ::
when I change the size of the browser data it correctly appears what is this !!
update : this problem related to swiperjs
and the solution will be like that:
1 - assign swiper instance to React.CreateRef(null) : this.swiper = React.CreateRef(null)
2 - in componentDidUpdate() make a swiper update : this.swiper.current.update()
4 - use a arrow function syntax in swiper on functions to refer to the outer scope

Unable to handle state 'loading' properly in react with redux

Hey guys just moved to redux so in react what i was doing was in componentDidMount(), i was calling api and soon as i received the data i was setting loading to false (initially loading was true) to get rid of the 'react spinner',
but after using redux now in componentDidMount() i am calling my action creater which is in another and there i am receving my data so how do i manage 'loading' here ? can i somehow pass something from action creater to my component that triggers state and set loading to false ? or is there any other to do it ? How do you all manage it ?
here is my code
Home.js
class home extends Component {
UNSAFE_componentWillMount() {
this.props.verifyToken();
}
componentDidMount() {
this.props.categoryAction();
}
constructor(props) {
super(props);
this.state = {
categoriesWithTheirImages: [],
displayToggle: false,
loading: false,
};
}
renderCategory = () => {
return this.props.allCategories.map((item) => {
return (
<div
className="category_div"
key={item._id}
onClick={() => this.setState({ displayToggle: true })}
>
<img
src={item.image}
alt="miss-mistake"
className="category_image_home"
/>
<span className="category_heading_home">{item.categoryName}</span>
</div>
);
});
};
render() {
if (this.state.loading) {
return (
<div className="sweet-loading-main">
<FadeLoader
css={override}
sizeUnit={"px"}
size={50}
color={"#ff9d72"}
loading={this.state.loading}
/>
</div>
);
} else {
console.log(this.props.allCategories);
return (
<React.Fragment>
{/* <Fade left> */}
<Header />
<div className="main_content_homepage">
<p className="category_select">Please select a category</p>
<div className="category_list">{this.renderCategory()}</div>
</div>
{this.renderStoryActionDialog()}
{/* </Fade> */}
</React.Fragment>
);
}
}
}
const mapStateToProps = (state) => {
console.log(state);
const images = [family, ring, beer, feedback, academic];
let categoriesWithImages = state.getCategoryReducer.map((item, index) => {
item.image = images[index];
return item;
});
console.log(categoriesWithImages);
return { allCategories: categoriesWithImages };
};
export default connect(mapStateToProps, { verifyToken, categoryAction })(home);
and my action.js file
import { CATEGORY } from "../actionTypes";
export const categoryAction = ()=> {
return dispatch => {
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
console.log(response)
dispatch({ type: CATEGORY, payload: response });
})
.catch(err => console.log("Eror in adding", err));
};
};
reducer file
import { USER, CATEGORY} from "../actionTypes";
const getCategoryReducer = (state = [], action) => {
switch (action.type) {
case CATEGORY:
return action.payload;
default:
return state;
}
};
export default getCategoryReducer;
You should handle the loading state in your reducer file. At the moment, it's defined in your Component file. For e.g when you dispatch the action, it should update your loading state too. I would do something like this in reducer.
import { USER, FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL} from "../actionTypes";
const INITIAL_STATE = {
loading: false,
err: false,
data: []
}
const getCategoryReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY:
return Object.assign({}, state, {
loading: true,
data: [],
})
case FETCH_CATEGORY_SUCCESS
return Object.assign({}, state, {
loading: false,
data: action.payload,
})
case FETCH_CATEGORY_FAIL
return Object.assign({}, state, {
loading: false,
data: action.payload,
err: true
})
default:
return state;
}
};
export default getCategoryReducer;
and your action file would look something like this
import { FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL } from "../actionTypes";
export const categoryAction = ()=> {
//setting loading to true
return dispatch => {
dispatch({ type: FETCH_CATEGORY });
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
//setting loading to false
dispatch({ type: FETCH_CATEGORY_SUCCESS, payload: response });
})
.catch(err => console.log("Eror in adding", err); dispatch({ type: FETCH_CATEGORY_FAIL, payload: err }););
};
};
You can then read the loading props in your Home.js

data is fetched but the state is not updated

I'm fetching data from an endpoint. But the state is not updated. it's always undefined.
For some reason this.props.users is undefined. Am I doing something wrong?
After componentDidMount() I trigger the action fetchUsers that send a request to the endpoint. The data is fetched successfully but at the end the state is not updated.
This is my Layout component
class Layout extends Component {
render() {
return (
<div className="container">
{
this.props.users.map((user, key) => {
return <a className="list-group-item list-group-item-action active">User #{user.id}</a>
})
}
</div>
)
}
}
const mapStateToProps = state => {
return {
channels: state.users.data,
}
}
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () =>
dispatch(user.fetchUsers()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Layout);
This the action file
export const fetchUsers = () => {
return (dispatch, getState) => {
let headers = { "Content-Type": "application/json" };
return fetch("http://127.0.0.1:3030/api/users/", { headers, })
.then(res => {
if (res.status < 500) {
return res.json().then(data => {
return { status: res.status, data };
})
} else {
console.log("Server Error!");
throw res;
}
})
.then(res => {
if (res.status === 200) {
dispatch({ type: 'USERS_FETCHED', data: res.data });
return res.data;
}
})
}
}
And this is the reducer
const initialState = {
users: []
};
export default function channels(state = initialState, action) {
switch (action.type) {
case 'USERS_FETCHED':
return { ...state, users: action.data };
default:
return state;
}
}
I think the error comes from your call to the dispatcher in the mapDispatchToProps. Since you are exporting directly the function fetchUsers, you should not be calling user.fetchUsers.
const mapDispatchToProps = dispatch => {
return {
fetchUsers: () =>
dispatch(fetchUsers()),
}
}

How to get response value from API in action.js using React

I've read many examples about this and got no result for my problem, I want to get the values inside MySQL database by using localhost, code with PHP and return the value as JSON format e.g.
[
{"id":"7",
"name":"Sammy",
"address":"New York",
"age":"42"}
]
with this format, I can fetch the data by using this code in GetApi.js
class GetApi {
static getAllUsers() {
return fetch('http://192.168.1.199/App/show_all_data.php')
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(itemsIsLoading(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(itemsFetchDataSuccess(items)))
.catch(() => dispatch(itemsHasErrored(true)));
}
}
export default GetApi;
here's the action.js
import GetApi from '../../api/GetApi';
export function itemsHasErrored(bool: boolean) {
return {
type: "ITEMS_HAS_ERRORED",
hasErrored: bool
};
}
export function itemsIsLoading(bool: boolean) {
return {
type: "ITEMS_IS_LOADING",
isLoading: bool
};
}
export function itemsFetchDataSuccess(items: Object) {
return {
type: "ITEMS_FETCH_DATA_SUCCESS",
items
};
}
export function itemsFetchData(url: any) {
return function(dispatch) {
return GetApi.getAllUsers().then(items => {
dispatch(itemsFetchDataSuccess(items));
dispatch(itemsIsLoading(false));
}).catch(error => {
throw(error);
});
};
}
here's the reducer.js
const initialState = {
isLoading: true,
hasErrored: false,
items: []
};
export default function(state: any = initialState, action: Function) {
switch (action.type) {
case "ITEMS_HAS_ERRORED":
return { ...state, hasErrored: action.hasErrored };
case "ITEMS_IS_LOADING":
return { ...state, isLoading: action.isLoading };
case "ITEMS_FETCH_DATA_SUCCESS":
return { ...state, items: action.items };
default:
return state;
}
}
called action.js function in index.js
import { itemsFetchData } from "../../actions";
...
all codings that were not related with calling action.js
...
const navigation = this.props.navigation;
let items = this.props.items;
if (items.hasOwnProperty('item')) {
items = items.item
}
return (
<List
dataArray={this.props.items}
renderRow={(
data
) =>
<ListItem icon style={styles.listitem}>
<Left>
<Text>
{data.name}
</Text>
</Left>
<Right>
<Text>
{data.address}
</Text>
</Right>
</ListItem>}
/>
);
function bindAction(dispatch) {
return {
fetchData: url => dispatch(itemsFetchData(url))
};
}
const mapStateToProps = state => ({
items: state.homeReducer.items,
hasErrored: state.homeReducer.hasErrored,
isLoading: state.homeReducer.isLoading
});
export default connect(mapStateToProps, bindAction)(ShowData);
I got no results when I'm running the code, it's just showed the loading icon. even when I set isLoading:false, the home menu showed up without the data
I'm just trying to minimize the code inside index.js because it's too long to post that here. I will do that if necessary in the next comment.
I recommend using epics , below is an example link for you to follow.
Epic Example
You can look at the actions and data ajax calls from epic and how it connects back to the action.
Note: Axios is been used here instead of fetch api...

Trouble getting child component to update in Redux

I'm building a simple CRUD note app and I'm having issues getting the child components to update after simple POST and DELETE api calls to create and delete notes.
Here's the parent component with a simple form and a child component <NotepadsShowView /> to render the submitted notes.
class AuthenticatedHomeView extends Component {
_handleSubmit(e) {
e.preventDefault()
const { dispatch } = this.props
const data = {
title: this.refs.title.value,
description: this.refs.description.value,
private: this.refs.private.checked
}
dispatch(Actions.createNotepad(this.props.currentUser.id, data))
this._resetForm()
}
_resetForm() {
this.refs.title.value = ''
this.refs.description.value = ''
this.refs.private.checked = true
}
render() {
return (
<div>
<form onSubmit={::this._handleSubmit}>
{/* form removed for clarity */}
</form>
<NotepadsShowView/>
</div>
)
}
}
and the NotepadsShowView child component:
class NotepadsShowView extends Component {
componentWillMount() {
const { dispatch, currentUser } = this.props
dispatch(Actions.fetchNotepads(currentUser.id))
}
_renderEachOwnedNotepad() {
const { ownedNotepads } = this.props
return ownedNotepads.map((notepad, i) => {
return <NotepadShowView key={notepad.id} {...notepad} {...this.props}/>
})
}
render() {
const { isFetchingNotepads } = this.props
const notepads = this._renderEachOwnedNotepad()
if (isFetchingNotepads || notepads.length == 0) return null
return (
<ul className="notepads-container">
{notepads}
</ul>
)
}
}
const mapStateToProps = (state) => ({
isFetchingNotepads: state.notepads.fetching,
currentUser: state.session.currentUser,
ownedNotepads: state.notepads.ownedNotepads,
sharedNotepads: state.notepads.sharedNotepads
})
export default connect(mapStateToProps)(NotepadsShowView)
Here is the action creator:
const Actions = {
createNotepad: (userId, data) => {
return dispatch => {
httpPost(`/api/v1/users/${userId}/notepads`, {data: data})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_CREATED,
notepad: data
})
})
.catch(error => {
error.response.json()
.then(json => {
dispatch({
type: CONSTANTS.NOTEPADS_CREATE_ERROR,
errors: json.errors,
})
})
})
}
},
fetchNotepads: (userId) => {
return dispatch => {
dispatch({
type: CONSTANTS.NOTEPADS_FETCHING
})
httpGet(`/api/v1/users/${userId}/notepads`, {id: userId})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_RECEIVED,
notepads: data.notepads
})
})
.catch(error => {
error.response.json()
.then(json => {
dispatch({
type: CONSTANTS.NOTEPADS_ERRORS,
errors: json.error
})
})
})
}
},
deleteNotepad: (userId, notepadId) => {
return dispatch => {
httpDelete(`api/v1/users/${userId}/notepads/${notepadId}`, {id: notepadId})
.then(data => {
dispatch({
type: CONSTANTS.NOTEPADS_OWNED_DELETE,
id: notepadId
})
})
}
},
}
Here is the reducer:
const initialState = {
ownedNotepads: [],
fetching: true,
}
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case CONSTANTS.NOTEPADS_FETCHING:
return {
...state,
fetching: true,
}
case CONSTANTS.NOTEPADS_RECEIVED:
return {
...state,
fetching: false,
ownedNotepads: action.notepads
}
case CONSTANTS.NOTEPADS_CREATED:
return {
...state,
ownedNotepads: [
...state.ownedNotepads,
{
id: action.id,
title: action.title,
description: action.description,
private: action.private
}
]
}
case CONSTANTS.NOTEPADS_OWNED_DELETE:
const index = state.ownedNotepads.findIndex(note => note.id === action.id)
return {
...state,
ownedNotepads: [
...state.ownedNotepads,
state.ownedNotepads.slice(0, index),
state.ownedNotepads.slice(index + 1)
]
}
default:
return state
}
}
A user submits a new notepad which triggers an POST api call. Server returns the new notepad and the reducer adds the notepad to the Redux state. No issues here. However, when a notepad is created the notepad props are undefined and no new notepads are being shown in the child UI components. They don't know of the update and I assume it's because I'm not handling the state update.
I am using componentWillMount (cWM) above to fetch the updated notepads state before the initial render. I'm assuming I should use componentWillReceiveProps but I understand there will be an infinite loop if I dispatch the fetchNotepads action because the dispatch in cWM will run again.
My question is how do I update the component props when the Redux state changes? Do I have to use component state? What about the lifecycle methods?

Categories