Im having trouble accessing data from my backend using express, I am also sort of confused on how i should set up my routing. Do I only need express routes for when I have to dig something out of my database?
My User component
import React from 'react';
class User extends React.Component {
state = {
username: ""
}
componentDidMount() {
fetch("/api/:user")
.then(res =>res.json()
.then(data => {
console.log("data", JSON.stringify(data, null, 4));
this.setState({data})
}))
}
render() {
return (
<div>
{this.state.username}
</div>
)
}
}
export default User;
my user routes, the route is /api/:user
router.get("/:user", (req, res)=>{
// find user
console.log(req.params);
User.find({username:req.params.user}, (err, foundUser)=>{
if(err) {
console.log(err);
} else {
res.json(foundUser);
}
});
});
when i console.log(req.params) it returns :user, not the actual user requested
I am giving a sample code for you to learn.
In the App.js we define the routes using react-router-dom package.
import React from "react";
import ReactDOM from "react-dom";
import {
BrowserRouter as Router,
Route,
Redirect,
Switch,
Link
} from "react-router-dom";
import Users from "./Users";
import User from "./User";
function App() {
return (
<Router>
<Link to="/">Users</Link>
<Switch>
<Route path="/" exact component={Users} />
<Route path="/:userId/" exact component={User} />
<Redirect to="/" />
</Switch>
</Router>
);
}
export default App;
In Users component, we get a list of users from jsonplaceholder api and list them, give a dynamic link with a userId to the User component.
import React from "react";
import { Link } from "react-router-dom";
class Users extends React.Component {
state = {
users: []
};
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => res.json())
.then(users => this.setState({ users }));
}
render() {
return (
<ul>
{this.state.users.map(user => (
<li key={user.id}>
<Link to={`/${user.id}`}> {user.name}</Link>
</li>
))}
</ul>
);
}
}
export default Users;
In the user component, we get the userId param from this.props.match.params.userId,
and using that userId we call another api to get user details.
import React from "react";
class User extends React.Component {
state = {
user: null
};
componentDidMount() {
fetch(`https://jsonplaceholder.typicode.com/users/${this.props.match.params.userId}`)
.then(res => res.json())
.then(user => {
this.setState({ user });
});
}
render() {
const { user } = this.state;
if (!user) return <div>Loading user...</div>;
return (
<div>
<h1>User Name: {user.name}</h1>
<p>Email: {user.email}</p>
<p>Website: {user.website}</p>
</div>
);
}
}
export default User;
Note that we dynamically constructed the user detail api url by with template literal using the backtick character. This was one of the problems you had in your code.
Codesandbox
In your url, the :user is a way to say in the backend code "a value goes at this place", and this must be the user id from the database I presume.
So in the React code you should do :
const userId = 4 // the id of the user that you want to fetch
fetch(`/api/${userId}`)
Related
having some trouble rendering props in a component for a project using Reactjs. The information is showing in props in the react dev tools, yet I am unable to render them on the browser. When console logging, there is no value showing...
I'm wondering if I need to dig deeper into the api in order to grab what I need?
CocktailRecipe.js
````import React, { Component } from 'react'
// import Spinner from '../layout/Spinner'
class CocktailRecipe extends Component {
componentDidMount(){
this.props.getCocktail(this.props.match.params.idDrink);
// console.log(this.props.match.params.idDrink)
}
render() {
const {strDrink} = this.props.cocktailrecipe;
console.log(strDrink);
// console.log(this.props.cocktailrecipe.strDrink);
// const {loading} = this.props.loading;
// if (loading) {
// <Spinner />
// }else{
return (
<div>
<h3>{strDrink}</h3>
<h3>This is the title</h3>
</div>
)
// }
}
}
export default CocktailRecipe````
app.js
````import { Component, Fragment } from 'react';
import { BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import './App.css';
import Navbar from './layout/Navbar';
import CocktailList from './cocktail/CocktailList';
import axios from 'axios';
import Search from './cocktail/Search';
import Alert from './layout/Alert';
import About from './pages/About';
import CocktailRecipe from './cocktail/CocktailRecipe';
class App extends Component {
state={
cocktails: [],
cocktailrecipe:{},
loading: false,
msg:'',
type:''
}
async componentDidMount() {
try {
this.setState({loading: true})
const res = await axios.get('https://www.thecocktaildb.com/api/json/v1/1/search.php?s=');
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
handleSearchCocktails= async (text) => {
try{
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${text}`);
// console.log(res.data);
this.setState({cocktails: res.data.drinks, loading: false})
} catch(error) {
console.log(error)
}
}
// Get cocktail recipe
getCocktail = async (idDrink) => {
try {
const res = await axios.get(`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${idDrink}`);
// console.log(res.data.drinks);
this.setState({cocktailrecipe: res.data.drinks.id, loading: false})
} catch(error) {
console.log(error)
}
}
handleClearCocktails= () => {
this.setState({cocktails:[], loading: false})
}
handleSetAlert=(msgfromSearch, typefromSearch)=>{
this.setState({ msg:msgfromSearch, type:typefromSearch })
setTimeout(()=>this.setState({msg:'', type:''}), 5000)
}
render() {
const {cocktails, loading, cocktailrecipe} = this.state;
return (
<Router>
<div className="App">
<Navbar title="COCKTAIL LIBRARY" />
<div className="container">
<Alert msg={this.state.msg} type={this.state.type} />
<Switch>
<Route exact path='/' render={props=>(
<Fragment>
<Search searchCocktails={this.handleSearchCocktails} clearCocktails={this.handleClearCocktails} showClear={this.state.cocktails.length>0?true:false} setAlert={this.handleSetAlert} />
<CocktailList loading={loading} cocktails={cocktails} />
</Fragment>
)} />
<Route exact path='/about' component={About} />
<Route exact path='/cocktailRecipe/:idDrink' render={props => (
<CocktailRecipe {...props} getCocktail={this.getCocktail} cocktailrecipe={cocktailrecipe} loading={loading}/>
)} />
</Switch>
</div>
</div>
</Router>
);
}
}
export default App;````
In your screenshot, props cocktailrecipe is an array of object.
Use array desctructuring instead of object on CocktailRecipe.js
- const {strDrink} = this.props.cocktailrecipe;
+ const [strDrink] = this.props.cocktailrecipe;
So turns out that my wifi connection is part of the problem. And grabbing the wrong object.
In CocktailRecipe.js I added in:
line 22:
const { drinks } = this.props.cocktailrecipe;
and then put into the render():
{drinks && drinks[0].strDrink }
I'm told that this may not be the most elegant or efficient solution, so if anybody has a better way, please let me know.
Hi I have been developing this application using react and react-router-dom The Page component is wrapped by a HOC that imports a contentservice to access a rest api.
My navigation is in the App component. The relevant part is the
<Link to="/page/123">About Page</Link>
and
<Link to="/page/456">Contact Page</Link>
When these links are clicked the page doesn't redraw as i expected. First time i go to 'About Page' it's all good. Then when i click to go to 'Contact Page' nothing changes. Then i click on the 'About Page' again and the 'Contact Page' shows.
In all the cases above the browser address bar shows the right path and if i refresh the page i go to the right page.
Here is my navigation page:
import React, { Component } from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { connect } from "react-redux";
import Home from "./Home";
import Admin from "./Admin";
import Members from "./Members";
import Login from "./Login";
import Page from "./Page";
import PrivateRoute from "./PrivateRoute";
import "./App.css";
class App extends Component {
render() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home Page</Link>
</li>
<li>
<Link to="/page/123">About Page</Link>
</li>
<li>
<Link to="/page/456">Contact Page</Link>
</li>
<li>
<Link to="/members">Members</Link>
</li>
<li>
<Link to="/admin">Admin</Link>
</li>
</ul>
</div>
<Switch>
<Route path="/login" component={Login} />
<Route path="/page/:id" component={Page} />
<Route exact path="/" component={Home} />
<PrivateRoute path="/members">
<Members />
</PrivateRoute>
<PrivateRoute path="/admin">
<Admin />
</PrivateRoute>
</Switch>
</Router>
);
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
};
};
export default connect(mapStateToProps, null)(App);
This is my page component:
import React, { Component } from "react";
import WithBackend from "./WithBackend";
class Page extends Component {
constructor(props) {
super(props);
this.resource = "/pages/";
this.state = { model: null };
}
render() {
if (this.state.model != null) {
return (
<div className="container">
<div className="row">
<div className="col-md">
<h1>{this.state.model.title}</h1>
<h2 dangerouslySetInnerHTML={{ __html: this.state.model.body }} />
</div>
</div>
</div>
);
} else {
return (
<div>
<h2>Page id: {this.props.match.params.id}</h2>
</div>
);
}
}
componentDidMount() {
this.props
.getEntity(this.resource, this.props.match.params.id)
.then((model) => this.setState({ model }));
}
componentDidUpdate(nextProps) {
if (nextProps.match.params.id !== this.props.match.params.id) {
this.props
.getEntity(this.resource, nextProps.match.params.id)
.then((data) => {
this.setState({ model: data });
});
}
}
}
export default WithBackend(Page);
This is the Withbackend HOC:
import React from "react";
import ContentService from "./ContentService";
const WithBackend = (WrappedComponent) => {
class HOC extends React.Component {
constructor() {
super();
this.contentService = new ContentService();
this.getEntity = this.getEntity.bind(this);
this.getEntities = this.getEntities.bind(this);
}
getEntity(resource, id) {
return this.contentService
.getEntity(resource, id)
.then((response) => response.json())
.catch((e) => {
console.log(e);
});
}
getEntities(resource) {
return this.contentService
.getEntities(resource)
.then((response) => response.json())
.catch((e) => {
console.log(e);
});
}
render() {
return (
<WrappedComponent
getEntity={this.getEntity}
getEntities={this.getEntities}
{...this.props}
/>
);
}
}
return HOC;
};
export default WithBackend;
And the content service:
class ContentService {
baseUrl = "http://localhost:8080";
getEntity(resource, id) {
const path = this.baseUrl + resource + id;
const fetchPromise = fetch(path, {
method: "GET",
});
return Promise.resolve(fetchPromise);
}
getEntities(resource) {
const fetchPromise = fetch(this.baseUrl + resource, {
method: "GET",
});
return Promise.resolve(fetchPromise);
}
}
export default ContentService;
Has anyone got any ideas why this is happening? I am not sure if it has anything to do with the Page component being wrapped by HOC but just thought it is worth mentioning.
Thank you.
Issue
The componentDidUpdate lifecycle method receives the previous props, state, and snapshot values, not the next ones.
componentDidUpdate
componentDidUpdate(prevProps, prevState, snapshot)
By sending the "previous" props' match param id you were a "cycle" behind.
Solution
Use the current id value from props.
componentDidUpdate(prevProps) {
if (prevProps.match.params.id !== this.props.match.params.id) {
this.props
.getEntity(this.resource, this.props.match.params.id)
.then((data) => {
this.setState({ model: data });
});
}
}
I'm coding login function that would return to home component after finish save token data to client.
However, when it go back to homepage, i can not use setState because do not have any componentWillMount or componentDidMount function was called.
Login.js
axios.post(`${Config.API_URL}/users/login`, param)
.then(response => {
if (response) {
this.setState({
errorCode : response.status
});
}
if(response.status===ErrorCode.SUCCESS){
var authorization = {"authorization": response.headers.authorization.toString()}
SessionStorage.saveLocalStorage(authorization);
this.props.history.push("/");
}
})
App.js
componentWillMount() {
if(SessionStorage.isAuthorization()){
this.setState({
isAuthorization : true
});
}
console.log('Component Will MOUNT!')
}
ComponentWillMount() never been called so can not set value for isAuthorization = true anyway.
A couple of notes:
1) I'm assuming App.js is actually the holder of all your routes. In this case, App.js is never unmounted from ReactDOM. What you want to do is define a separate component for your "/" route, keeping your App.js clean, focusing only on rendering routes. We will call this component Home.
2) I'm not familiar with any library that uses SessionStorage(). You might be looking for the sessionStorage native JS library. This will have methods called setItem and getItem for storing and retrieving your token.
3) With the separate Home component, we can call componentDidMount() to retrieve the token.
With that in mind we can do the following as a template on how you can achieve the functionality you're looking for: https://codesandbox.io/s/suspicious-leftpad-moqs2
App.js
class App extends React.Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<div>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/login" component={Login} />
</Switch>
</div>
</BrowserRouter>
</Provider>
);
}
}
Home.js
import React from "react";
import { Link } from "react-router-dom";
class Home extends React.Component {
state = {
token: ""
};
componentDidMount() {
const token = sessionStorage.getItem("authorization");
if (token) {
this.setState({
token: token
});
}
}
render() {
return (
<div>
<Link to="/login">Login</Link>
<p>{this.state.token}</p>
</div>
);
}
}
export default Home;
Login.js
import React from "react";
class Login extends React.Component {
handleClick = () => {
var token = 210;
sessionStorage.setItem("authorization", token);
this.props.history.push("/");
};
render() {
return (
<div>
<button onClick={this.handleClick}>Login</button>
</div>
);
}
}
export default Login;
please, what is the best way in React how to achieve:
submit form (and..)
redirect to another page (and..)
have some props from the origin form here?
I have discovered two possibilities how to redirect:
Source article: https://tylermcginnis.com/react-router-programmatically-navigate/
1) with React Router: history.push()
2) with React Router: <Redirect />
1) With history.push(): Redirecting works but i have no idea how to add custom props to redirected page.
2) With <Redirect />: adding custom props works (in this way):
<Redirect to={{ pathname: '/products', state: { id: '123' } }} />
But redirecting does not work to me, I keep receiving errors after submission.
Source code:
import React from 'react';
import './App.css';
import { withRouter, Redirect } from 'react-router-dom'
class App extends React.Component {
state = {
toDashboard: false,
}
handleSubmit = () => {
this.setState(() => ({
toDashboard: true
}));
}
render() {
if (this.state.toDashboard === true) {
return <Redirect to={{
pathname: '/products', state: { id: '123' }
}} />
}
return (
<div>
<h1>Register</h1>
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default withRouter(App);
Errors:
Warning: You tried to redirect to the same route you're currently on: /products"
Form submission canceled because the form is not connected
What is the best way how to achieve my target, please?
You need to cancel the default submit action.
so change you handleSubmit method to
handleSubmit = (e) => {
e.preventDefault();
this.setState({
toDashboard: true
});
}
What is finally working fine to me is code below here.
From App.js it is routed to Products.js, then i click on the button and it is redirected to NotFound.js and i can reach props "state: { id: 123 }" and i display it here.
Hope it will help to someone who is looking for some working submission patern.
App.js
import React from 'react';
import './App.css';
import { Route, Switch } from 'react-router-dom';
import Products from './Products';
import NotFound from './NotFound';
import Home from "./Home";
class App extends React.Component {
render() {
return (
<div>
<Switch>
<Route path="/products" component={Products} />
<Route path="/notfound" component={NotFound} />
<Route path="/" exact component={Home} />
</Switch>
</div>
);
}
}
export default App;
Products.js
import React, { Component } from "react";
class Products extends Component {
handleSubmit = (e) => {
e.preventDefault();
this.props.history.push({ pathname: '/notfound', state: { id: 123 } });
}
render() {
console.log(this.props);
return (
<div>
<h1>Products</h1>
<form onSubmit={this.handleSubmit}>
<button type="submit">Submit</button>
</form>
</div>
);
}
}
export default Products;
NotFound.js
import React from "react";
const NotFound = (props) => {
console.log(props);
return (
<div>
<h1>Not Found</h1>
<h2>{props.location.state.id}</h2>
</div>
);
};
export default NotFound;
I'm using React with the Wordpress REST API. The issue I am having is I cannot seem to wrap my head around how to (properly) use the component lifecycles to update the Post component when the slug property changes on the root App Component and fetching async data.
The way I have it set up currently, the App component state looks something like this:
this.state = {
pages: this.getPages(),
slug: this.getSlug(),
homePage: this.fetchPost('home'),
};
So the pages property is a promise and the App component initially renders a Spinner component. Eventually the async call receives a response. I perform a filter on the array of post objects to look for the current page post.
const thePage = this.state.pages.filter(page => {
return page.slug === slug;
});
The filter returns an array with one object (this current page). I update the state with this.setState({post: thePage[0]});
When I change change routes with react-router-dom, the slug and post are not updating. Below is my code.
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './components/App/App';
// Take the React component and show it on the screen
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'));
App JS:
// App.js
import React, {Component} from 'react';
import { Route, withRouter } from 'react-router-dom';
import axios from 'axios';
import './App.scss';
import {wpAPI} from '../..//api';
import {/*loadState,*/ saveState} from '../loadState/loadState';
import FrontPage from '../../pages/FrontPage';
import Header from '../Header/Header';
import Post from '../Post/Post';
import Sidebar from '../Sidebar/Sidebar';
import Footer from '../Footer/Footer';
import Spinner from '../Spinner/Spinner';
// Create a React Component
class App extends Component {
constructor(props) {
super(props);
// Bindings
this.getPages = this.getPages.bind(this);
this.getPages();
this.state = {
isHome: false,
slug: this.props.location.pathname,
fetchingPages: true,
fetchingPost: true,
};
console.log('App State: (constructor)');
console.log(this.state);
}
/**
* Fetch Data
* #return {Promise}
*/
getPages = async () => {
const response = await axios.get(wpAPI['pages']);
this.setState({
fetchingPages: false,
pages: response.data
});
saveState(this.state);
}
getPage = (slug) => {
const thePage = this.state.pages.filter(page => {
return page.slug === slug.replace('/', '');
});
this.setState({
isHome: false,
fetchingPost: false,
post: thePage[0],
slug: slug,
});
}
/**
* The component has mounted. Fetch post based on slug
*/
componentDidMount() {
console.log('App State: (componentDidMount)');
console.log(this.state);
console.log('App Props: (componentDidMount)');
console.log(this.props);
}
componentDidUpdate(prevProps, prevState) {
console.log('App State: (componentDidUpdate)');
console.log(this.state);
const {fetchingPages, fetchingPost, isHome} = this.state;
const slug = this.props.location.pathname;
if (this.state.slug !== this.props.location.pathname) {
console.log('Slugs Dont match, getting page');
this.getPage(slug);
}
if (slug === '/' && !isHome) {
console.log('Setting isHome True');
this.setState({
isHome: true,
fetchingPost: false
});
}
if (fetchingPages === false && fetchingPost === true) {
console.log('Fetching Post');
this.getPage(slug);
}
}
renderContent() {
const {post, fetchingPost} = this.state;
if (!fetchingPost) {
return (
<section id="app" className="animated fadeIn">
<Header />
<main>
<Route path="/" exact render={(props) => <FrontPage {...props} /> } />
<Route path="/:slug" exact render={(props) => <Post post={post} /> } />
</main>
<Sidebar />
<Footer />
</section>
)
}
return <Spinner message='loading data...' />;
}
render() {
return this.renderContent();
}
};
export default withRouter(App);
Post.js
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import './Post.scss';
import Spinner from '../Spinner/Spinner';
class Post extends Component {
constructor(props) {
super(props);
this.state = {
slug: props.slug,
post: props.post,
};
}
componentDidMount() {
console.log('Post State: (componentDidUpdate)');
console.log(this.state);
}
componentDidUpdate() {
console.log('Post State: (componentDidUpdate)');
}
render() {
if ( this.state.post ) {
const {post} = this.state;
return (
<div className={`post post-${post.id} ${post.slug} animated fadeIn`}>
<header>
<h1 className="small-caps" dangerouslySetInnerHTML={{__html: post.title.rendered}}></h1>
</header>
<section id="content" dangerouslySetInnerHTML={{__html: post.content.rendered}}></section>
</div>
)
}
return <Spinner message='Fetching Post...'/>
}
}
export default withRouter(Post);
There's nothing in your code to say that when your App receives new props from the withRouter HOC, then update state.slug.
You could add:
this.setState({
slug: this.getSlug();
});
to your componentDidUpdate() function, however I'm not sure why you need it as state when it's available as a prop anyway in this.props.location.pathname which is being passed down to your FrontPage component, and could just as easily be passed down to your Posts component in the same way.