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

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>
);

Related

How to pass props through Link method in React?

I am trying to pass a prop from one component in which I search for and select a game to another component where I will render the details of the selected game. I am keeping my components as two separate pages, but I am struggling to get anything passing down to the child component. Here are my two files, and I have no idea where I am going wrong.
import React, { Component } from "react";
import Selected from "./Selected";
import { Link } from 'react-router-dom';
class Search extends Component {
constructor(props) {
super(props);
this.state = {
/*
API request format:
GET https://api.rawg.io/api/platforms?key=YOUR_API_KEY
GET https://api.rawg.io/api/games?key=YOUR_API_KEY&dates=2019-09-01,2019-09-30&platforms=18,1,7
Docs: https://api.rawg.io/docs
*/
baseURL: "https://api.rawg.io/api/games?",
apiKey: `key=${process.env.REACT_APP_RAWG_API_KEY}&`,
gamesQuery: "search=",
searchInput: "",
// later on we can determine whether to add additional parameters like page size, genres, etc.
searchURL: "",
gallery : [],
selectedGame: [],
};
}
handleChange = (event) => {
this.setState({
// we're grabbing the element or elements and dynamically setting the input value to the key corresponding to the input id of the same name in this.state
[event.target.id]: event.target.value,
});
};
handleSubmit = (event) => {
// keep the page from refreshing on submit
event.preventDefault();
this.setState(
{
// builds out our search url from the pieces we've assembled
searchURL:
this.state.baseURL +
this.state.apiKey +
this.state.gamesQuery +
this.state.searchInput,
},
() => {
// we fetch the url from the api
fetch(this.state.searchURL)
// .then waits till the fetch is complete
.then((response) => {
return response.json();
})
.then(
(json) => this.setState({
gallery : json.results
}),
(err) => console.log(err)
);
}
);
};
handleInspect = (event) => {
for (let i in this.state.gallery) {
if (i.id === event.id) {
this.setState ({
selectedGame : i
})
}
}
}
render() {
let game;
if (this.state.selectedGame) {
game = this.state.selectedGame
}
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>Search</label>
<input
id="searchInput"
type="text"
placeholder="What's the Name of the Game"
value={this.state.searchInput}
onChange={this.handleChange}
/>
<input type="submit" value="Find Games" />
</form>
<div id='gallery'>
{this.state.gallery.map(function(d, idx){
return (
<li key={idx}>
<a href={"/selected/"+d.id}
onClick={()=>this.handleInspect(d.id)}
>{d.name}</a>,
{d.id},
<Link to={{pathname: `/selected/${d.id}`,
gameResults : game}} />,
</li>)})}
</div>
</div>
);
}
}
export default Search;
And the component I try to pass to and fails.
import React from 'react';
import { Link } from 'react-router-dom';
class Selected extends React.Component {
render() {
{console.log(this.props)}
return (
<h1>woo</h1>
);
}};
export default Selected;
The result is below, with no props having been passed at all

react setState() from external?

