How to fix the data.map not a function api - javascript

I'm trying to have and show the data of an api .
https://pokeapi.co/api/v2/pokemon/1
I have tried to display the data but it's not working.
PokemonSpecies.js
import React from "react";
// import Loader from "./Loader";
import { Card, Col } from "react-bootstrap";
import { Container, Row } from "react-bootstrap";
import CardPokemon from "./containers/CardPokemon";
class PokemonSpecies extends React.Component {
state = {
data: [],
isLoading: false,
abilities: []
};
async componentDidMount() {
const id = this.props.match.params.id;
this.setState({ isLoading: true });
try {
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const json = await response.json();
this.setState({
data: json,
isLoading: false
});
console.log({ json });
} catch (err) {
console.log(err.msg);
this.setState({ isLoading: false });
throw err;
}
}
render() {
const { data } = this.state;
return (
<div className="Pokemon">
<Container>
<Row>
<Col lg="4"></Col>
<Col lg="4">
<CardPokemon data={data} />
</Col>
<Col lg="4"></Col>
</Row>
<Row></Row>
</Container>
</div>
);
}
}
export default PokemonSpecies;
CardPokemon.js
import React from "react";
import DataSinglePokemon from "./DataSinglePokemon";
const CardPokemon = ({ data }) => {
return (
<>
{data.map((info, index) => (
<DataSinglePokemon
key={info + index}
id={info.id}
name={info.name
.toLowerCase()
.split(" ")
.map(letter => letter.charAt(0).toUpperCase() + letter.substring(1))
.join(" ")}
height={info.height}
{...info}
/>
))}
</>
);
};
export default CardPokemon;
DataSinglePokemon.js
import React from "react";
import { Card, Col } from "react-bootstrap";
import "../../App.css";
const DataSinglePokemon = props => {
const { height, name, id } = props;
const urlImage = `https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${id}.png?raw=true`;
return (
<Col lg={3}>
<Card>
<Card.Header>{id}</Card.Header>
<Card.Body className="mx-auto">
<Card.Title> {name} </Card.Title>
<Card.Text>
<img alt={name} src={urlImage} />
<br></br>
Taille : {height}
</Card.Text>
</Card.Body>
</Card>
</Col>
);
};
export default DataSinglePokemon;
I have the json, but when I'm trying to display the name or the abilities of the Pokemon I have this error I have try a lot of things but I'm new on React js... :
TypeError: data.map is not a function
CardPokemon
src/components/containers/CardPokemon.js:7
4 |
5 | const CardPokemon =({data}) =>{
6 |
> 7 | return(
8 |
9 | <>
10 | {data.map((info,index) =>(

I guess the problem at below line. Can you please check the response of the API call.
const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${id}`);
const json = await response.json();
this.setState({
data: json, // Check the data type of json. It should be in Array.
isLoading: false
});

I see that your code is working fine, the only mistake you made is that, the json variable is not an array. It has a results key inside which you need to map:
See this Log:
So you need to do this:
const json = await response.json();
this.setState({
data: json.results,
isLoading: false
});
Instead of using only the data.
Here is the demo Sandbox which I created to see the error: https://codesandbox.io/s/sleepy-jang-psedq
Hope this helps.

Related

Getting Parsing error: Unexpected token, expected "," error while passing addToCart in dispatch with two parameters

I am getting error Parsing error: Unexpected token, expected "," at line 55 while dispatching addToCart action with two parameters. I am using redux toolkit. At line 27 this dispatch method is working properly. Please see the picture below. I am passing two parameters as an object.(redux toolkit syntax for passing parameters in async function)
Error at line 55
//CartScreen
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useParams, useLocation } from 'react-router-dom';
import {
Row,
Col,
ListGroup,
Image,
Form,
Button,
Card,
} from 'react-bootstrap';
import Message from '../components/Message';
import { addToCart } from '../features/addToCart/cartSlice';
const CartScreen = () => {
const { id } = useParams();
const location = useLocation();
const qty = location.search ? Number(location.search.split('=')[1]) : 1;
const dispatch = useDispatch();
const cart = useSelector((store) => store.cart);
const { cartItems } = cart;
console.log(cart);
useEffect(() => {
if (id) {
dispatch(addToCart({ id, qty }));
}
}, [dispatch, id, qty]);
return (
<Row>
<Col md={8}>
<h1>Shopping Cart</h1>
{cartItems.length === 0 ? (
<Message>
Your Cart is empty <Link to='/'>Go Back</Link>{' '}
</Message>
) : (
<ListGroup variant='flush'>
{cartItems.map((item) => {
<ListGroup.Item key={item.product}>
<Row>
<Col md={2}>
<Image src={item.image} alt={item.name} fluid rounded />
</Col>
<Col md={3}>
<Link to={`/product/${item.product}`}>{item.name} </Link>
</Col>
<Col md={2}>${item.price}</Col>
<Col md={2}>
<Form.Control
as='select'
value={qty}
onChange={(e) => dispatch(addToCart({item.product,e.target.value}))}
>
{
[...Array(item.countInStock).keys()].map((x) => (
<option key={x + 1} value={x + 1}>
{x + 1}
</option>
))
}
console.log(...Array(product.countInStock).keys())
</Form.Control>
</Col>
</Row>
</ListGroup.Item>;
})}
</ListGroup>
)}
</Col>
<Col md={2}></Col>
<Col md={2}></Col>
</Row>
);
};
export default CartScreen;
//CartSlice
import { createSlice, createAsyncThunk, current } from '#reduxjs/toolkit';
import axios from 'axios';
export const addToCart = createAsyncThunk(
'addToCart',
async ({ id, qty }, thunkAPI) => {
//getting id and qty from cartScreen
try {
const { data } = await axios(`/api/products/${id}`);
localStorage.setItem(
'cartItems',
JSON.stringify(thunkAPI.getState().cart.cartItems)
); //data ko local storage save rakhny k lye... isko humny json.stringify kea hy kun k local storage mein sirf string store kr sakty... yahan hum ny local storage mein store kea hy lekin isko fetch store mein jakar krein gay
const productData = {
product: data._id,
name: data.name,
image: data.image,
price: data.price,
countInStock: data.countInStock,
qty,
};
return productData;
} catch (error) {
console.log(error);
}
}
);
const initialState = {
cartItems: [],
};
const cartSlice = createSlice({
name: 'cartReducer',
initialState,
extraReducers: {
[addToCart.pending]: (state) => {
state.cartItems = [];
},
[addToCart.fulfilled]: (state, action) => {
const item = action.payload;
const existItem = state.cartItems.find(
(cartItem) => cartItem.product === item.product
);
if (existItem) {
return {
...state,
cartItems: state.cartItems.map((cartItem) => {
return cartItem.product === existItem.product ? item : cartItem;
}),
};
} else {
state.cartItems = [...state.cartItems, item];
}
},
[addToCart.rejected]: (state) => {
state.cartItems = 'Some error has occured';
},
},
});
export default cartSlice.reducer;

Loading data into grid on click returns Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'dataState')

I have a grid created using a React library that I want to fill with data with an API call once the user clicks on a button called Fetch Products. Currently, my grid does not get populated and I get this error when I debug it:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'dataState')
and I am not sure why. Why isn't the grid populating properly and what else can I do? Here is my code:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Grid, GridColumn as Column } from '#progress/kendo-react-grid';
import { process } from '#progress/kendo-data-query';
import {
setExpandedState,
setGroupIds,
} from '#progress/kendo-react-data-tools';
const initialDataState = {
take: 10,
skip: 0,
products: [],
group: [
{
field: 'id',
},
],
};
const processWithGroups = (data, dataState) => {
const newDataState = process(data, dataState);
setGroupIds({
data: newDataState.data,
group: dataState.group,
});
return newDataState;
};
const fetchAllData = () => {
fetch(
'https://otp.metroservices.io/otp/routers/default/index/routes/uscalacmtarail:801/stops'
)
.then((response) => response.json())
.then((productsList) => {
const newDataState = processWithGroups(
productsList,
this.state.dataState
);
this.setState({
products: productsList, // update the data
result: newDataState, // update the procesed data
});
});
};
const FirstButton = () => {
return (
<div>
<button type="button" onClick={fetchAllData}>
Fetch Products
</button>
</div>
);
};
class App extends React.PureComponent {
state = {
dataState: initialDataState,
result: processWithGroups(initialDataState.products, initialDataState),
collapsedState: [],
products: [],
};
dataStateChange = (event) => {
const newDataState = processWithGroups(
this.state.products, // use the none processed data
event.dataState
);
this.setState({
result: newDataState,
dataState: event.dataState,
});
};
expandChange = (event) => {
const item = event.dataItem;
if (item.groupId) {
const newCollapsedIds = !event.value
? [...this.state.collapsedState, item.groupId]
: this.state.collapsedState.filter(
(groupId) => groupId !== item.groupId
);
this.setState({
collapsedState: newCollapsedIds,
});
}
};
// componentDidMount() {
// this.fetchAllData()
// }
render() {
const newData = setExpandedState({
data: this.state.result.data, // pass the proccessed data
collapsedIds: this.state.collapsedState,
});
return (
<div>
<FirstButton />
<Grid
style={{
height: '520px',
}}
resizable={true}
reorderable={true}
filterable={true}
sortable={true}
groupable={true}
data={newData}
onDataStateChange={this.dataStateChange}
{...this.state.dataState}
onExpandChange={this.expandChange}
expandField="expanded"
>
<Column field="id" filterable={false} title="ID" width="50px" />
<Column field="name" title="Name" />
<Column field="cluster" title="Cluster" filter="numeric" />
</Grid>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('my-app'));
this.state.dataState does not exist in fetchAllData.
You need to pass the this.state.dataState in App to FirstButton then to fetchAllData. After all that, you can use that
Put the functions inside the React.Component that require to interact with states and if needed to use something to the outside of the React.Component, pass the reference state. In your code, you can put your 'fetchAllData' function inside you React.Component and pass this as a prop to your FirstButton
Example:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Grid, GridColumn as Column } from '#progress/kendo-react-grid';
import { process } from '#progress/kendo-data-query';
import {
setExpandedState,
setGroupIds,
} from '#progress/kendo-react-data-tools';
const initialDataState = {
take: 10,
skip: 0,
products: [],
group: [
{
field: 'id',
},
],
};
const processWithGroups = (data, dataState) => {
const newDataState = process(data, dataState);
setGroupIds({
data: newDataState.data,
group: dataState.group,
});
return newDataState;
};
// pass the fetchAllData function as a prop
const FirstButton = ({fetchData}) => {
return (
<div>
<button type="button" onClick={fetchData}>
Fetch Products
</button>
</div>
);
};
class App extends React.PureComponent {
state = {
dataState: initialDataState,
result: processWithGroups(initialDataState.products, initialDataState),
collapsedState: [],
products: [],
};
dataStateChange = (event) => {
const newDataState = processWithGroups(
this.state.products, // use the none processed data
event.dataState
);
this.setState({
result: newDataState,
dataState: event.dataState,
});
};
expandChange = (event) => {
const item = event.dataItem;
if (item.groupId) {
const newCollapsedIds = !event.value
? [...this.state.collapsedState, item.groupId]
: this.state.collapsedState.filter(
(groupId) => groupId !== item.groupId
);
this.setState({
collapsedState: newCollapsedIds,
});
}
};
// you can put this function inside
fetchAllData = () => {
fetch(
'https://otp.metroservices.io/otp/routers/default/index/routes/uscalacmtarail:801/stops'
)
.then((response) => response.json())
.then((productsList) => {
const newDataState = processWithGroups(
productsList,
this.state.dataState
);
this.setState({
products: productsList, // update the data
result: newDataState, // update the procesed data
});
});
};
// componentDidMount() {
// this.fetchAllData()
// }
render() {
const newData = setExpandedState({
data: this.state.result.data, // pass the proccessed data
collapsedIds: this.state.collapsedState,
});
return (
<div>
<FirstButton fetchData={this.fetchAllData}/>
<Grid
style={{
height: '520px',
}}
resizable={true}
reorderable={true}
filterable={true}
sortable={true}
groupable={true}
data={newData}
onDataStateChange={this.dataStateChange}
{...this.state.dataState}
onExpandChange={this.expandChange}
expandField="expanded"
>
<Column field="id" filterable={false} title="ID" width="50px" />
<Column field="name" title="Name" />
<Column field="cluster" title="Cluster" filter="numeric" />
</Grid>
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('my-app'));

How can I write callback func for setState on event click

after onclick event occurs in backpackList.js, fetch data in context.js and then through setState I want to update noneUserCart . After that i want to get data from context.js to backpackList.js to show web page. but the data is inital data []. How can I solve this problem?!
I think this is a Asynchronous problem, but I'm new react, so I don't know how to write code for this. or do I use async, await.
Help me please!
import React, { Component } from 'react';
const ProductContext = React.createContext();
const ProductConsumer = ProductContext.Consumer;
class ProductProvider extends Component {
constructor() {
super();
this.state = {
totalProducts: 0,
isLogin: false,
cartList: [],
isNavOpen: false,
isCartOpen: false,
noneUserCart: [],
};
}
noneUserAddCart = bagId => {
fetch('/data/getdata.json', {
method: 'GET',
})
.then(res => res.json())
.catch(err => console.log(err))
.then(data => {
this.setState(
{
noneUserCart: [...this.state.noneUserCart, data],
},
() => console.log(this.state.noneUserCart)
);
});
};
render() {
return (
<ProductContext.Provider
value={{
...this.state,
handleCart: this.handleCart,
getToken: this.getToken,
addNoneUserCart: this.addNoneUserCart,
hanldeCheckout: this.hanldeCheckout,
openNav: this.openNav,
showCart: this.showCart,
habdleCartLsit: this.habdleCartLsit,
deleteCart: this.deleteCart,
noneUserAddCart: this.noneUserAddCart,
}}
>
{this.props.children}
</ProductContext.Provider>
);
}
}
export { ProductProvider, ProductConsumer };
import React, { Component } from 'react';
import { ProductConsumer } from '../../context';
export default class BackpackList extends Component {
render() {
const {
backpackdata,
backdescdata,
isdescOpen,
showDesc,
descClose,
rangenumone,
rangenumtwo,
} = this.props;
return (
<div>
{backdescdata.map((bag, inx) => {
return (
<>
{isdescOpen && bag.id > rangenumone && bag.id < rangenumtwo && (
<div className="listDescContainer" key={inx}>
<div className="listDescBox">
<ProductConsumer>
{value => (
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
</div>
)}
</ProductConsumer>
<span className="descClosebtn" onClick={descClose}>
X
</span>
</div>
</div>
</div>
)}
</>
);
})}
</div>
);
}
}
fetch is asynchronous, this.setState is yet called when console.log
<div
className="cartBtn"
onClick={() => {
const token = value.getToken();
if (token) {
value.handleCart(bag.id, token);
} else {
value.noneUserAddCart(bag.id);
console.log(value.noneUserCart);
// this part. value.noneUserCart is undefined
}
}}
>
add to cart.
{value.noneUserCart}
{/* when finished, result should show here */}
</div>

Meteor and react map returing undefined, I know the data is there but it loads, despite waiting for isLoading

I have the following code that passing leadsBuilder props to lead in the LeadBuilderSingle componenet. It has an array in a object and I access that array and try to map over it but it returns undefined. The data is being waited on and I am using isLoading, so I am not sure what is causing this error. It loads on first loading, but on page refresh gives me undefined.
import React, { useState, useEffect } from "react";
import Dasboard from "./Dashboard";
import { Container } from "../styles/Main";
import { LeadsBuilderCollection } from "../../api/LeadsCollection";
import { LeadBuilderSingle } from "../leads/LeadBuilderSingle";
import { useTracker } from "meteor/react-meteor-data";
const LeadCategoriesAdd = ({ params }) => {
const { leadsBuilder, isLoading } = useTracker(() => {
const noDataAvailable = { leadsBuilder: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leadsBuilder");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leadsBuilder = LeadsBuilderCollection.findOne({ _id: params._id });
return { leadsBuilder };
});
return (
<Container>
<Dasboard />
<main className="">
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<LeadBuilderSingle key={params._id} lead={leadsBuilder} />
</>
)}
</main>
</Container>
);
};
export default LeadCategoriesAdd;
import React from "react";
export const LeadBuilderSingle = ({ lead, onDeleteClick }) => {
console.log(lead);
return (
<>
<li>{lead.type}</li>
{lead.inputs.map((input, i) => {
return <p key={i}>{input.inputType}</p>;
})}
</>
);
};
FlowRouter.route("/leadCategories/:_id", {
name: "leadeBuilder",
action(params) {
mount(App, {
content: <LeadCategoriesAdd params={params} />,
});
},
});
try this :
lead.inputs && lead.inputs.map ((input, i) => {...}

Axios get method response in React cannot be displayed getting data from firebase as an array in my blog application

I wonder if someone could help me. I have read many StackOverflow's answers around this and other great articles like this one and I couldn't implement an answer yet.
I have got a simple blog app in React. I have a form to submit the data and I have separate post and posts component as well. I can actually send data to my firebase database. I also get the response in GET method but I cannot show the response as I need it to be. I need an array of posts which each post has a title and content so that I can send its data to my Post component. But I always get an error like( map cannot be used on the response) and I actually cannot get an array out of my database. I even wonder if I am sending data in the right format. Please check my code below and help me out. Thanks.
// The individual post component
const Post = props => (
<article className="post">
<h2 className="post-title">{props.title}</h2>
<hr />
<p className="post-content">{props.content}</p>
</article>
);
// The form component to be written later
class Forms extends React.Component {}
// The posts loop component
class Posts extends React.Component {
state = {
posts: null,
post: {
title: "",
content: ""
}
// error:false
};
componentDidMount() {
// const posts = this.state.posts;
axios
.get("firebaseURL/posts.json")
.then(response => {
const updatedPosts = response.data;
// const updatedPosts = Array.from(response.data).map(post => {
// return{
// ...post
// }
// });
this.setState({ posts: updatedPosts });
console.log(response.data);
console.log(updatedPosts);
});
}
handleChange = event => {
const name = event.target.name;
const value = event.target.value;
const { post } = this.state;
const newPost = {
...post,
[name]: value
};
this.setState({ post: newPost });
console.log(event.target.value);
console.log(this.state.post.title);
console.log(name);
};
handleSubmit = event => {
event.preventDefault();
const post = {
post: this.state.post
};
const posts = this.state.posts;
axios
.post("firebaseURL/posts.json", post)
.then(response => {
console.log(response);
this.setState({ post: response.data });
});
};
render() {
let posts = <p>No posts yet</p>;
if (this.state.posts) {
posts = this.state.posts.map(post => {
return <Post key={post.id} {...post} />;
});
}
return (
<React.Fragment>
<form className="new-post-form" onSubmit={this.handleSubmit}>
<label>
Post title
<input
className="title-input"
type="text"
name="title"
onChange={this.handleChange}
/>
</label>
<label>
Post content
<input
className="content-input"
type="text"
name="content"
onChange={this.handleChange}
/>
</label>
<input className="submit-button" type="submit" value="submit" />
</form>
</React.Fragment>
);
}
}
class App extends React.Component {
render() {
return (
<React.Fragment>
<Posts />
</React.Fragment>
);
}
}
// Render method to run the app
ReactDOM.render(<App />, document.getElementById("id"));
And this is a screenshot of my firebase database:
My Firebase database structure
It is interesting that what I found is rarely mentioned anywhere around it.
This is the entire Posts component:
class Posts extends React.Component {
state = {
posts: [],
post: {
title: "",
content: ""
}
};
componentWillMount() {
const { posts } = this.state;
axios
.get("firebaseURL/posts.json")
.then(response => {
const data = Object.values(response.data);
this.setState({ posts : data });
});
}
handleChange = event => {
const name = event.target.name;
const value = event.target.value;
const { post } = this.state;
const newPost = {
...post,
[name]: value
};
this.setState({ post: newPost });
console.log(event.target.value);
console.log(this.state.post.title);
console.log(name);
};
handleSubmit = event => {
event.preventDefault();
const {post} = this.state;
const {posts} = this.state;
axios
.post("firebaseURL/posts.json", post)
.then(response => {
console.log(response);
const newPost = response.data;
this.setState({ post: response.data });
});
};
render() {
let posts = <p>No posts yet</p>;
if (this.state.posts) {
posts = this.state.posts.map(post => {
return <Post key={post.id} {...post} />;
});
}
return (
<React.Fragment>
{posts}
<form className="new-post-form" onSubmit={this.handleSubmit}>
<label>
Post title
<input
className="title-input"
type="text"
name="title"
onChange={this.handleChange}
/>
</label>
<label>
Post content
<input
className="content-input"
type="text"
name="content"
onChange={this.handleChange}
/>
</label>
<input className="submit-button" type="submit" value="submit" />
</form>
</React.Fragment>
);
}
}
Actually as I first time read in this question you should not rely on console.log to see if your posts (or your response data) has been updated. Because in componentDidMount() when you immediately update state you will not see the change in console. So what I did was to display the data that I got from the response using map over the posts and it showed my items as I actually had an array although couldn't see in the console. This is my code for componentDidMount:
axios.get("firebaseURL/posts.json").then(response => {
const data = Object.values(response.data);
this.setState({
posts: data
});
And show the posts:
let posts = <p>No posts yet</p>;
if (this.state.posts) {
posts = this.state.posts.map(post => {
return <Post key={post.id} {...post} />;
});
}
And it shows all the posts as expected. Take away is to be careful once woking on componentDidMound and other lifecycle methods as you might not see the updated data in the console inside them but you actually need to use it as it is in the response. The state is updated but you are not able to see it inside that method.
Not a database expert, but I believe your database is structured a bit odd and will only cause problems further down the line, especially when it comes to editing/updating a single post. Ideally, it should structured like a JSON array:
posts: [
{
id: "LNO_qS0Y9PjIzGds5PW",
title: "Example title",
content: "This is just a test"
},
{
id: "LNOc1vnvA57AB4HkW_i",
title: "Example title",
content: "This is just a test"
},
...etc
]
instead its structured like a JSON object:
"posts": {
"LNO_qS0Y9PjIzGds5PW": {
"post": {
"title": "Example title",
"content": "This is just a test"
}
},
"LNOc1vnvA57AB4HkW_i": {
"post": {
"title": "Example title",
"content": "This is just a test"
}
},
...etc
}
Anyway, your project should have a parent Posts container-component that controls all your state and fetching of data, then it passes down its state and class methods to component children. Then the children can update or display the parent's state accordingly.
OR
You should separate your Posts container-component, so that it either displays found posts or a "No posts found" component. And then, have your Posts Form component be it's own/unshared component whose only function is to show a form and submit it to a DB.
Up to you and what you think fits your needs.
Working example: https://codesandbox.io/s/4x4kxn9qxw (the example below has one container-component that shares with many children)
Note: If you change posts to an empty array [], instead of data in fetchData()s this.setState() function, you can have the PostForm be displayed under the /posts route!
ex: .then(({ data }) => this.setState({ isLoading: false, posts: [] }))
index.js
import React from "react";
import { render } from "react-dom";
import App from "./routes";
import "uikit/dist/css/uikit.min.css";
import "./styles.css";
render(<App />, document.getElementById("root"));
routes/index.js
import React from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Home from "../components/Home";
import Header from "../components/Header";
import Posts from "../containers/Posts";
export default () => (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/posts" component={Posts} />
<Route path="/postsform" component={Posts} />
</Switch>
</div>
</BrowserRouter>
);
containers/Posts.js
import isEmpty from "lodash/isEmpty";
import React, { Component } from "react";
import axios from "axios";
import PostsForm from "../components/postsForm";
import ServerError from "../components/serverError";
import ShowPosts from "../components/showPosts";
import Spinner from "../components/spinner";
export default class Posts extends Component {
state = {
content: "",
error: "",
isLoading: true,
posts: [],
title: ""
};
componentDidUpdate = (prevProps, prevState) => {
// check if URL has changed from "/posts" to "/postsform" or vice-versa
if (this.props.location.pathname !== prevProps.location.pathname) {
// if so, check the location
this.setState({ isLoading: true }, () => this.checkLocation());
}
};
componentDidMount = () => this.checkLocation();
checkLocation = () => {
// if the location is "/posts" ...
this.props.location.pathname === "/posts"
? this.fetchData() // then fetch data
: this.setState({ // otherwise, clear state
content: "",
error: "",
isLoading: false,
posts: [],
title: ""
});
};
// fetches posts from DB and stores it in React state
fetchData = () => {
axios
.get("firebaseURL/posts.json")
.then(({ data }) => this.setState({ isLoading: false, posts: data }))
.catch(err => this.setState({ error: err.toString() }));
};
// handles postsForm input changes { content: value , title: value }
handleChange = e => this.setState({ [e.target.name]: e.target.value });
// handles postsForm form submission
handleSubmit = event => {
event.preventDefault();
const { content, title } = this.state;
alert(`Sumbitted values: ${title} - ${content}`);
/* axios.post("firebaseURL/posts.json", { post: { title, content }})
.then(({data}) => this.setState({ content: "", posts: data, title: "" }))
.catch(err => this.setState({ error: err.toString() }))
*/
};
// the below simply returns an if/else chain using the ternary operator
render = () => (
this.state.isLoading // if isLoading is true...
? <Spinner /> // show a spinner
: this.state.error // otherwise if there's a server error...
? <ServerError {...this.state} /> // show the error
: isEmpty(this.state.posts) // otherwise, if posts array is still empty..
? <PostsForm // show the postForm
{...this.state}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
/>
: <ShowPosts {...this.state} /> // otherwise, display found posts!
);
}
components/postsForm.js
import React from "react";
export default ({ content, handleSubmit, handleChange, title }) => (
<form
style={{ padding: "0 30px", width: 500 }}
className="new-post-form"
onSubmit={handleSubmit}
>
<label>
Post title
<input
style={{ marginBottom: 20 }}
className="uk-input"
type="text"
name="title"
onChange={handleChange}
placeholder="Enter post title..."
value={title}
/>
</label>
<label>
Post content
<input
style={{ marginBottom: 20 }}
className="uk-input"
type="text"
name="content"
onChange={handleChange}
placeholder="Enter post..."
value={content}
/>
</label>
<button
disabled={!title || !content}
className="uk-button uk-button-primary"
type="submit"
>
Submit
</button>
</form>
);
components/showPosts.js
import map from "lodash/map";
import React from "react";
export default ({ posts }) => (
<div className="posts">
{map(posts, ({ post: { content, title } }, key) => (
<div key={key} className="post">
<h2 className="post-title">{title}</h2>
<hr />
<p className="post-content">{content}</p>
</div>
))}
</div>
);
components/serverError.js
import React from "react";
export default ({ err }) => (
<div style={{ color: "red", padding: 20 }}>
<i style={{ marginRight: 5 }} className="fas fa-exclamation-circle" /> {err}
</div>
);

Categories