I correctly passed the data to the consumer using context api,
however the product component doesn't display.
Listofproducts component:
import React, { Component } from "react";
import Product from "./product";
import { Consumer } from "./context";
class Listofproducts extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<Consumer>
{value => {
value.map(data => {
console.log(data); // its returning the data correctly
return <Product key={data.id} product={data} />;
});
}}
</Consumer>
);
}
}
export default Listofproducts;
Product component where i sent the data with the consumer value:
import React, { Component } from "react";
class Product extends Component {
render() {
console.log(this.props.product); // not showing anything on the console nor an error
return <div>hello from product</div>;
}
}
export default Product;
Thank you in advance.
You aren't returning the mapped data from within the Consumer which is why your Product components are not getting rendered. Add a return keyword to mapped data and it will work correctly
<Consumer>
{value => {
return value.map(data => {
console.log(data);
return <Product key={data.id} product={data} />;
});
}}
</Consumer>
You are not returning anything from the function given as child to Consumer.
Add the return keyword and it will work as expected.
class Listofproducts extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<Consumer>
{value => {
return value.map(data => {
console.log(data); // its returning the data correctly
return <Product key={data.id} product={data} />;
});
}}
</Consumer>
);
}
}
Related
I'm trying to do something like this;
I have a file called /components/master_layout.js and it has the following content:
import useUser from "../data/use-user";
function MasterLayout({ children }) {
const { data, error, mutate } = useUser();
if ( error ) return <div>error</div>
if ( !data && !error ) return <div>loading..</div>
return (
<div>
{children}
</div>
)
}
export default MasterLayout
In short, this layout file returns according to the response of the useuser function.
Here is an example of a page where I use this layout:
file path and name: /pages/dashboard/index.js
import MasterLayout from "../../components/master_layout";
function Dashboard() {
return (
<MasterLayout>
dashboard..
</MasterLayout>
)
}
export default Dashboard
Can I use useUser data from Layout in '/pages/dashboard/index.js' and my other pages?
The reason I want this is, I'm trying to do something like:
import MasterLayout from "../../components/master_layout";
function Dashboard({data}) {
return (
<MasterLayout>
Welcome back, {data.username}
</MasterLayout>
)
}
export default Dashboard
Do I have any other choice but to pull the useUser for each page one by one and transfer it to the master layout as
You can use HOC pattern in this case. Something like
// with-data.js
import React from "react";
import useUser from "../data/use-user";
const withData = (WrappedComponent) => {
class WithData extends React.Component {
constructor(props) {
super(props);
this.state = {
data: "",
};
}
componentDidMount() {
const { data, error, mutate } = useUser();
this.setState({data:data});
}
render() {
const { data, ...otherProps } = this.props;
return (
<WrappedComponent data={this.state.data}/>
)
//* See how we can enhance the functionality of the wrapped component
}
}
return WithData;
};
export default withData;
Now you can use the withData,
import MasterLayout from "../../components/master_layout";
import withData from "../withData.js"
function Dashboard({data}) {
return (
<MasterLayout>
Welcome back, {data.username}
</MasterLayout>
)
}
export default withData(Dashboard);
In fact wrapping around any component with withData, can access the data variable.
I am trying to display an array of news articles on the page and getting an error:
Unhandled Rejection (TypeError): this.state.newsPost.map is not a function
and this is my code that i am running:
import React, { Component } from 'react'
import { Container, Row, Col } from 'bootstrap-4-react';
import News from '../Articles/News';
import Post from '../Posts/Post/Post';
import axios from 'axios';
const REACT_APP_NEWS_ARTICLE_API = process.env.REACT_APP_NEWS_ARTICLE_API
export default class Body extends Component {
constructor(props){
super(props)
this.state = {
posts: [{}],
newsPost: [{}]
}
}
componentDidMount = (props) => {
axios.all([axios.get(`${process.env.REACT_APP_SERVER_URL}/posts`),
axios.get(`https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=${REACT_APP_NEWS_ARTICLE_API}`)])
.then(axios.spread((...responses) => {
const responseOne = responses[0]
const responseTwo = responses[1]
this.setState({
posts: responseOne.data,
newsPost: responseTwo.data
})
}
))
};
render() {
console.log(this.state.newsPost) //returns an array of articles
return (
<Container id="bodycontainer" className="container">
<Row className="technews">
// This is the loop that's returning the error
{this.state.newsPost.map((item) => {
<div key={item.id} className="technewsitem">
{item}
</div>
})}
</Row>
</Container>
)
}
}
How can I run the code in order to display the array on the page, any pointers is greatly appreciated.
Check if the value you are assigning to newsPost inside componentDidMount() is an Array
Check data is an Array then use array methods
1
newPost && Array.isArray(newPost) ? newPost.map((value,index)=>{
//code
}):<></>
2
newPost.length>0 ? newPost.map((value,index)=>{
//code
}):<></>
So I'm trying to create a list app using React. I don't have any errors or warnings, and my input bar is showing, but my textList won't render. In other words when ever I hit enter, the text info I put in the input bar won't create a list as expected.
I tried using keyCode === 13 and onKeyDown, but it's not having any effect on the app. What am I missng?
Here's my code:
filtered-input.js
import React, { Component } from "react";
import "./filtered-input.css";
import ItemList from "./item-list";
export class FilteredInput extends Component {
constructor(props) {
super(props);
this.state = {
textList: [],
};
}
handleChange(e) {
this.setState({ value: e.target.value });
}
handleAdd = (e) => {
if (e.keyCode === 13) {
let lists = this.props.state.textList;
lists.push(this.props.state.value);
this.setState({ textList: lists });
}
};
render() {
console.log("Here comes state -->");
console.log(this.state);
return (
<div>
<input
className="filtered-input-box"
type="text"
onKeyDown={this.props.handleAdd}
value={this.state.value}
onChange={this.props.handleChange}
/>
<ItemList item={this.state.textList}></ItemList>
</div>
);
}
}
ItemList.js
import React, { Component } from 'react';
export class ItemList extends Component {
render() {
const items = this.props.item.map((item) =>
<li>{item}</li>
);
return (
<ul>
{items}
</ul>
);
}
}
export default ItemList;
App.js
import React, { Component } from 'react';
import { FilteredInput } from './filtered-input.js';
export class App extends Component {
render() {
return (
<div>
<p>This page demonstrates a component that maintains its own state.</p>
<FilteredInput />
</div>
);
}
}
There were small syntactical mistakes here and there, apart from that everything looks perfect:
import React, { Component } from "react";
import "./style.css";
export default class App extends Component {
render() {
return (
<div>
<p>This page demonstrates a component that maintains its own state.</p>
<FilteredInput />
</div>
);
}
}
class FilteredInput extends Component {
constructor(props) {
super(props);
this.state = {
textList: [],
value: ""
};
}
handleChange = e => {
console.log(e.target.value);
this.setState({ value: e.target.value });
};
handleAdd = e => {
console.log("hi");
if (e.keyCode === 13) {
let lists = this.state.textList;
lists.push(this.state.value);
this.setState({ textList: lists });
}
};
render() {
console.log("Here comes state -->");
console.log(this.state);
return (
<div>
<input
className="filtered-input-box"
type="text"
onKeyDown={this.handleAdd}
value={this.state.value}
onChange={this.handleChange}
/>
<ItemList item={this.state.textList} />
</div>
);
}
}
class ItemList extends Component {
render() {
const items = this.props.item?.map(item => <li>{item}</li>);
return <ul>{items}</ul>;
}
}
Full working app : Stackblitz
I am getting the following error in my code. Can you please help me to understand the issue. I have included my page component and task list component.
TypeError: this.state.tasks.map is not a function
Page Show.js
import React, { Component } from 'react';
import axios from 'axios';
import TasksList from './TasksList';
export default class Show extends Component {
constructor(props) {
super(props);
this.state = {tasks: [] };
}
componentDidMount(){
axios.post('http://mohamed-bouhlel.com/p5/todolist/todophp/show.php')
.then(response => {
this.setState({ tasks: response.data });
})
.catch(function (error) {
console.log(error);
})
}
tasksList(){
return this.state.tasks.map(function(object,i){
return <TasksList obj = {object} key={i} />;
});
}
render() {
return (
<div>
{ this.tasksList() }
</div>
)
}
}
Page TasksList.js
import React, { Component } from 'react';
export default class TasksList extends Component {
render() {
return (
<div>
<div>{this.props.obj.task}</div>
</div>
)
}
}
Using a GET request and correct protocol (https vs http) seems to resolve the issue.
axios.get("https://mohamed-bouhlel.com/p5/todolist/todophp/show.php")
Response.data is not an array and basically you can't call map on a non-array.
I suggest console.log(response.data) to check the data type.
And I guess maybe you're doing a axios.post instead of a correct axios.get. log the response.data and you'll find out.
After get the comments array from post component and pass it to comments component
the logs start to show the error in the screenshot below
the components are:
import React, { Component } from "react";
import axios from "axios";
import Comments from "../components/comments";
class Article extends Component {
constructor(props) {
super(props);
this.state = {
title: "",
error: "",
comment: ""
};
}
componentDidMount() {
this.getComments();
}
getComments = () => {
const {
match: { params }
} = this.props;
return axios
.get(`/articles/${params.id}/comments`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
}
})
.then(response => {
return response.json();
})
.then(response => this.setState({ comments: response.comments }))
.catch(error =>
this.setState({
error
})
);
};
render() {
return (
<div>
{this.state.title}
<div>
<h2>Comments</h2>
<Comments
getComments={this.getComments}
/>
</div>
</div>
);
}
}
export default Article;
and Comments component
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;
so, I've tried to pass it by props, and set the state on comments component.
and instead of use just comments.map I've tried to use this.state but show the same error in the logs.
So, someone please would like to clarify this kind of issue?
seems pretty usual issue when working with react.
If an error occurs you do:
.catch(error => this.setState({ error }) );
which makes the chained promise resolve to undefined and that is used as comments in the Comments state. So you have to return an array from the catch:
.catch(error => {
this.setState({ error });
return [];
});
Additionally it woupd make sense to not render the Comments child at all if the parents state contains an error.
The other way is checking whether it’s an array and if so check it’s length and then do .map. You have initialized comments to empty array so we don’t need to check whether it’s an array but to be on safer side if api response receives an object then it will set object to comments so in that case comments.length won’t work so it’s good to check whether it’s an array or not.
Below change would work
<div>
{Array.isArray(comments) && comments.length>0 && comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
The first time the comments component renders there was no response yet so comments were undefined.
import React, { Component } from "react";
import PropTypes from "prop-types";
import Comment from "./comment";
import axios from "axios";
import Article from "../screens/article";
class Comments extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
comment: "",
error: ""
};
this.load = this.load.bind(this);
this.comment = this.comment.bind(this);
}
componentDidMount() {
this.load();
}
load() {
return this.props.getComments().then(comments => {
this.setState({ comments });
return comments;
});
}
comment() {
return this.props.submitComment().then(comment => {
this.setState({ comment }).then(this.load);
});
}
render() {
const { comments } = this.state;
if (!comments) return <p>No comments Available</p>;
return (
<div>
{comments.map(comment => (
<Comment key={comment.id} commment={comment} />
))}
</div>
);
}
}
export default Comments;