My React/Redux API wont log out properly shows [[PromiseValue]] - javascript

Quite new to coding but hitting hurdles with a React/Redux API.
I'm following Bucky Roberts' YouTube tutorial & ive replaced his static names object with an API that i then pull into my reducer.
This logs out the data fine, as an array - but when i try to pull it in to my Container, i get either a map is not a function error (even though it is an array) or if i log out console.log('props: ', this.props) i get [[PromiseValue]] with the array[10] showing.
But i'm pretty sure drilling into that in my component would be a no go.
CONTAINER:
class UserList extends Component {
createListItems() {
console.log('props: ', this.props);
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.name}
</li>
)
})
}
render() {
return (
<div>
<ul>
{this.createListItems()}
</ul>
</div>
)
}
}
function mapStateToProps(state) {
return {
users: state.users
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({selectUser: selectUser}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(UserList);
API:
const MyData = fetch('https://jsonplaceholder.typicode.com/users')
.then(function(data1) {
return data1.json()
}).then(function(data2) {
return data2
}).then(function(data3) {
console.log('API RESPONSE: ', data3)
return data3
})
export default MyData;
MyData is then pulled into a reducer, then into combineReducers({}) and the data should show back in mapStateToProps as above in the container. What Am I Doing Wrong? I feel like it's something to do with resolving Promises but my api actually logs everything out correctly. So is it something to do with having API data passed in higher up, in createStore or?:
import MyData from './MyData'
export default function() {
return MyData
}

The proper way to deal with API requests is to put them inside actions and use some middleware to handle asynchronous functions. I'm not sure what is going on with your API function, but anyway consider this solution:
In componentWillMount call an action which fetches users, example with redux-thunk middleware:
export function fetchUsers() {
return async (dispatch) => {
const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
dispatch({
type: FETCH_USERS,
payload: data
});
}
}
Then you won't have a promise in reducer, but json object and you will be able to map the users, but remember that there is a split second when you don't have users to map, so you have to handle this condition to avoid error:
createListItems() {
if(this.props.users.length > 0) {
return this.props.users.map((user) => {
return (
<li
key={user.id}
onClick={() => this.props.selectUser(user)}
>
{user.name}
</li>
)
})
} else {
return <div>Loading...</div>;
}
}

Related

Catch error films.map is not a function in React

I get the catch error or type error on
{films.map((film, i) => { ...
can not read proper 'map' or undefined from the Rest Api swapi in React.
build id url to show information
Create the component
State to add api data
Binding the function fetchData to this state
Call to api function characters
Call to api function movies
Life circle to call the function
Rendering the component
Bring the data of the character to the data to show them
mapping the list
const api_url_films = 'https://swapi.dev/api/films/'
class Card extends Component {
constructor(props) {
super(props)
this.state = {
films: []
}
this.fetchDataFilms = this.fetchDataFilms.bind(this)
}
fetchDataFilms(){
fetch(api_url_films)
.then(data => (data.json()))
.then(results => {
const api_data = results
this.setState({
films: api_data
})
})
}
componentDidMount() {
this.fetchDataFilms()
}
render() {
const {films} = this.state
return(
<div>
{films.map((film, i) => {
return (
<li key={i}>{film}</li>
)
}
)}
</div>
)
}
}
I try to add in a unordered list also but doesn't work
thanks
I think html is rendered before data is fetched although the films variable is an empty array in initial state.
When films variable is set from fetching function, html will be re-rendered.
try this code. This component will show an empty box until film data is fetched.
hope this works!
render() {
let films = this.state.films
if(films == null || films == []) return <></>
return(
<div>
{films.map((film, i) => {
return (
<li key={i}>{film}</li>
)
}
)}
</div>
)
}
The response has this shape:
{
count: number,
next: any,
previous: any,
results: Object[], // <-- this is what you want
}
Update the handler to access into the response object and save the results array.
fetchDataFilms(){
fetch(api_url_films)
.then(data => (data.json()))
.then(results => {
const api_data = results
this.setState({
films: api_data.results, // <-- access the results array
})
})
}
Additionally, the film objects you are mapping are still object, so they are not valid React children. You will need to access specific properties.
{films.map((film) => {
return (
<li key={film.title}>{film.title}</li> // or director, or producer, etc...
)
}

Use static fetch service

I have created an express mongoose api. I want to use that api from my React-application.
I want to create a service that would manage those api requests. But I am new in react-native and I can't use that service. I tried creating a static class but I cannot make it works. Here is an example :
// apiService.js
class ApiService {
static fetchUsers = () => {
return fetch('XXX/users')
.then((response) => {
return response.json()
.then((data) => {
return data;
})
})
.catch((error) => {
console.error(error);
});
}
}
export default ApiService;
And my screen
// UserScreen.js
import ApiService from '../services/apiService';
export default class UserScreen extends Component {
constructor(props) {
super(props);
this.state = {
users: [],
isLoading: true,
}
}
componentDidMount = () => {
let users = ApiService.fetchUsers();
this.setState({data: users});
this.setState({isLoading: false});
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator/>
</View>
)
} else {
return (
<View style={{ flex: 1, marginTop: 100 }}>
{
this.state.data.map((val, key) => {
return <TouchableOpacity
style={styles.homeButton}
key={key}
onPress={() => this.redirectHandler(val)}>
</TouchableOpacity>
})
}
</View>
)
}
}
}
I tried using async and wait but I can't find a way to retrieve data. The data are well retrieve in the apiService but I can't share them with the UserScreen.
How can I use a (static or not) class/function in react-native and get the data from the screen
Update
Here is what I tried with async
class ApiService {
static fetchUsers = async () => {
try {
let response = await fetch('XXXXX/users/');
let json = await response.json();
return json;
} catch (error) {
console.error(error);
}
}
}
export default ApiService;
And in my Userscreen
componentDidMount = async () => {
try {
let users = await ApiService.fetchUsers();
this.setState({isLoading: false});
this.setState({data: users});
} catch (error) {
console.log(error);
}
}
The problem lies in the setState that you are performing twice. If you look at the logic of the component, first we check for isLoading, if true we show some message/spinner otherwise we are showing a list of users/data.
Sequence of the Set State:
this.setState({isLoading: false});
this.setState({data: users});
Note that each setState triggers a re-render of the component, so in this case first we set isLoading to false (1st Re-Render) and then we set the data (2nd Re-Render)
The problem is, when 1st Re-Render is done, isLoading is set to false and the condition which we talked about above, now enters the "showing the user/data list" part. Another thing to note here is we have defined users: [] in state and when we are setting the users array (from the api call), we set that in a variable called data. The variable data is never defined in state so essentially it is "undefined".
Issue in your code:
this.state.data.map(...)
You cannot map over an undefined variable and this is where the problem lies. Doing so will throw an error saying "cannot read property map of undefined".
To fix this:
When setting the users list, instead of doing this.setState({ data: users }) just do this.setState({ users: users }) and change this.state.data.map( to users.map(
Also, unnecessary re-renders are costly and in case of React Native, they are costlier. Merge your setState(...) calls when possible. For example,
this.setState({
isLoading: false,
users: users
})

NextJS getServerSideProps pass data to Page Class

i know this has probably been already asked, but i'm at a point where i don't know what to do.
I'm not a (very) experienced developer in javascript or NextJS.
My Problem(1):
I got the method: export const getServerSideProps: GetServerSideProps = async () => {} implemented to fetch some data from a integrated API (pages/api from NextJS). The code itself is probably not well(or worse) written, but it works. (for now at least)
export const getServerSideProps: GetServerSideProps = async () => {
try {
// get userID
await fetch("http://localhost:32147/api/v1/user/get?requestedField=userID&fieldName=username&fieldValue=<value removed>").then(
(userIDResponse: Response): any => {
// get userID as json
userIDResponse.json().then((userIDResult: Response): any => {
// get messages
fetch(
"http://localhost:32147/api/v1/message/get?requestedField=*&fieldName=userID&fieldValue=" +
JSON.stringify(userIDResult[0].userID)
).then((messageResponse: Response): any => {
// get messages as json
messageResponse.json().then((messageResult) => {
return {
props: { messages: messageResult },
{/* marker1 */}
}
})
})
})
}
)
} catch (error) {
console.log(error)
}
}
just to be clear, this method works, data fetching works but just if i access it at marker1
that one part where i return the props:
return {
props: { messages: messageResult },
}
i can't do that 'cause nextjs is gonna break because of getServerSideProps() didn't return anything.
I tried to store the final data into a variable, that i declared on the first line of this method, but it ended up being empty the whole time.
How can i solve this?
My Problem(2): if i set a manual value at the end of this method for testing, it doesn't get passed to the main Page Class (index.tsx)
i can just access it using this.props.<prop name>, in this case: this.props.messages, right?
The whole index.tsx:
import React, { Component } from "react"
import { GetServerSideProps } from "next"
import Router from "next/router"
import Head from "next/head"
import Navbar from "../lib/Navbar"
import MessagesModal from "../lib/MessagesModal"
export const getServerSideProps: GetServerSideProps = async () => {
try {
// get userID
await fetch("http://localhost:32147/api/v1/user/get?requestedField=userID&fieldName=username&fieldValue=<value removed>").then(
(userIDResponse: Response): any => {
// get userID as json
userIDResponse.json().then((userIDResult: Response): any => {
// get messages
fetch(
"http://localhost:32147/api/v1/message/get?requestedField=*&fieldName=userID&fieldValue=" +
JSON.stringify(userIDResult[0].userID)
).then((messageResponse: Response): any => {
// get messages as json
messageResponse.json().then((messageResult) => {
return {
props: { messages: messageResult },
}
})
})
})
}
)
} catch (error) {
console.log(error)
}
}
interface HomeProps {
messages?: []
}
export default class Home extends Component<HomeProps> {
constructor(props) {
super(props)
}
state = {
messagesModal: false,
messages: [],
}
// triggers logout
triggerLogOut(): void {}
render(): JSX.Element {
return (
<>
<Head>
<title>OneDrive Event Connector</title>
</Head>
<Navbar
ItemClickCallback={(callbackItem: string): void => {
if (callbackItem === "messages") {
this.setState({ messageModal: !this.state.messageModal })
} else if (callbackItem === "log_out") {
this.triggerLogOut()
} else {
Router.push("/" + callbackItem)
}
}}
/>
<div className="app-content"></div>
<MessagesModal
messages={this.props.messages}
isOpen={this.state.messagesModal}
toggleModal={() => {
this.setState({ messageModal: !this.state.messagesModal })
}}
/>
</>
)
}
}
This is just a "fun" project for me to practise and learn.
Would be greate if anyone could give me even a hint on what is my problem/mistake here...
Thanks.
Kind regards
Oliver
i can't do that 'cause nextjs is gonna break because of getServerSideProps() didn't return anything.
exactly - in your code, you are returning values inside of a chain of promises - you need to make sure, that values are returned from each step
here's a working example - similar flow with swapped API - to help you understand how to return something, going back from the inside of your chained promises
export const getServerSideProps: GetServerSideProps = async () => {
try {
// initial fetch
const result = await fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((todosResponse: Response): any => {
return todosResponse.json().then((todo) => {
// fetch something more
return fetch(
"https://jsonplaceholder.typicode.com/users/" + todo.userId
).then((userResponse: Response): any => userResponse.json());
})
})
return {
props: { messages: result },
}
} catch (error) {
console.log(error)
}
}
My advise is also to read more on promises / async await in JS world
My Problem(2)
i can just access it using this.props., in this case: this.props.messages, right?
yes, that's right
interface HomeProps {
messages?: []
}
export default class Home extends Component<HomeProps> {
constructor(props) {
super(props)
}
render() {
return (
<>
{JSON.stringify(this.props.messages)}
</>
)
}
}

Setting state in getDetails() occurs infinite loop

this.state = {
data: [],
details: []
}
componentDidMount() {
this.getDetails()
this.getCountries()
}
getCountries() {
Utils.rest('POST', 'https:///api-spot-get-all', {
country: '',
windProbability: ''
}).then(async (r) => {
const data = await r.json();
this.setState({
data: data.result
})
}).catch(err => {
console.log(err.message);
});
}
`getDetails() {
return new Promise((resolve, reject) => {
let details_list = [];
this.state.data.map(item => {
return (
Utils.rest('POST', 'https:///api-spot-get-details', {
spotId: item.id
})
.then(async (r) => {
const details_item = await r.json()
console.log(`Loaded ${details_list.length} item ...(of ${this.state.data.length})`);
if (details_list.length === this.state.data.length) {
await resolve(details_list)
}
details_list.push(details_item.result);
})
);
})
})
}`
render() {
return (
{
this.state.data.map((item, key) => {
return (
{item.id}
{item.id}
);
})
}
Here is my code. After first call I am receiving id and passing it as input to second call
I think this is happening because you're calling this.getCountries() in the render function. So the function is called in every render, that causes a new request that sets a new state, which will trigger a new render an so on, creating an infinite loop. So, if you delete the function calling from the render function it should work.
This is a basic example:
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const myarray=[1,2,3,4,5]
const mytable=myarray.map((item,key)=>{return(
<table key={key}>
<tr><td>id</td><td>{item}</td></tr>
</table>)
})
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{mytable}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can create a const in the render and use after in return so you can tryin your code to do something like that:
render() {
const myComponent= this.state.data.map((item, key) => { return (
<div key={key}>
<span>{item.it}</span>
</div>
) });
return (
{myComponent}
)
}
I used and is just for example you can use what structor you want as in the first example I used table...
Please note that you are calling getDetails method in render. render is not a good place to add methods which modify the internal state. please check the react doc for additional details.
There are a lot of strange things there. First of all, getDetails returns a Promise, but the promise is not resolved anywhere. Its usage should be something like:
getDetails()
.then(data => {
// do something with the data
}, error => {
// manage here the error
}):
Also, this.state.data.map should be this.state.data.forEach and delete the return from inside, because you don't need to return anything outside
On the other hand, there's an issue with getCountries. Its name sais it's a GET, but the API call sends a POST.
After that's clarified, inside getDetails you're using the data retrieved in getCountries, so its call should be inside the request resolving inside getCountries or either change getCountries to a Promise and do something like:
this.getCountries()
.then(data => {
this.getDetails();
});
You don't care when the getDetails call ends, so it doesn't need to return a Promise.
And, in the end, the render function should look more like this:
render() {
return (
<div>
{this.state.data.map((item, key) =>
<div key={key}>{item.id} - {item.id}</div>
)}
</div>
)
}
After this it should work properly, more or less. I have to warn you, though. Probably you would need to do something easier to become familiar with React's flow and how to properly work with state and JS's asynchronous functions.

React/Redux - Why this Promise.all continues to execute after dispatch?

I have a React-Native project with redux.
I perform some axios calls, and basically, the app works fine.
But, there is one thing that is not right.
I put a console.log before the dispatch, and even the app loads and renders everything just fine, I see the console.log looping on and on in the console.
I'm not sure why this is happening, but I read the "run to completion" concept in Javascript, which can be the reason. Even though, I couldn't figure it out.
any ideas how can I fix this? thank you very much.
UPDATE: here is the component that invokes the action, in the renderEtiquetas() function. This can be the reason causing this loop, since it runs on every re-render cycle (not sure about this). I tried moving the invoke to componentDidMount() but it didn't seem to run.
I'm new to React so I'm probably doing something dumb.
component.js
class EtiquetasList extends Component {
componentDidMount() {
this.props.FetchEtiquetas();
}
renderEtiquetas() {
if ( this.props.etiquetas.length == 0 ) {
return <ActivityIndicator size="large" color="#00ff00" />
} else {
this.props.FetchGalleries( this.props.etiquetas );
if ( this.props.galleries.length > 0 ) {
return this.props.etiquetas.map(etiqueta =>
<EtiquetaDetail key={etiqueta.id} etiqueta={etiqueta} galleries={ this.props.galleries } />
);
}
}
}
render() {
return (
<ScrollView>
{ this.renderEtiquetas() }
</ScrollView>
);
}
}
const mapStateToProps = (state) => {
return {
etiquetas: state.data.etiquetas,
isMounted: state.data.isMounted,
galleries: state.slides.entries
};
};
export default connect(mapStateToProps, { FetchEtiquetas, FetchGalleries })(EtiquetasList);
actions.js
export function FetchGalleries( etiquetas ) {
return function (dispatch) {
return Promise.all(
etiquetas.map( record =>
axios.get('mydomain.com/?id='+record.id)
)).then(galleries => {
let my_data = [];
let data_json = '';
galleries.map( record => {
record.data.map( subrecord => {
// this is simplified for this example, it works as intended
data_json = data_json + '{ title: "' + subrecord.title+'"}';
});
my_data.push( data_json );
});
console.log( my_data ); // this keeps printing in the console
return dispatch({ type: FETCH_GALLERIES_SUCCESS, payload: my_data });
});
}
}
Aha, FetchGalleries is running inside the render function, it will cause an action->render->action->render infinite loop.
Edit:
How about trying to merge your FetchGalleries and FetchEtiquetas into one action:
export const fetchGalleries = () => {
return function (dispatch) {
return axios.get('/path/to/etiquetas').then(etiquetas => {
dispatch({ type: FETCH_ETIQUETAS_SUCCESS, payload: etiquetas });
// ... your previous promise all code
});
}
}
and only need to call this new fetchGalleries at componentDidMount.
You are close, you just need to return or await Promise.all otherwise it will not be awaited
export function FetchGalleries( etiquetas ) {
return function (dispatch) {
return Promise.all(....
}
// UPDATE: The answer from 李骏骁 is correct

Categories

Resources