I have been able to pull data from an API that I built using MongoDB and Express, but am having trouble rendering the nested data to my React component.
For example, if I type in <p>{restaurant.cuisine}</p> I am able to retrieve Burgers, American, but if I try and access {restaurant.status.delivery}, I get an error that says:
Cannot read property 'delivery' of undefined.
But if I {console.log(restaurant.status} I can see the object? I tried turning the object into an array using Object.values, but that didn't work either.
The same thing happens if I try to access the nested objects in {restaurant.images} and {restaurant.geometry}.
Here's a copy of my React hook:
import { useReducer, useEffect } from 'react';
import axios from 'axios';
const ACTIONS = {
MAKE_REQUEST: 'make-request',
GET_DATA: 'get-data',
ERROR: 'error',
};
function reducer(state, action) {
switch (action.type) {
case ACTIONS.MAKE_REQUEST:
return { loading: true, restaurant: [] };
case ACTIONS.GET_DATA:
return {
...state,
loading: false,
restaurant: action.payload.restaurant,
};
case ACTIONS.ERROR:
return {
...state,
loading: false,
error: action.payload.error,
restaurant: [],
};
default:
return state;
}
}
export default function useFetchSingleRestaurant({ id }) {
const [state, dispatch] = useReducer(reducer, {
restaurant: [],
loading: true,
});
useEffect(() => {
dispatch({ type: ACTIONS.MAKE_REQUEST });
axios
.get('http://localhost:4444/restaurants/' + id)
.then((res) => {
dispatch({
type: ACTIONS.GET_DATA,
payload: { restaurant: res.data.restaurant },
});
})
.catch((e) => {
dispatch({
type: ACTIONS.ERROR,
payload: { error: e },
});
});
}, [id]);
return state;
}
I'm accessing it in my SingleRestaurant component:
function SingleRestaurant({ match }) {
const { restaurant } = useFetchSingleRestaurant({ id: match.params.id });
return (
<p>{restaurant.status.delivery}</p>
)
}
And then here's my backend setup as well:
showRestaurant = async (req, res) => {
const restaurant = await Restaurant.findById(req.params.id)
.populate({ path: 'reviews', populate: { path: 'author' } })
.populate('author');
if (!restaurant) {
req.flash('error', 'Restaurant not found.');
return res.redirect('/restaurants');
}
res.send({ restaurant });
};
Until your server request returns restaurant it will be set as the default [] that you have set.
An empty array does not have a property of status, so hence the error.
if you change your default to null:
const [state, dispatch] = useReducer(reducer, {
restaurant: null,
loading: true,
});
And then check for a value:
function SingleRestaurant({ match }) {
const { restaurant } = useFetchSingleRestaurant({ id: match.params.id });
if (!restaurant) return 'Loading'
return (
<p>{restaurant.status.delivery}</p>
)
}
You could also pass back the loading state from your hook and then do a check on that.
Related
The code trying to fetch data:
class Search extends Component {
state = {
bookList: [],
bookk: "",
search: [],
searchResults: [],
isLoading: true,
isError: false,
searchQuery: "",
}
/**
* React lifecycle method to fetch the data
*/
async componentDidMount() {
Axios.get('http://localhost:8000/hentObjekterJSON/Studium')
.then(result => {
const bookData = result.data
bookk = result.data
this.setState({ bookList: bookData.Studium})
this.rebuildIndex()
})
.catch(err => {
this.setState({ isError: true })
console.log("====================================")
console.log(`Something bad happened while fetching the data\n${err}`)
console.log("====================================")
})
}
The JSON-Response:
{"Studium": {"studiumkode": "ABIOK", "studium": "ABIOK (anestesi/barnevern/intensiv/operasjon/kreftsykepleie"}}
Neither bookList nor bookk seems to have data assigned at all.
I have checked and the URL is serving the JSON (as a JsonResponse))
Why won't Axios find and use the data?
Some suggested pointers...
class Search extends Component {
state = {
bookList: [],
bookk: "",
search: [],
searchResults: [],
isLoading: true,
isError: false,
searchQuery: "",
}
/**
* React lifecycle method to fetch the data
*/
// Not sure you need async on the below line
async componentDidMount() {
Axios.get('http://localhost:8000/hentObjekterJSON/Studium')
.then(result => {
const bookData = result.data
console.log(bookData) // Actually see what is returned.
// You may need to parse the data....
const bookData = JSON.parse(result.data)
bookk = result.data // This should a 'setState'.
this.setState({ bookList: bookData.Studium,
bookk: result.data})
// setState is async, so rebuild might run before setState has finished.
this.rebuildIndex()
// If you want something to run only once set state has completed, then try
this.setState({ bookList: bookData.Studium,
bookk: result.data}, this.rebuildIndex())
})
.catch(err => {
this.setState({ isError: true })
console.log("====================================")
console.log(`Something bad happened while fetching the data\n${err}`)
console.log("====================================")
})
}
I'm relatively new in this environment. I use "Ant design pro 4" with React and Typescript for a new project.
I call successful my IdentityServer 4 for a token for a login. I see my response in my Browser.
But how get the token in my code?
import { Reducer } from 'redux';
import { Effect } from 'dva';
export interface StateType {
status?: 'ok' | 'error';
jsonRes: string;
type?: string;
currentAuthority?: 'user' | 'guest' | 'admin';
}
export interface LoginModelType {
namespace: string;
state: StateType;
effects: {
login: Effect;
};
reducers: {
changeLoginStatus: Reducer<StateType>;
};
}
const Model: LoginModelType = {
namespace: 'login',
state: {
// status: undefined,
jsonRes: '',
},
effects: {
* login({ payload }, { call, put }) {
const response = yield call(accountLogin, payload);
yield put({
type: 'changeLoginStatus',
payload: response,
});
},
},
reducers: {
changeLoginStatus(state, { payload }) {
return {
...state,
jsonRes: payload.json, //not work
};
},
},
};
export default Model;
EDIT:
Maybe that's helpful.
export async function accountLogin(params: LoginParamsType) {
const sendData = `grant_type=password&username=${params.userName}&password=${params.password}& ........`;
const retValue = request('https://localhost:44308/connect/token', {
method: 'POST',
data: sendData,
mode: 'no-cors',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'string',
});
return retValue;
}
use payload.json().It will read the response stream to completion and parses the response as json.
I'm sure you had it figured it out by now, but here it goes anyways
reducers: {
changeLoginStatus(state, { payload }) {
return {
...state,
jsonRes: payload.access_token, //<--this should do it
};
},
when you call const response = yield call(accountLogin, payload); it gets you the output you see in debug window.
Hope that helps.
I'm having trouble returning an array to my React component from a fetch call from my Express server, that I set up in Redux.
I'm trying to just return the vitamins array from this json from Express:
router.get('/', function(req, res, next) {
vitamins: [
{
name: "Vitamin B2"
}
],
minerals: [
{
name: "Zinc"
}
]});
});
This is the fetch call and FETCH_VITAMINS_SUCCESS action in my actions.js.
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(res => res.json())
.then(micros => {
dispatch(fetchVitaminsSuccess(micros.vitamins));
return micros.vitamins;
})
};
}
export const FETCH_VITAMINS_SUCCESS = 'FETCH_VITAMINS_SUCCESS';
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: { vitamins }
});
This is my reducers.js where i'm trying to set the state to "micros.vitamins".
const initialState = {
micros: [],
};
function vitaminReducer(state = initialState, action) {
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return {
...state.vitamins,
micros: action.payload
};
default:
return state;
}
}
This is my React component Vitamins.js where I'm importing fetchVitamins() and trying to pass the names of each vitamins to a menu dropdown in an option tag.
componentDidMount() {
this.props.fetchVitamins();
}
renderData() {
const { vitamins } = this.state.micros;
return vitamins.map((micro, index) => {
return (
<option value={micro.value} key={index}>{micro.name}</option>
)
})
}
render() {
return (
<select value={this.props.value}>
{this.renderData()}
</select>
)
}
const mapStateToProps = state => ({
micros: state.vitamins,
});
export default connect(mapStateToProps, { fetchVitamins })(Vitamins);
Right now I get back the error "TypeError: Cannot read property 'micros' of null", highlighting over my renderData() function.
It should be:
const { vitamins } = this.props.micros;
because you pass micros as props from redux. While you tried to access it from the state (which I guess was not initialized that's why it's null).
Another thing, you pass Object in payload:
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: { vitamins }
});
and set it as micros in your reducer:
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return {
...state.vitamins,
micros: action.payload
};
However, your micros are initially an array which may lead to unexpected errors. Maybe you should change your initial state to something resembling the response like:
const initialState = {
micros: {
vitamins: []
},
};
This way const { vitamins } = this.state.micros; will always return some array - before and after the response.
I'm trying to fetch data from my Express server in Redux, and mapping over the object to just use one array, called "vitamins". This is the json object.
router.get('/', function(req, res, next) {
vitamins: [
{
name: "Vitamin B2"
}
],
minerals: [
{
name: "Zinc"
}
]});
});
This is my action.js, where I'm creating the function fetchVitamins() to just fetch micros.vitamins.
export function fetchVitamins() {
return dispatch => {
return fetch("/users")
.then(res => res.json())
.then(micros => {
dispatch(fetchVitaminsSuccess(micros.vitamins));
return micros.vitamins;
})
};
}
export const FETCH_VITAMINS_SUCCESS = 'FETCH_VITAMINS_SUCCESS';
export const fetchVitaminsSuccess = vitamins => ({
type: FETCH_VITAMINS_SUCCESS,
payload: { vitamins }
});
This is my reducers.js
const initialState = {
micros: [],
};
function vitaminReducer(state = initialState, action) {
switch(action.type) {
case FETCH_VITAMINS_SUCCESS:
return {
...state,
micros: action.payload.vitamins
};
default:
return state;
}
}
This is my React component Vitamins.js where I'm importing fetchVitamins() and trying to pass the names of each vitamins to a menu dropdown in an option tag.
componentDidMount() {
this.props.dispatch(fetchVitamins());
}
renderData() {
const { vitamins } = this.state.micros;
return vitamins.map((micro, index) => {
return (
<option value={micro.value} key={index}>{micro.name}</option>
)
})
}
render() {
return (
<select value={this.props.value}>
{this.renderData()}
</select>
)
}
const mapStateToProps = state => ({
micros: state.micros.vitamins,
});
Right now when it renders, I get this error: "TypeError: Cannot read property 'vitamins' of undefined", highlighting over "micros: state.micros.vitamins,".
Am I calling and setting state correctly? If I set my initialState to micros: [], then setting the state to "state.micros.vitamins" should work, I thought.
because of you get the server data n vitamins objects so that data should be in vitamins:[], in that Format so that why state.macros.vitamins work.
I not undestand everything with javascript etc, I want to get my data returned by ma action redux but i'have a problem with my code.
const mapStateToProps = state => {
const group = state.groupReducer.group ? state.groupReducer.group : [ ]
return {
group
}
how i can get my data ?
When I try with that:
const mapStateToProps = state => {
const group = state.groupReducer.group.data.data[0] ? state.groupReducer.group.data.data[0] : [ ]
return {
group
}
And my goal is map around group
renderGroup = group => {
return group.map((groups => {
<div key={groups.data.data.id}>
//
</div>
}))
}
Sagas.js
export function* loadApiDataGroup() {
try {
// API
const response = yield
call(axios.get,'http://localhost:8000/api/group');
yield put(loadGroup(response))
} catch (e) {
console.log('REQUEST FAILED! Could not get group.')
console.log(e)
}
}
Action.js
export function loadGroup(data){ return { type: LOAD_GROUP, data }};
export function creatGroup(data){ return { type: CREATE_GROUP, data}};
// reducer
export default function groupReducer( state= {}, action = {}){
switch (action.type){
case LOAD_GROUP:
return {
...state,
group: action.data
}
case CREATE_GROUP:
return {
...state
}
default:
return state
}
thank you to help me
Try
const mapStateToProps = state => ({
group: state.groupReducer.group || []
});
Then you can use this.props.group in the component. Even though you might only want one thing in mapStateToProps, it's usually not directly returned like that.
If group is the response of an API request, you need to unpack data first, this is done in your async action creator (you will want to use redux-thunk or something similar):
const getGroup = () => async (dispatch) => {
dispatch({ type: 'GET_GROUP_REQUEST' });
try {
const { data } = await axios.get('/some/url');
dispatch({ type: 'GET_GROUP_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'GET_GROUP_FAILURE', payload: error });
}
};