New to Reactjs, I have followed a tut or 2 to build a relatively simple app, that sends queries to Mongodb and renders the results. Although I am yet to render them. I can pass the find() through and get back results that I like, and log them to the console, but for the life of me I cannot figure out how to get the results into "state", or anywhere else in the app. It's likely a very simple mistake somewhere. But I don't have enough knowledge of react to figure it out.
Here is the (small) App.js file in it's entirety, I thought it easier than trying to pick through it and make a valid sample.
// /client/App.js
import React, { Component } from 'react';
import axios from 'axios';
import * as PropTypes from "prop-types";
import {useEffect, useState} from "react";
function View(props) {
return null;
}
View.propTypes = {children: PropTypes.node};
function Text(props) {
return null;
}
function MyForm() {
const [user_search, setName] = useState("");
const handleSubmit = async (event) => {
event.preventDefault();
console.log(`The Search you entered was: ${user_search}`);
let felcher = user_search.split(/[ ,]+/);
let search_obj = {}
for (let i in felcher) {
search_obj[i] = felcher[i]
}
axios.post('http://localhost:3001/api/searchData', {
search_obj
}
).then(resp => {
console.log("RESPONSE FROM POST", resp['data'])
});
}
return (
<form onSubmit={handleSubmit}>
<label>Enter Search Terms:
<input
type="text"
value={user_search}
onChange={(e) => setName(e.target.value)}
/>
</label>
<input type="submit" />
</form>
)
}
let formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',})
Text.propTypes = {children: PropTypes.node};
class App extends Component {
// initialize our state
state = {
data: [],
_id: 0,
ticker: '',
primary_share: [],
title: null,
document_date: null,
release_date: null,
search_text: null,
url: null,
result_state: null,
};
componentDidMount() {
this.getDataFromDb();
if (!this.state.intervalIsSet) {
let interval = setInterval(this.getDataFromDb, 1000);
this.setState({ intervalIsSet: interval });
}
}
componentWillUnmount() {
if (this.state.intervalIsSet) {
clearInterval(this.state.intervalIsSet);
this.setState({ intervalIsSet: null });
}
}
getDataFromDb = () => {
fetch('http://localhost:3001/api/getData')
.then((data) => data.json())
.then((res) => this.setState({ data: res.data }));
};
render() {
const { data } = this.state;
return (
<div>
<MyForm />
<div class={"row"}>
<div class={"col-4"}>
{/*<ul>*/}
{/* {data.length <= 0*/}
{/* ? 'Getting Results......'*/}
{/* : data.map((dat) => (*/}
{/* <li class="border" style={{ padding: '10px' }} key={dat._id}>*/}
{/* <span style={{ color: 'gray' }}> Ticker: </span> {dat.ticker} <br />*/}
{/* <span style={{ color: 'gray' }}> Release Date: </span> {dat.release_date} <br />*/}
{/* <span style={{ color: 'gray' }}> Document Title: </span>{dat.title} <br />*/}
{/* <span style={{ color: 'gray' }}> Document URL: </span>{dat.url} <br />*/}
{/* </li>*/}
{/* ))}*/}
{/*</ul>*/}
</div>
</div>
</div>
);
}
}
export default App;
The area I am struggling with is where print the results to the console here ...
console.log("RESPONSE FROM POST", resp['data'])
In the "MyForm()" function. I feel if I could setState() there, but it appears to not work.
But I can't do anything else that gets them over to render. HELP!!!!
SetState is a hook that returns two items: the state and setter (or the function to set the state). In your case you will have a setState at the top of your function:
const [data, setData] = useState([]) // what ever you put as an argument will be the default data until it is set
const [err, setErr] = useState(null) // this will be our error state
In your axios request you will use:
axios
.post('http://localhost:3001/api/searchData', { search_obj })
.then(resp => {
setData(resp['data']) // see here we call the state function
})
.catch(err => {
setErr(err) // and here for our error
})
Then in our return we can use the data any way we like:
return (
<>
<div>{data}</data>
<div>{err ? err : 'no errors'}</div>
</>
)
Does that make sense? (Code not tested)

How to change Antd form initialValues depends at url or id?

