How to getById in redux - javascript

I am newbie in react and redux, trying to fetch data using api, but trying to fetch by id, unfortunately, id is undefined in redux logger and no result back from server side,
Here is my code
My route <Route path="/admin/:id" component={AdminPage} />
Action
function getAll(id){
return dispatch =>{
dispatch(request(id));
adminService.getAll(id)
.then( admin => {
dispatch(success(admin));
dispatch(alertActions.success('Welcome Back !'));
},error => {
dispatch(failure(error.toString()));
dispatch(alertActions.error(error.toString()));
});
};
function request(id) { return { type: adminConstants.GETALL_REQUEST, id } }
function success(admin) { return { type: adminConstants.GETALL_SUCCESS, admin } }
function failure(error) { return { type: adminConstants.GETALL_FAILURE, error } }
Reducer
export function admin( state={}, action){
switch(action.type){
case adminConstants.GETALL_REQUEST:
return { loading: true, id : action.id };
case adminConstants.GETALL_SUCCESS:
return { items: action.admin };
case adminConstants.GETALL_FAILURE:
return { error: action.error };
default:
return state
}
}
Service
function getAll(id){
const requestOptions = {
method : 'GET',
headers: authHeader()
};
return fetch(`${apiUrl}/admin/${id}`, requestOptions).then(handleResponse)
.then( data => { return data; });
}
AdminPage
class AdminPage extends React.Component{
componentDidMount(){
this.props.dispatch(adminActions.getAll());
}
render(){
const { admin } = this.props;
return(
<div>
<h3> Admin Panel</h3>
{admin.items &&
<ul>
{admin.items.map((data, index) =>
<li key={data.id}>
email id : {data.email},
end date : {data.dateEnd},
customerId : {data.customerId}
</li>
)}
</ul>
}
</div>
);
}
}
function mapStateToProps(state) {
const { admin } = state;
return { admin};
}
const connectedAdminPage = connect(mapStateToProps)(AdminPage);
export { connectedAdminPage as AdminPage };
Link on Home page
<Link to="/admin/5c4f69d5259f7d14434b4cb6">Admin</Link>

If you're trying to get the 'id' in
Route path="/admin/:id">
then try
console.log("mark1",this.props.match.params.id)
inside 'AdminPage' component.
If it works then you can pass it to the dispatcher via the component and use inside your function.
For more documentation try https://reacttraining.com/react-router/web/example/url-params.
If this is not what you were asking comment below and I'll edit accordingly. :)
Edit: So here is the solution for the 'AdminPage'
class AdminPage extends React.Component{
componentDidMount(){
this.props.dispatch(adminActions.getAll(this.props.match.params.id));
}
...
}
Now it should not be undefined in the action, can you confirm?

Related

Async api fetch with redux thunk

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} />
)
}

Promises in react.js, model attribute is promise instead of array

