I don't know if I forgot anything but .map is not returning the expected components.
This is my code:
data(){
var user = firebase.auth().currentUser;
var uid = user.uid;
axios.get('apilink here'+user.uid).then(function(response){
var arr = Object.values(response.data);
var listItems = arr.map(function(item){
return <Text>{item.name}</Text>
})
})
}
and this what I'm rendering:
render() {
return (
<Container style={{backgroundColor:'#F7F7F7'}}>
<Content>
{this.data}
</Content>
</Container>
)
}
}
You are approaching this in the wrong way. Your data function is not returning anything first of all so it won't display anything in your render method. You can debug that by simply calling your function and you will see that the value returned is undefined.
You are loading data from a remote resource, according to docs a good practice is to handle this in componentDidMount:
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. Setting state in this method will
trigger a re-rendering.
So one way to approach this would be to move your data code into componentDidMount() and update the state of the component once the data has been retrieved. Something like this:
...
constructor(props) {
super(props);
this.state = { data: [] };
}
componentDidMount() {
var user = firebase.auth().currentUser;
var uid = user.uid;
axios.get('apilink here' + user.uid)
.then(function(response){
var arr = Object.values(response.data);
this.setState({ data: arr });
});
}
render() {
return (
<Container style={{backgroundColor:'#F7F7F7'}}>
<Content>
{
this.state.data
.map(function(item) {
return <Text>{item.name}</Text>
})
}
</Content>
</Container>
)
}
The problem is that you can't use your data() function directly in your render method because it's async.
You will need to work with a component state, which will be set after your API response.
Lets say this is your component:
export default class App extends React.Component {
constructor(props){
super(props)
this.state = {
data: null
}
this.getData()
}
getData = () => {
var user = firebase.auth().currentUser;
var uid = user.uid;
axios.get('apilink here'+user.uid).then(function(response){
var arr = Object.values(response.data);
var listItems = arr.map(function(item){
return <Text>{item.name}</Text>
})
this.setState({data: listItems})
})
}
render() {
return (
<Container style={{backgroundColor:'#F7F7F7'}}>
<Content>
{this.state.data}
</Content>
</Container>
);
}
}
This will let you load the state async response to your component
Related
I am learning react.
I have a simple react app sample that :
Fetch users
Once users are fetched, show their name on a Card
What I'd like to do is to expand this sample. Instead of using a simple list of users, I'd like to use a list of pokemons. What I try to do is :
Fetch the list of pokemon and add in state.pokemons
Show the Card with the pokemon name from state.pokemons
From that list, get the URL to fetch the detail of the given pokemon and add in state.pokemonsDetails
From the state.pokemonsDetails, update the Cards list to show the image of the pokemon.
My problem is: I don't even know how to re-render the Cards list after a second fetch.
My question is: How to update the Cards list after the second fetch?
See my code below:
import React from "react";
import CardList from "../components/CardList";
import SearchBox from "../components/SearchBox"
import Scroll from "../components/Scroll"
import './App.css';
class App extends React.Component{
constructor(){
super();
this.state = {
pokemons:[],
pokemonsDetails:[],
searchfield: ''
}
}
getPokemons = async function(){
const response = await fetch('https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20');
const data = await response.json();
this.setState({pokemons:data.results})
}
getPokemonDetails = async function(url){
//fetch function returns a Promise
const response = await fetch(url);
const data = await response.json();
//console.log('getPokemonDetails', data);
this.setState({pokemonsDetails:data});
}
componentDidMount(){
this.getPokemons();
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value})
}
render(){
const {pokemons, pokemonsDetails, searchfield} = this.state;
if(pokemons.length === 0){
console.log('Loading...');
return <h1>Loading....</h1>
}else if (pokemonsDetails.length === 0){
console.log('Loading details...');
pokemons.map(pokemon => {
return this.getPokemonDetails(pokemon.url);
});
return <h1>Loading details....</h1>
}else{
return(
<div>
<h1>Pokedex</h1>
<SearchBox searchChange={this.onSearchChange}/>
<Scroll>
<CardList pokemons={pokemons}/>
</Scroll>
</div>
);
}
}
}
export default App;
Some remarks :
I can see a problem where my Cards list is first created with state.pokemons, then, I would need to update Cards list with state.pokemonsDetails. The array is not the same.
Second problem, I don't even know how to call the render function after state.pokemonsDetails is filled with the fetch. I set the state, but it looks like render is not called every time
More a question than a remark. The way I update my state in getPokemonDetails might be incorrect. I keep only one detail for one given pokemon. How to keep a list of details? Should I use something else than setState to expand pokemonsDetails array?
You can combine 2 API calls before pokemons state update that would help you to control UI re-renderings better
You can try the below approach with some comments
Side note that I removed pokemonDetails state, so you won't see the loading elements for pokemonDetails as well
import React from "react";
import CardList from "../components/CardList";
import SearchBox from "../components/SearchBox";
import Scroll from "../components/Scroll";
import "./App.css";
class App extends React.Component {
constructor() {
super();
this.state = {
pokemons: [],
searchfield: ""
};
}
getPokemons = async function () {
const response = await fetch(
"https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20"
);
const data = await response.json();
//try to get all pokemon details at once with fetched URLs
const pokemonDetails = await Promise.all(
data.results.map((result) => this.getPokemonDetails(result.url))
);
//map the first and second API response data by names
const mappedPokemons = pokemonDetails.map((pokemon) => {
const pokemonDetail = pokemonDetails.find(
(details) => details.name === pokemon.name
);
return { ...pokemon, ...pokemonDetail };
});
//use mapped pokemons for UI display
this.setState({ pokemons: mappedPokemons });
};
getPokemonDetails = async function (url) {
return fetch(url).then((response) => response.json());
};
componentDidMount() {
this.getPokemons();
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
};
render() {
const { pokemons, searchfield } = this.state;
if (pokemons.length === 0) {
return <h1>Loading....</h1>;
} else {
return (
<div>
<h1>Pokedex</h1>
<SearchBox searchChange={this.onSearchChange} />
<Scroll>
<CardList pokemons={pokemons} />
</Scroll>
</div>
);
}
}
}
export default App;
Sandbox
If you want to update pokemon details gradually, you can try the below approach
import React from "react";
import CardList from "../components/CardList";
import SearchBox from "../components/SearchBox";
import Scroll from "../components/Scroll";
import "./App.css";
class App extends React.Component {
constructor() {
super();
this.state = {
pokemons: [],
searchfield: ""
};
}
getPokemons = async function () {
const response = await fetch(
"https://pokeapi.co/api/v2/pokemon/?offset=0&limit=20"
);
const data = await response.json();
this.setState({ pokemons: data.results });
for (const { url } of data.results) {
this.getPokemonDetails(url).then((pokemonDetails) => {
this.setState((prevState) => ({
pokemons: prevState.pokemons.map((pokemon) =>
pokemon.name === pokemonDetails.name
? { ...pokemon, ...pokemonDetails }
: pokemon
)
}));
});
}
};
getPokemonDetails = async function (url) {
return fetch(url).then((response) => response.json());
};
componentDidMount() {
this.getPokemons();
}
onSearchChange = (event) => {
this.setState({ searchfield: event.target.value });
};
render() {
const { pokemons, searchfield } = this.state;
if (pokemons.length === 0) {
return <h1>Loading....</h1>;
} else {
return (
<div>
<h1>Pokedex</h1>
<SearchBox searchChange={this.onSearchChange} />
<Scroll>
<CardList pokemons={pokemons} />
</Scroll>
</div>
);
}
}
}
export default App;
Sandbox
Side note that this approach may cause the performance issue because it will keep hitting API for fetching pokemon details multiple times and updating on the same state for UI re-rendering
I am working on a web dev using ReactJs, NodeJs and Mysql. I got problem in displaying fetching data using axios.
here is my API :
app.get('/enduser',(req, res) => {
let ENDUSER_QUERY = "SELECT * FROM enduser_tbl";
let query = dbConn.query(ENDUSER_QUERY, (err, results) => {
if(err) {
console.log(err)
} else {
return res.status(200).json({"status": 200, "err" : null, "response": results});
}
});
});
and I call the API in my reactjs page component
class ProjectList extends Component {
constructor(props){
super(props);
this.state = {
enduser_tbl : []
}
}
async componentDidMount() {
const Url = "http://localhost:4000/enduser"
await axios.get(Url)
.then( enduser_tbl => {
console.log(enduser_tbl.data)
this.setState({
enduser_tbl : enduser_tbl.data
})
})
}
render() {
const enduser_tbl = this.state;
return (
<Container>
{ enduser_tbl.map ((enduser, project_id) =>
<ListGroup>
<ListGroup.Item key={project_id}> {enduser.project_type} </ListGroup.Item>
</ListGroup>
)}
</Container>
)
}
}
export default ProjectList
I got no error in my terminal but many problem appears in Chrome. here is the response from chrome
Error in destructuring, missed curly brace const { enduser_tbl }
render() {
const { enduser_tbl = [] } = this.state;
return (
<Container>
{ enduser_tbl.map ((enduser, project_id) =>
<ListGroup>
<ListGroup.Item key={project_id}> {enduser.project_type} </ListGroup.Item>
</ListGroup>
)}
</Container>
)
}
For safe side:
const Url = "http://localhost:4000/enduser"
await axios.get(Url)
.then( { data: enduser_tbl = [] } => {
console.log(data)
this.setState({
enduser_tbl
})
})
enduser_tbl.data will be undefined,
it will be either enduser_tbl.response or enduser_tbl which you are setting in your state.
this.setState({
enduser_tbl : enduser_tbl.response
})
Two things, you are not getting the correct piece of the state in your render function. You also need to deal with that both the API call and setState are async, so you can't rely on it being defined when your component is rendered.
You can do it like this:
const enduser_tbl = this.state.enduser_tbl || [];
return (
<Container>
{ enduser_tbl.map ((enduser, project_id) =>
<ListGroup>
<ListGroup.Item key={project_id}> {enduser.project_type} </ListGroup.Item>
</ListGroup>
)}
</Container>
)
I think that you are trying to map an object because this.state is an object. Try changing the code as given below.
class ProjectList extends Component {
constructor(props){
super(props);
this.state = {
enduser_tbl : []
}
}
async componentDidMount() {
const Url = "http://localhost:4000/enduser"
await axios.get(Url)
.then( enduser_tbl => {
console.log(enduser_tbl.data)
this.setState({
enduser_tbl : enduser_tbl.data
})
})
}
render() {
const enduser_tbl = this.state.enduser_tbl;
return (
<Container>
{ enduser_tbl.map ((enduser, project_id) =>
<ListGroup>
<ListGroup.Item key={project_id}> {enduser.project_type} </ListGroup.Item>
</ListGroup>
)}
</Container>
)
}
}
This error rises because you are not passing array to map function please add below code and let me know is it work for you are not
componentDidMount = async (e) =>{
const url = "http://localhost:4000/enduser"
try{
const res = await axios.get(url)
this.setState({
enduser_tbl :res.data
})
}
ctach(ex){
console.log(ex)
}
}
First, check your API response it might look like this
{
"status": 200,
"response": [{...}, {...}]
}
then when receiving your data make sure you've set it to the state properly
e.g:
await axios.get(Url)
.then(enduser_tbl => {
this.setState({
enduser_tbl: enduser_tbl.response
})
})
finally, make sure that you've destructured it properly just like xdeepkav said e.g
const { enduser_tbl } = this.state;
The error the you're encountering is because enduser_tbl can't be read as mappable data/array. To make it clear here's an example of your error
I keep trying to call an image location using stored data on firebase it seems to work fine on my other component, however, I keep getting this error on this component when switching to the page on my app, even the console logging of the data works fine. Any help would be great thanks!
export class Dog extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount = async () => {
await firebase
.database()
.ref("pets")
.on("value", snapshot => {
this.setState({ data: [].concat.apply([], snapshot.val()) });
});
};
render() {
return (
<View>
{this.state.data.map((obj, index) => {
console.log(obj.image);
return (
<View key={index}>
<Image source={require(obj.image)} />
</View>
);
})}
</View>
);
}
}
export default Dog;
i wanted to create multiple request from hoc,where i was able to create hoc for single request(redux action which call api),please check the code for single request
i have created hoc for reducing repeated code in every component,like on componentdidmount calling api,managing error,managing loading state but it is only for single request like you can see in intial object in given hoc,so i want a to create hoc which can executes for multiple request(redux action which calls api),i dont know that this solution which is working for single request is properly implemented or not
So,please help me to create hoc which can be resealable for any given scenario
hoc
export const ComponentWithAPIRequest = ({ onMount = null, LoaderRequestID,onUnmount = null }) => (WrappedComponent) => {
return class ComponentWithAPIRequest extends Component {
state = {
stateLoader: true // intial Load
};
componentDidMount = () => {
this.Request();
};
componentWillUnmount() {
onUnmount !== null ? this.props[onUnmount]() : null;
}
Request = () => {
onMount !== null ? this.props[onMount](LoaderRequestID) : null; // API request
this.setState({ stateLoader: false });
};
render() {
const { error, isLoading } = this.props; // pass it here somehow.
const { stateLoader } = this.state;
const isLoadingFromAPI = this.props.isLoadingRequestIds.indexOf(LoaderRequestID) !== -1 ? true : false;
if (stateLoader) {
return (
<div className="text-center">
<CircularProgress />
</div>
);
}
if (isLoadingFromAPI) {
return (
<div className="text-center">
<CircularProgress />
</div>
);
} else {
return <WrappedComponent {...this.props} retry={this.Request} />;
}
}
};
};
component
export const isContainer = ({ intial, list }) => (WrappedComponent) => {
const IsContainer = (props) => <WrappedComponent {...props} />;
return compose(ComponentWithAPIRequest(intial), hasRequestError)
(IsContainer);
};
hasRequestError // is error hoc
ComponentWithAPIRequest // is above added hoc
#isContainer({
intial: {
onMount: 'FirstRequest', // is a redux action which call api,here i want to have multiple request like ['FirstRequest','SecondRequest']
LoaderRequestID: 'FirstRequestLoader', // is an unique id for loader,which then for multiple request be converted to respective request like ['FirstRequestLoader','SecondRequestLoader']
onUnmount: 'ResetLeaderBoardAll' // is a redux action when component unmount
}
})
class ComponentView extends Component {
render() {
return (
<SomeComponent {...this.props}/>
);
}
}
const mapStateToProps = (state) => {
return {
somestate:state
};
};
export default connect(mapStateToProps, {
FirstRequest,
SecondRequest
})(ComponentView);
If I were you, I would update your hoc and pass multiple URLs as parameter to send multiple requests like:
export default connect(mapStateToProps, mapDispatchToProps)(ComponentWithAPIRequest(ComponentView,
[
"url1", "url2" ...
]));
And pass them back to ComponentView with 'props' or 'props.requestResponses'(single) object. Then I would use them like
const url1response = this.props.requestResponses["url1"];
In my case, I would store them in ComponentWithAPIRequest's state but you can use redux and get them from mapStateToProps in ComponentView as well.
I have a problem with React.
When I press the "+" button, this console message appears and nothing happens:
Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`
I found several questions with similar titles, but common thing among them is that there were calls of functions with setState inside render method.
My render method has no calls, but error appears.
Why?
Thank you for reading.
Code:
import React from 'react';
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input
ref={node => {
input = node;
}}
/>
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
const TodoList = ({todos, remove}) => {
// Map through the todos
const todoNode = todos.map((todo) => {
return (<Todo todo={todo} key={todo.id} remove={remove}/>)
});
return (<ul>{todoNode}</ul>);
};
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
};
window.id = 0;
class TodoApp extends React.Component {
constructor(props) {
// Pass props to parent class
super(props);
// Set initial state
this.state = {
data: []
}
}
// Add todo handler
addTodo(val) {
// Assemble data
const todo = {text: val, id: window.id++}
// Update data
this.state.data.push(todo);
// Update state
console.log('setting state...');
this.setState({data: this.state.data});
}
// Handle remove
handleRemove(id) {
// Filter all todos except the one to be removed
const remainder = this.state.data.filter((todo) => {
if (todo.id !== id) return todo;
});
// Update state with filter
this.setState({data: remainder});
}
render() {
// Render JSX
return (
<div>
<Title />
<TodoForm addTodo={
(val)=>{
this.addTodo(val)
}
}/>
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
</div>
);
}
}
export default TodoApp;
In your render method for Todo you invoke remove, which is where your erroneous state update happens.
To fix this, return a function from the handleRemove method of TodoApp that updates the state. Simplified version:
handleRemove(id) {
return () => {
...
this.setState({ data: remainder });
}
}
Also worth noting here that because you're using the current state, it's best to use the setState callback (which gets prevState as an argument), and not rely on this.state.
setState docs
Andy_D very helped and my answer has two solutions:
First in render function change
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
to
<TodoList
todos={this.state.data}
remove={() => this.handleRemove.bind(this)}
/>
or change code
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={remove(todo.id)}>{todo.text}</li>)
};
to that:
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={() => remove(todo.id)}>{todo.text}</li>)
};