I got same component with Antd form for add/edit article. With pathes in router
<Route path="/add" component={ !currentUser ? Login : ArticleEditor } />
<Route path="/article/:id/edit" component={ !currentUser ? Login : ArticleEditor } />
When I click "edit" button I add initialValues to form, than if I click "Create new article" url changes to "/add", but form didn't update values. Values remains from edited article. How to update form values? Tried to set initialValues depends at path, or "id" but its not worked. How to update antd form values in that case?
const initialValues = this.props.location.pathname === '/add' ? {} : {
title: this.props?.title,
body: this.props?.body,
description: this.props?.description
};
Here you can see the component code - codesandbox link
The main issue with the code is form fields are not reset when url is changed, you can detect path change in shouldComponentUpdate and set isLoading to true and rest should work.
Updating initialValues will not work because, antd does shallow compare and once initialValues are set, you will not be able to change them.
There was an issue in the logic of componentDidUpdate which I corrected as well.
import React from "react";
import ErrorsList from "../ErrorsList/ErrorsList";
import userService from "../../services/userService";
import { connect } from "react-redux";
import { push } from "react-router-redux";
import { Form, Input, Button } from "antd";
import { store } from "../../store";
import actionCreators from "../../actionCreators";
const formItemLayout = {
labelCol: { span: 24 },
wrapperCol: { span: 24 }
};
const formSingleItemLayout = {
wrapperCol: { span: 24, offset: 0 }
};
const mapStateToProps = (state) => ({
...state.editor
});
const mapDispatchToProps = (dispatch) => ({
onLoad: (payload) => dispatch(actionCreators.doEditorLoaded(payload)),
onUnload: () => dispatch(actionCreators.doEditorUnloaded()),
onUpdateField: (key, value) =>
dispatch(actionCreators.doUpdateFieldEditor(key, value)),
onSubmit: (payload, slug) => {
dispatch(actionCreators.doArticleSubmitted(payload));
store.dispatch(push(`/`)); //article/${slug}
},
onRedirect: () => dispatch(actionCreators.doRedirect())
});
class ArticleEditor extends React.Component {
constructor(props) {
super(props);
this.id = this.props.match.params.id;
const updateFieldEvent = (key) => (e) =>
this.props.onUpdateField(key, e.target.value);
this.changeTitle = updateFieldEvent("title");
this.changeDescription = updateFieldEvent("description");
this.changeBody = updateFieldEvent("body");
this.changeTagInput = updateFieldEvent("tagInput");
this.isLoading = true;
this.submitForm = () => {
const article = {
title: this.props.title,
description: this.props.description,
body: this.props.body,
tagList: this.props.tagInput.split(",")
};
const slug = { slug: this.props.articleSlug };
const promise = this.props.articleSlug
? userService.articles.update(Object.assign(article, slug))
: userService.articles.create(article);
this.props.onSubmit(promise, this.props.articleSlug);
};
}
componentDidUpdate(prevProps, prevState) {
if (this.props.match.params.id !== prevProps.match.params.id) {
if (prevProps.match.params.id) {
this.props.onUnload();
}
this.id = this.props.match.params.id;
if (this.id) {
return this.props.onLoad(userService.articles.get(this.id));
}
this.props.onLoad(null);
}
this.isLoading = false;
}
componentDidMount() {
if (this.id) {
this.isLoading = true;
return this.props.onLoad(userService.articles.get(this.id));
}
this.isLoading = false;
this.props.onLoad(null);
}
componentWillUnmount() {
this.props.onUnload();
}
shouldComponentUpdate(newProps, newState) {
if (this.props.match.params.id !== newProps.match.params.id) {
this.isLoading = true;
}
return true;
}
render() {
const { errors } = this.props;
const initialValues = {
title: this.props?.title,
body: this.props?.body,
description: this.props?.description,
tags: this.props?.tagList
};
return this.isLoading ? (
"loading..."
) : (
<div className="editor-page">
<div className="container page">
<div className="">
<div className="">
<ErrorsList errors={errors}></ErrorsList>
<Form
{...formItemLayout}
initialValues={initialValues}
onFinish={this.submitForm}
>
<Form.Item
label="Title"
name="title"
placeholder="Article Title"
rules={[
{
required: true,
message: "Please input article title"
}
]}
>
<Input onChange={this.changeTitle} />
</Form.Item>
<Form.Item
label="Description"
name="description"
placeholder="Short description"
rules={[
{
required: true,
message: "Please input article description"
}
]}
>
<Input onChange={this.changeDescription} />
</Form.Item>
<Form.Item
name="body"
label="Article Text"
placeholder="article text"
>
<Input.TextArea onChange={this.changeBody} />
</Form.Item>
<Form.Item name="tags" label="Tags" placeholder="Enter tags">
<Input onChange={this.changeTagInput} />
</Form.Item>
<Form.Item {...formSingleItemLayout}>
<Button
className="editor-form__btn"
type="primary"
htmlType="submit"
disabled={this.props.inProgress}
>
Submit Article
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ArticleEditor);
take a look at this forked codesandbox.
You have to clean the fields before you re-use the 'ArticleEditor' component. Here you are using the same component for two different route, hence it's not changing.
You have to check if you are editing or adding a new entry to the Editor. Your editor component may look like this then,
const ArticleEditor = props => {
const [form] = Form.useForm();
useEffect(() => {
if (props.match.params.id) form.setFieldsValue({value : 'Some values'})
else form.resetFields()
}, [props?.match?.params]);
return (
<Form form={form} onFinish={yourFinishMethod}>
//...your form fields
</Form>
)
}

Fetch and display new contents when user click on each list item [React]

First, I want to load a JSON from my server; it will contain a list of objects. Then, I need to render these objects like a list. When a list item is clicked, the user should be redirected to a page that shows information about the clicked item. That information displayed should be fetched from another API call to my server.
Can someone guide me, please?
state = {
isLoading: true,
users: [],
error: null
};
fetchUsers() {
fetch(`http://localhost:3001/blog/view`)
.then(response => response.json())
.then(data =>
this.setState({
users: data,
isLoading: false,
})
)
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.fetchUsers();
}
render() {
const { isLoading, users, error } = this.state;
return (
<React.Fragment>
<h1 style={{textAlign: 'center'}}>My Blog</h1>
{error ? <p>{error.message}</p> : null}
{!isLoading ? (
users.map(user => {
const { _id, title, details,date } = user;
return (
<div className='blog'>
<div key={_id}>
<p>Name: {title}</p>
<p>Email Address: {details}</p>
<p >Email Address: {date}</p>
<hr className='banner-text hr' />
</div>
</div>
);
})
) : (
<h3>Loading...</h3>
)}
</React.Fragment>
);
}
}
Here is a sample project to demonstrate how you can use react and react-router together.
We first fetch a list of users from some api, and then display them as a list.
Using react-router, we add a link to each item so that when it's clicked, the page url changes
but page wont reload! these are internal links
Then again using react-router, we display different contents based on the url.
And at last, we have a UserPage component that when mounted, fetches the data for the specific user page and renders it.
Hope it is clear enough
This is a very good tutorial on react-router
And this is the official react tutorial
I strongly recommend that you take a look at them
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js
import React, { Component } from 'react'
import {BrowserRouter as Router, Link, Route} from 'react-router-dom'
import User from "./User"
import UserPage from "./UserPage"
class App extends Component {
constructor() {
super()
this.state = {
users: [],
isLoading: false
}
}
componentDidMount() {
this.setState({ isLoading: true })
fetch('https://your/api/url')
.then(response => response.json())
.then(response => {
this.setState({
users: response,
isLoading: false
})
})
}
render() {
let content;
if (this.state.isLoading) {
content = <h1>Loading...</h1>
} else if (this.state.users.length > 0) {
content = this.state.users.map(u =>
<Link to={`/users/${u._id}`}>
<User key={u._id} user={u} />
</Link>
)
} else {
content = <h4>No users found!</h4>
}
return (
<Router>
<div>
<Route path="/users/:_id" component={UserPage}/>
<Route exact={true} path="/" render={() => content}/>
</div>
</Router>
)
}
}
export default App;
User.js
import React from 'react'
function User(props) {
const {title, details, date} = props.user;
return (
<div>
<p>Name: {title}</p>
<p>Email Address: {details}</p>
<p>Email Address: {date}</p>
<hr className='banner-text hr' />
</div>
)
}
export default User
UserPage.js
import React, {Component} from 'react'
class UserPage extends Component{
constructor(props) {
super(props)
this.state = {
isLoading: false,
data: '',
id: this.props.match.params._id
}
}
componentDidMount() {
this.setState({ isLoading: true })
fetch(`https://your/api/url/for/user/${this.state.id}`)
.then(response => response.json())
.then(response => {
this.setState({
data: response,
isLoading: false
})
})
}
render() {
return (
this.state.isLoading ?
(<h1>Loading page of user {this.state.id}...</h1>)
:
(
<div>
<p>{this.state.data}</p>
</div>
)
)
}
}
export default UserPage

Show full post using direct route, how to pass values to the template based on route name?

I have a simple post React project where I have a list of posts Posts.js and a full post template FullPost.js. In Posts.js, I pass the slug and id so that I can render the url.
For example, 'food' has a list of posts. I click on a post and it passes the 'slug: carrots' and 'id: 2' to 'food/carrots'. This 'id' is passed so it can find the correct pathname to my carrot post in my database i.e. 'mydatabase.com/food/posts/2/'
This works well, however when I refresh the page the 'slug' and 'id' that got passed to the FullPost component earlier disappears. Additionally, I would like to be able to access the URL directly, for example, if I type in 'www.mywebsite.com/food/carrots' it would load my carrot post. Currently my FullPost does not load when I refresh or when I go to the post's URL directly. Is there a way to load the post from these other entry points?
Below is my code:
My database:
posts:
0:
slug: "beans"
id: 1
1:
slug: "milk"
id: 2
2:
slug: "carrots"
id: 3
Posts.js: A list of posts.
import React, { Component } from 'react';
import axios from '../../../axiosPosts';
import Aux from '../../../hoc/Aux/Aux';
import classes from './Posts.css';
import Post from '../../../components/Post/Post';
class Posts extends Component {
state = {
posts: []
}
componentDidMount () {
this.getData(this.props.pathname, this.props.filter);
}
getData(pathname, filter) {
axios.get(pathname + '.json')
.then(response => {
const post = response.data.filter(({category}) => category === filter);
const updatedPosts = post.map(post => {
return {
...post
}
});
this.setState({
posts: updatedPosts
});
})
.catch(error => {
console.log(error);
});
}
postSelectedHandler = ( slug, id ) => {
const URL = `${this.props.match.url}/${slug}`;
this.props.history.push({pathname: URL, id: id });
}
render () {
let posts = <p style={{textAlign: 'center'}}>Whoops! Something went wrong.</p>;
if(!this.state.error) {
posts = this.state.posts.map(post => {
return (
<Post
key={post.id}
title={post.slug}
clicked={() => this.postSelectedHandler( post.slug, post.id )} />
);
});
};
return (
<Aux>
<div className={classes.PostList}>{posts}</div>
</Aux>
)
}
}
export default Posts;
FullPost.js (Actual Post)
import React, { Component } from 'react';
import axios from '../../../axiosPosts';
import classes from './FullPost.css';
class FullPost extends Component {
state = {
loadedPost: null,
}
componentDidMount () {
const { location: {id} } = this.props;
this.loadData(id);
}
loadData(id) {
if ( id ) {
if ( !this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id !== +id) ) {
axios.get( '/food/posts/' + (id - 1) + '.json' )
.then( response => {
this.setState( { loadedPost: response.data, locationId: id } );
});
}
}
}
render () {
let post = <p style={{ textAlign: 'center' }}>Please select a Post!</p>;
const { location: {id} } = this.props;
if ( id ) {
post = <p style={{ textAlign: 'center' }}>Loading...!</p>;
}
if ( this.state.loadedPost ) {
post = (
<div className={classes.FullPost}>
<h1 className={classes.Title}>{this.state.loadedPost.title}</h1>
</div>
);
}
return post;
}
}
export default FullPost;
App.js: Main js
import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';
import classes from './App.css';
import Layout from './hoc/Layout/Layout';
import Posts from './pages/Posts/Posts';
import FullPost from './pages/FullPost/FullPost';
class App extends Component {
constructor(props) {
super(props);
console.log('[App.js] Inside Constructor', props);
}
render() {
return (
<div className={classes.App}>
<Layout>
<Switch>
<Route path="/food/" exact component={Posts} />
<Route path="/food/:slug" exact component={FullPost} />
<Route render={() => <h1>Whoops! What you're looking for isn't here anymore.</h1>} />
</Switch>
</Layout>
</div>
);
}
}
export default App;
You have to use something like React Router.
In a nutshell your issue is that there is no URL matching logic in your code (everything would work the same even if you didn't change the URL at all).

Categories