I'm trying to implement a restaurant app where a user can add dishes to a menu. The menu will be displayed in a side bar. Dish information is provided through an API. I'm having issues with the API requests/promises. I'm storing a list of the dishes in DinnerModel. I'm making the requests to the API in DinnerModel.
When I add a dish to the menu by clicking the add button in IngredientsList, I get redirected to a screen that shows Sidebar. But in Sidebar, the dishes are NaN. The console.logs show that this.state.menu in Sidebar is actually a Promise, not an array. I'm having trouble understanding why this is and what to do about it.
Note that update in Sidebar is supposed to run modelInstance.getFullMenu() which returns an array. But instead, a promise is returned. Why? What can I do to fix this?
Here's my code:
Dinnermodel.js:
const DinnerModel = function () {
let numberOfGuests = 4;
let observers = [];
let selectedDishes = [];
// API Calls
this.getAllDishes = function (query, type) {
const url = 'https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/search?query='+query+"&type="+type;
return fetch(url, httpOptions)
.then(processResponse)
.catch(handleError)
}
//function that returns a dish of specific ID
this.getDish = function (id) {
let url = "https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/"+id+"/information";
return fetch(url, httpOptions)
.then(processResponse)
.catch(handleError)
}
// API Helper methods
const processResponse = function (response) {
if (response.ok) {
return response.json();
}
throw response;
}
this.addToMenu = function(id, type){
var newDish = this.getDish(id).then()
newDish.dishType = type;
selectedDishes.push(newDish);
notifyObservers();
}
//Returns all the dishes on the menu.
this.getFullMenu = function() {
return selectedDishes;
}
DishDetails.js:
class DishDetails extends Component {
constructor(props) {
super(props);
this.state = {
id: props.match.params.id,
status: "INITIAL",
type: props.match.params.type,
};
}
addToMenu (){
modelInstance.addToMenu(this.state.id, this.state.type);
this.props.history.push("/search/"+this.state.query+"/"+this.state.type);
}
componentDidMount = () => {
modelInstance.getDish(this.state.id)
.then(dish=> {
this.setState({
status:"LOADED",
ingredients: dish.extendedIngredients,
dishText: dish.winePairing.pairingText,
pricePerServing: dish.pricePerServing,
title: dish.title,
img: dish.image,
instructions: dish.instructions,
})
})
.catch(()=>{
this.setState({
status:"ERROR",
})
})
}
render() {
switch(this.state.status){
case "INITIAL":
return (
<p>Loading...</p>
);
case "ERROR":
return (
<p>An error has occurred, please refresh the page</p>
);
}
return (
<IngredientsList ingredients={this.state.ingredients} pricePerServing={this.state.pricePerServing} id={this.state.id} onButtonClick={() => this.addToMenu()}/>
<Sidebar />
);
}
}
export default withRouter(DishDetails);
Sidebar.js:
class Sidebar extends Component {
constructor(props) {
super(props)
// we put on state the properties we want to use and modify in the component
this.state = {
numberOfGuests: modelInstance.getNumberOfGuests(),
menu: modelInstance.getFullMenu(),
}
modelInstance.addObserver(this);
}
// this methods is called by React lifecycle when the
// component is actually shown to the user (mounted to DOM)
// that's a good place to setup model observer
componentDidMount() {
modelInstance.addObserver(this)
}
// this is called when component is removed from the DOM
// good place to remove observer
componentWillUnmount() {
modelInstance.removeObserver(this)
}
handleChangeGuests(event){
let noOfGuests = event.target.value;
modelInstance.setNumberOfGuests(noOfGuests);
}
// in our update function we modify the state which will
// cause the component to re-render
update() {
this.setState({
numberOfGuests: modelInstance.getNumberOfGuests(),
menu: modelInstance.getFullMenu(),
})
console.log("menu in Sidebar.js");
console.log(this.state.menu);
}
render() {
//console.log(this.state.menu);
let menu = this.state.menu.map((dish)=>
<div key={"menuitem-"+dish.id} className="menuitemwrapper">
<div className="menuitem">
<span className="dishname">{dish.title}</span>
<span className="dishprice">{dish.pricePerServing*modelInstance.getNumberOfGuests()}</span>
</div>
</div>
);
return (
<div id="sidebar-dishes">
{menu}
</div>
);
}
}
export default Sidebar;
IngredientsList.js:
class IngredientsList extends Component{
constructor(props){
super(props);
this.state = {
ingredients: props.ingredients,
pricePerServing: props.pricePerServing,
id: props.id,
noOfGuests: modelInstance.getNumberOfGuests(),
}
modelInstance.addObserver(this);
}
update(){
if(this._ismounted==true){
this.setState({
noOfGuests: modelInstance.getNumberOfGuests(),
});
}
}
componentDidMount(){
this._ismounted = true;
}
componentWillUnmount(){
this._ismounted = false;
}
render () {
return (
<button onClick={() => this.props.onButtonClick()} type="button" className="btn btn-default">Add to menu</button>
);
}
}
export default IngredientsList;
EDIT:
Changed DinneModel.addToMenu to:
this.addToMenu = function(id, type){
var newDish = this.getDish(id)
.then(()=>{
newDish.dishType = type;
selectedDishes.push(newDish);
notifyObservers();
});
}
I still get a promise logged in the console from the console.log in Sidebar.js, and NaN in the Sidebar render.
getDish is not in your code posted, but I assume that it returns a promise. And this.getDish(id).then() also returns a promise. That’s why selectedDishes array has promises in it.
this.addToMenu = function(id, type){
var newDish = this.getDish(id).then()
newDish.dishType = type;
selectedDishes.push(newDish);
notifyObservers();
}
To get actual newDish data, you need to use a callback function for the then.
this.addToMenu = function(id, type){
this.getDish(id).then(function (newDish) {
newDish.dishType = type;
selectedDishes.push(newDish);
notifyObservers();
});
}

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...

React/Redux firing action->render before update a store

A have a simply react/redux app. I Fetch data from API async but component not waiting for data and firing render.
class RestaurantList extends React.Component {
componentWillMount() {
this.props.getRestaurantList();
}
render() {
console.log("render");
let {translation} = store.getState().app;
//------------I NEED DATA ON THIS LET (restaurantList)
let {restaurantList} = this.props.restaurants;
return (
<div>
<TableContainer data={restaurantList}/>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
restaurants: state.restaurants
};
};
const mapDispatchToProps = (dispatch) => {
return {
getRestaurantList() {
dispatch(ACTIONS.getRestaurantList());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(RestaurantList);
On my action i fetching data using axios :
export function getRestaurantList() {
console.log("action");
return dispatch => {
axios({
method: "GET",
url: URLS.BASE_URL + URLS.URL_RESTAURANT_LIST
}).then((response) => {
console.log(response);
dispatch({
type: CONST.GET_RESTAURANT_LIST,
payload: response.data
})
})
}
}
And my component fired method ComponenWillMount after that render () and next store which update store and set good data to my variable. Maybe u give me advice how to do that to have on my render my fetching data because now on my table I transfer undefined on start. Maybe you give me an example to using another framework like redux-saga or other.
You could try conditionally rendering your TableContainer component so the table will only be rendered once there is data available:
renderTable() {
let { restaurantList } = this.props.restaurants
if (restaurantList) {
return <TableContainer data={ restaurantList } />
} else {
return <div></div>
}
}
render() {
return (
<div>
{ this.renderTable() }
</div>
)
}

How should map these data coming from Axios Http request (w/ React/Redux)

I've made an async call using redux-thunk and Axios. Everything is working fine however I have hard time building my component. I don't know how to map my props/data. Here is how the data are stored in my store :
I want to access the second data Array. I'm trying this :
const mapStateToProps = (state) => {
return {
isFetching: state.ThunkData.isFetching,
data: state.ThunkData.data.data,
error: state.ThunkData.error,
};
};
with the following error : Cannot read property 'data' of null. I need to replace data: state.ThunkData.data.data by data: state.ThunkData.data to make my app work.
Also adding {console.log(this.props.data.data)} in my component return me this :
How can I pass the data I want into my props so I can do something like this :
<ul>
{ props.map((m, i) =>
<li key={i}>{m.authorname}</li>
)}
</ul>
If needed here is my detailed code :
My action creator :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(data => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
My component :
class asyncL extends React.Component {
constructor(props) {
super(props);
}
componentWillMount() {
this.props.fetchTest(this.props.thunkData)
}
render() {
if (this.props.isFetching) {
return <p>{console.log(this.props.isFetching)}</p>
}else if (this.props.error) {
return <div>ERROR {this.props.error}</div>
}else {
return <p>{console.log(this.props.data.data)}</p>
}
}
}
My reducer :
const initialState = {data:null,isFetching: false,error:null};
export const ThunkData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
Ok. I've the solution :
I update my action creator to this ( add the response item) :
export const fetchTest = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
return axios.get('http://localhost:3000/authors')
.then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching:false,
data: response.data
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
that is solving my multiple object level request.
and now updating my component is easy :
class authorL extends React.Component {
constructor(props) {
super(props);
}
render() {
var props = this.props.author;
return (
<div>
<ul>
{ props.map((m, i) =>
<li key={i} onClick={() => this.props.getFilteredName(m.name)}>
{m.name}
</li>
)}
</ul>
</div>
);
};
}

Categories