I have a project where I used redux-saga to make an API call with axios and return the data to the store, which then I mapStateToProps using redux and now I want to map() it and show it on my DOM, but I'm getting "undefined".
Two things keep happening:
either the data doesn't get called in time and the render happens to fast so it says its undefined.
i get map() is not a function or for {blog.id} -- id is undefined.
When I console.log(blogs) i see my array of blogs so I'm not sure what I'm doing wrong. Is it because blogs is an array and so I need to do some kind of for loop to go through the each item and then map it?
Here is my main chunk of code and the console log
import React, {Component} from 'react';
import {connect} from 'react-redux'
import {loadBlogs} from '../../store/actions/blogActions'
class Bloglist extends Component {
componentDidMount() {
this.props.loadBlogs();
}
render() {
const {blogs} = this.props
console.log(blogs)
return (
<div>
<h1>{blogs.map(blog => (
<span>{blog.id}</span>
))}</h1>
</div>
)
}
}
const mapStateToProps = blogs => ({
blogs
})
const mapDispatchToProps = dispatch => ({
loadBlogs: () => dispatch(loadBlogs()),
})
export default connect(mapStateToProps, mapDispatchToProps)(Bloglist)
here is a console log example and an error:
Uncaught TypeError: blogs.map is not a function
this is when I just comment out the map() lines and just return a "hello", this console.log is showing me that the data is coming back
Bloglist.js:14 (3) [{…}, {…}, {…}]
Please let me know if you want any more samples or info. It's really important to get this done so any help would be appreciated! Thanks!
There can be 2 issues.
Please cross check the type of blogs, it should be array as map method works only on array.
You have to check for array's length before mapping it. As map method doesn't works on empty array.
Try this code --
<div>
<h1>{blogs && blogs.length > 0 ? blogs.map(blog => (
<span>{blog.id}</span>
)) : null}</h1>
</div>
At the beginning, your blogs is not an Array.
You should to update your reducer initialState, set blocks to be an empty array as default, just like
in reducer.js
const initialState = {
blogs: [],
};
export default (state = initialState, action) => {
switch(action) {
case....
default:
return { ...state };
}
}
Or, you also should check the blogs before rendering.
Array.isArray(blogs) && blogs.map(item => (
<div>
{// item details goes here}
</div>
))
use ? to handle this error. mostly probably the error is coming from the saga. you have to provide the code better suggest a solution.
<div>
<h1>{blogs?.map(blog => (
<span>{blog.id}</span>
))}</h1>
</div>
Try like this.
class Example extends Component {
state:{}
render(){
//.....code
}
}
Related
I have two pieces of state currently. One is an empty array called "subproducts", the other is called "additionalprod".
subproducts is an array which pulls information from my backend database using Axios. It is mounted inside of a componentDidMount for the API call.
additionalprod(uct) is a piece of state that gets its value from a prop from a parent, it is a number, in this case for my testing purposes, the number is 5. Number 5 links to a small array containing 3 properties. Product, img_url and id.
When I console.log the exact code
console.log(this.state.subproducts[5])
I get the response I want, that specific array, so I am 100% targeting it correctly. It currently has 2 arrays inside of it with their own props.
However, when I try and map through that state, using the code
{this.state.subproducts[5].map((product) => {
<h1>{product.img_url}</h1>
})}
I get the error
TypeError: Cannot read property 'map' of undefined
Anybody know whats wrong?
Code in question:
import React from "react"
import axios from "axios"
class SubProducts extends React.Component {
constructor(props) {
super(props)
this.state = {
subproducts: [],
stage: "3",
selected: this.props.selected,
additionalprod: this.props.additionalprod,
}
}
componentDidMount() {
axios.get("http://localhost/api/subproducts.php").then((res) => {
this.setState({ subproducts: res.data })
console.log(this.state.subproducts[5])
})
}
render() {
return (
<div>
{this.state.subproducts[this.state.additionalprod].map((product) => {
;<h1>{product.img_url}</h1>
})}
</div>
)
}
}
export default SubProducts
EDIT:
After using Sean Rileys fix, I am trying to console log {product} by itself and now I am being hit with this in the console, still can't get it to output though by putting {product.img_url}, there is an error, 'Failed to compile'
On the initial render, this.state.subproducts is empty so it can't run the map function on the array. It is only after the API call that the array is filled with data.
This is a very common React issue which was a very simple solution of checking that the array isn't empty before running the method on it.
{this.state.subproducts && this.state.subproducts[this.state.additionalprod].map((product) => {
<h1>{product.img_url}</h1>
})}
this.state.subproducts &&... is just checking if the array returns truthy which it will if it isn't empty and then, if so, it will run the next bit of code.
Another option is "optional chaining" which pretty much does the same thing.
{this.state.subproducts[this.state.additionalprod]?.map((product) => {
<h1>{product.img_url}</h1>
})}
Notice the question mark right before map ?.map. By adding the question mark after the array that we're calling the function on, it will only run if the array is defined otherwise it will return undefined but you won't get an error.
You have to wait for a response before render, just add checking on length for your array:
componentDidMount() {
this.setState({additionalprod: this.props.additionalprod}, () => {
axios.get("http://localhost/api/subproducts.php").then((res) => {
this.setState({ subproducts: res.data })
console.log(this.state.subproducts[5])
});
}
}
render() {
return (
<div>
{this.state.subproducts.length > 0 ?
this.state.subproducts[this.state.additionalprod].map((product) =>
<h1>{product.img_url}</h1>
}) : null
</div>
)
}
This is how my app looks like right now:
The search and sort functions are working dynamically, as soon as I type a letter, it finds the match but re-renders all of them.
If I type "hunger", it finds the hunger games films, but it's getting images and rendering when it already has them. Is there any way to make this process just for once so I don't wait for every search, every sorting? I use Redux so data is coming from store, store is getting the data from local json file.
I thought about storing on local storage but I couldn't figure it out. As you can see there is no ComponentDidMount or Hooks.
This is the code of this page:
import React from "react";
import "./MoviesAndSeries.css";
import ListShowItem from "./ListShowItem";
import { getVisibleShows } from "../Redux/Selector";
import { connect } from "react-redux";
import FilterShowItems from "./FilterShowItems";
const Movies: React.FC = (props: any) => {
return (
<div className="movies">
<FilterShowItems />
{props.movies.map((movie: any) => {
return <ListShowItem key={Math.random()} {...movie} />;
})}
</div>
);
};
const mapStateToProps = (state: any) => {
return {
movies: getVisibleShows(state.movies, state.filters),
filters: state.filters,
};
};
export default connect(mapStateToProps)(Movies);
You are using Math.random() as keys. The keys change all the time, and React can't know if the items already exist, so it re-renders all of them:
<ListShowItem key={Math.random()} {...movie} />;
Change the key to something stable, and unique, the movie id (if you have one) for example:
<ListShowItem key={movie.id} {...movie} />;
I'm currently trying to create a "TodoApp" using react-native and redux. I got the part where I can add and toggle different tasks working but I can't seem to get the visibility filter (show all/completed/active) in place.
Here's the relevant code of the TaskListContainer:
import { connect } from 'react-redux';
import TaskListComponent from '../components/TaskListComponent';
import {visibilityFilters} from "../actions/actionTypes";
const getVisibleTodos = (tasks, filter) => {
switch (filter) {
case visibilityFilters.SHOW_ALL:
return tasks;
case visibilityFilters.SHOW_COMPLETED:
return tasks.filter(t => t.completed);
case visibilityFilters.SHOW_ACTIVE:
return tasks.filter(t => !t.completed);
default:
return null;
}
}
const mapStateToProps = (state) => {
return {
//tasks: !state.taskReducers ? [] : state.taskReducers
tasks: getVisibleTodos(state.tasks, state.visibilityFilter)
};
};
const TaskListContainer = connect(mapStateToProps, null)(TaskListComponent);
export default TaskListContainer;
If the initial state is "SHOW_ALL" the app is working but whenever i add a new item it doesn't get displayed. If the state switches to "SHOW_COMPLETED" or "SHOW_ACTIVE" the app just crashes: "cannot read property 'filter' of undefined".
Every tutorial I've watched/read uses this "getVisibleTodos" function and it's working. Why wouldn't it for me?
Actually i don't think there is a need to do that way i have moved it to TaskListContainer it is working fine as expected see here:https://codesandbox.io/s/1ok4k5vro3
I made a work-around this problem. Instead of putting the getVisibleTodos function inside the Container I put it inside the Component, filtering the data after fetching it from the store and not just fetching the ones i was using. It a bit more inefficient but it works.
It's my understanding that the most common use care for iterating over a list of data is map, which is an array method that iterates over an array, but when I tried to apply it here:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => this.setState({ questions: response.data }));
}
// renderQuestions() {
// return this.state.questions.map(question => <Text>{}</Text>);
// }
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
I ended up getting an error in the Simulator saying that this.state.questions.map() is not a function. I have searched for similar errors online, but they do not apply to my use case.
Keep in mind I commented out the code and erased what I had inside of <Text> because my machine was about to take off.
I don't know what this error means short of not being able to use the map() array helper method, does that mean I need to be applying a different helper method to iterate through this list of questions?
I did a console log of the response object like so:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => console.log(response));
}
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
and I got back the response object in the console:
from axios with a status of 200 which means the request was successful. You will notice I also go the data property and inside that is the results property and then the category with questions is inside of it:
So I am wondering if its that results property that I need to also implmement, but when I tried it I would get map() undefined.
Your API returns an object, which has no map method.
response.data.results is an array so change it to that if you intend to map over it:
this.setState({ questions: response.data.results }))
It's advisable to use componentDidMount instead of componentWillMount for async update.
Trying to orient through the dark depths of Redux-React-API jungle - managed to fetch data from API and console.log it - but neither me nor my Google skills have managed to find out why it doesn't render.
React Components
Parent Component:
class Instagram extends Component {
componentWillMount(){
this.props.fetchInfo();
}
render() {
return (
<div className="container">
<div className="wrapper">
<InstagramPost />
</div>
</div>
)
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ fetchInfo }, dispatch);
}
export default connect(null, mapDispatchToProps)(Instagram);
Child Component:
class InstagramPost extends Component {
render() {
console.log(this.props.info);
this.props.info.map((p,i) => {
console.log("PROPS ID: " + p.id);
})
return (
<div>
<h1>POSTS</h1>
<ul className="uls">
{
this.props.info.map((inf, i) =>
<li key={i}>{inf.id}</li>
)
}
</ul>
</div>
)
}
}
const mapStateToProps = ({ info }) => {
return { info }
}
export default connect(mapStateToProps)(InstagramPost);
Redux Action method:
const ROOT_URL = 'https://jsonplaceholder.typicode.com/posts';
export const fetchInfo = () => {
const request = axios.get(ROOT_URL);
return {
type: types.FETCH_INFO,
payload: request
};
}
Redux Reducer method:
export default function(state = [], action) {
switch (action.type) {
case FETCH_INFO:
return action.payload.data;
default:
return state;
}
}
The JSON file looks like this:
In the console - it works and I get my Objects:
The state is also updated:
But when I map over this.props.info, trying to render this.props.info.id, nothing is rendered on the page.. Incredibly thankful for any input!
Looks like your props aren't set on the initial render. I'm guessing your API call hasn't finished.
Try checking the the variable is set or is an array first:
Something like this:
class InstagramPost extends Component {
render() {
if(!this.props.info) return null
return (
<div>
<h1>POSTS</h1>
<ul className="uls">
{
this.props.info.map((inf, i) => {
return <li key={i}>{inf.id}</li>
})
}
</ul>
</div>
)
}
}
const mapStateToProps = ({ info }) => {
return { info }
}
export default connect(mapStateToProps)(InstagramPost);
Or you may want to check the length this.props.info.length > 0.
There were two problems. As Mayank Shukla pointed out, nothing was returned from the map callback because of the block braces ({}) without a return statement.
The other problem was in the reducer. As the redux state for info is an array of users, you need to replace the old state on FETCH_INFO rather than add the fetched array to the beginning of it. Otherwise, you're maintaining an array of arrays of users, which will grow by one on each fetch.
Note that you don't need any checks on this.props.info, as it will be initialized to [] by your reducer and [].map(f) == [].
For redux debugging I can very much recommend installing the Redux DevTools extension, as it will allow you to inspect all updates to the store. It needs a little setup per project, but that's well worth it.
Oh, and in the future, you might want to refrain from updating your question with suggestions from the comments/answers, as the question will no longer make sense to other visitors :-)