I have a spring boot api with crud functionalities, on my react frontend I have this, which is a dashboard component and inside i am rendering a list of ProjectItem components and passing them to the dashboard component as props.
When I delete a project I'd like it to immediately remove the project from the component without having to refresh for it to happen.
Since I am passing the props down to my Dashboard component I am a bit confused on how to achieve this.
ProjectItem.js
BackendService is a service class with axios calls for the crud operations
import React, { useEffect, useState } from 'react'
import BackendService from '../services/BackendService';
import { Link } from 'react-router-dom';
import { useParams } from 'react-router';
const ProjectItem = ({projectName, projectIdentifier, description}) => {
const onDeleteClick = (id) => {
if (window.confirm("Are you sure you want to delete this project?")) {
BackendService.deleteProject(id)
.then()
.catch((err) => {
console.log(err.response);
});
alert("Project with ID " + id + " was deleted successfully");
}
};
return (
<div className="container">
<div className="card card-body bg-light mb-3">
<div className="row">
<div className="col-2">
<span className="mx-auto">{projectIdentifier}</span>
</div>
<div className="col-lg-6 col-md-4 col-8">
<h3>{projectName}</h3>
<p>{description}</p>
</div>
<div className="col-md-4 d-none d-lg-block">
<ul className="list-group">
<Link to="">
<li className="list-group-item update">
<i className="fa fa-edit pr-1"> Update Project Info</i>
</li>
</Link>
<button
className="list-group-item delete"
onClick={() => onDeleteClick(projectIdentifier)}
>
<i className="fa fa-minus-circle pr-1"> Delete Project</i>
</button>
</ul>
</div>
</div>
</div>
</div>
);
};
export default ProjectItem;
Dashboard.js
Where the ProjectItem components are rendered
import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import BackendService from '../services/BackendService'
import AppNavbar from './AppNavbar'
import ProjectItem from './ProjectItem'
const Dashboard = () => {
const [project, setProject] = useState({
projectName: "",
projectIdentifier: "",
description: "",
});
useEffect(() => {
BackendService.getProjects().then((res) => {
setProject(res.data);
});
}, []);
return (
<div className="projects">
<AppNavbar />
<div className="container">
<div className="row">
<div className="col-md-12">
<h1 className="display-4 text-center">Projects</h1>
<Link to="/addProject">
<button className="btn btn-warning">Create Project</button>
</Link>
{project &&
Object.values(project).map((prj) => {
return (
<div>
<ProjectItem key={prj.id}
projectName={prj.projectName}
projectIdentifier={prj.projectIdentifier}
description={prj.description}
/>
</div>
);
})}
<hr />
</div>
</div>
</div>
</div>
);
};
export default Dashboard
If you want to remove item without refresh the page then you have to call setProject and set it to new project list after BackendService.deleteProject request done in onDeleteClick.
https://reactjs.org/docs/state-and-lifecycle.html
I'm building an react app where I have a feed component and the feed component consists of array of post components. The post component contains a link in it for viewing the full post component. I'm not able to pass the respective props to the full post component. I'm not able to do a nested route for the fullpost component.
this is my layout component
import React, { Component } from "react";
import Header from "../Header/Header";
import classes from "./Layout.css";
import { Route, Switch } from "react-router-dom";
import FullPost from "../Feed/FullPost/FullPost";
import axios from "axios";
import Feed from "../Feed/Feed";
class Layout extends Component {
state = {
users: [],
};
componentDidMount() {
axios
.get("https://goodwill-60d8a.firebaseio.com/Users.json")
.then((response) => {
this.setState({ users: response.data });
});
}
render() {
console.log(this.state.users);
return (
<div className={classes.main}>
<div>
<Header />
</div>
<div className={classes.content}>
<Switch>
<Route
path="/"
exact
render={() => <Feed users={this.state.users} />}
/>
<Route path="/fullpost" exact render={() => <FullPost />} />
</Switch>
</div>
</div>
);
}
}
export default Layout;
FEED component
import React from "react";
import Post from "./Post/Post";
import classes from "./Feed.css";
const feed = (props) => {
const feedItems = Object.keys(props.users).map((key) => ({
id: key,
...props.users[key],
}));
return (
<div className={classes.feed}>
{feedItems.map((items) => (
<Post
key={items.id}
profilename={items.profileName}
profilepic={items.profilePic}
timestamp={items.timeStamp}
contentimage={items.contentImage}
contenttext={items.contentText}
trend={items.trend}
></Post>
))}
</div>
);
};
export default feed;
Post component
import React from "react";
import classes from "./Post.css";
import ShareButton from "../../UI/ShareButton/ShareButton";
import { Link } from "react-router-dom";
const post = (props) => {
return (
<div className={classes.post}>
<div className={classes.header}>
<div className={classes.profileimage}>
<img className={classes.pic} src={props.profilepic} alt="" />
</div>
<div className={classes.name}>{props.profilename}</div>
<div className={classes.timestamp}>{props.timestamp}</div>
</div>
<div className={classes.container}>
<div className={classes.image}>
<img src={props.contentimage} alt="" />
</div>
<div>
<div className={classes.text}>
<span>{props.contenttext}</span>
</div>
<div className={classes.fullpost}>
<Link to="/fullpost" {...props}>
See Full Post
</Link>
</div>
</div>
</div>
<div className={classes.footer}>
<div className={classes.trend}>
<span>{props.trend}</span>
</div>
<ShareButton />
</div>
</div>
);
};
export default post;
Full Post component
import React from "react";
import classes from "./FullPost.css";
import pic from "../../../Assets/Images/self.jpg";
import { Link } from "react-router-dom";
const fullPost = (props) => {
return (
<div className={classes.fullpost_container}>
<div className={classes.photo_container}>
<img src={props.profilepic} alt=" " />
</div>
<div className={classes.sidebar}>
<div className={classes.profileimage}>
<img src={props.profilePic} alt="" />
</div>
<div className={classes.profilename}>Tony Kroos</div>
<div className={classes.timestamp}>3 Hours Ago</div>
</div>
<div className={classes.feedlink}>
<Link to="/" className={classes.feedlink_text}>
See more posts
</Link>
</div>
</div>
);
};
export default fullPost;
I've got a problem with react and react-router.
When I click on a link (in my example contact in Footer.js), the url changes, but the desired component Location is not shown. When I refresh the site then, the correct component is displayed.
App.js:
import React, { Component } from 'react';
import { BrowserRouter as Router, HashRouter, Route, Link } from 'react-router-dom';
import 'bootstrap/dist/css/bootstrap.css';
import Footer from './Footer.js';
import Navigation from './Navigation.js';
import Background from './Background.js';
import Home from './Home.js';
import Products from './Products.js';
import Industries from './Industries.js';
import Partner from './Partner.js';
import Location from './Location.js';
import MeetUs from './MeetUs.js';
import ScrollUp from './ScrollUp.js';
import Divider from './Divider.js';
import Country from './Country.js';
import Language from './Language.js';
import Waypoint from 'react-waypoint';
import $ from "jquery";
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentLanguage: 'en',
currentBU: '',
currentIndustry: '',
showMainProductGroups: false,
currentCountry: 'group',
countryObject: Country['group'],
contacts: [],
mainProductGroups: [],
};
}
handleCountryChange() {
//...
}
handleLanguageChange() {
//...
}
handleBUChange() {
//...
}
render() {
const routes = [
{
path: '/',
exact: true,
components: () =>
<div>
<Home key="home" currentLanguage={this.state.currentLanguage} />
</div>,
},
{
path: '/contact',
exact: true,
components: () => <Location key="locations" currentLanguage={this.state.currentLanguage} country={this.state.countryObject} contacts= {this.state.contacts} onCountryChange={this.handleCountryChange.bind(this)} />
},
]
return (
<HashRouter>
<div>
<Background />
<div id="wrap">
<div id="main" className="container clear-top marginBottom50px">
<div id="content">
<Navigation key="navBar" currentLanguage={this.state.currentLanguage} onLanguageChange={this.handleLanguageChange.bind(this)} onBUChange={this.handleBUChange.bind(this)} onCountryChange={this.handleCountryChange.bind(this)} />
{
routes.map((route, index) => (
<Route key={index} path={route.path} exact={route.exact} component={route.components} />
))
}
</div>
</div>
</div>
<Footer key="footer" currentLanguage={this.state.currentLanguage} />
<ScrollUp key="scrollUp" />
</div>
</HashRouter>
);
}
}
export default App;
Home.js:
import React, { Component } from 'react';
import $ from "jquery";
import { Link } from 'react-router-dom';
import {withRouter} from 'react-router';
import Language from './Language.js';
import locations from './locations.jpg';
import locationLegend from './locationLegend.jpg';
require('bootstrap')
class Home extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<div className="container marginTop50px marginBottom50px area">
<div className="row">
<div className="col-12 text-center animDelay2 fadeInDown animated">
<h1>International Distribution of Specialty Chemicals</h1>
</div>
</div>
<div className="row marginTop25px">
<div className="col-12 text-center animDelay2 fadeInUp animated">
{Language[this.props.currentLanguage].homeStartText}
</div>
</div>
<div className="row marginTop25px">
<div className="col-12 text-center">
<img src={locations} className="img-fluid" alt="Locations" />
</div>
</div>
<div className="row marginTop25px">
<div className="col-12 text-center">
<img src={locationLegend} className="img-fluid" alt="Locations" />
</div>
</div>
</div>
);
}
}
export default withRouter(Home);
Location.js:
import React, { Component } from 'react';
import $ from "jquery";
import { Link } from 'react-router-dom';
import Language from './Language.js';
import Country from './Country.js';
import ContactPerson from './ContactPerson.js';
import locations from './locations.png';
import phone from './phoneBlack.svg';
import fax from './faxBlack.svg';
import email from './emailBlack.svg';
import {withRouter} from 'react-router';
require('bootstrap');
class Location extends Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('Country change:' + this.props.country.key);
$('#selectCountry').val(this.props.country.key); //name['en']
}
onCountryChange() {
let countryName = this.refs.country.value;
this.props.onCountryChange(countryName);
}
render() {
return (
<div className="container marginTop50px marginBottom50px area" id="locations">
<div className="row">
<div className="col-12 text-center">
<h2>{Language[this.props.currentLanguage].locations}</h2>
</div>
</div>
<div className="row marginTop25px">
<div className="col-12 text-center">
<div className="form-group">
<select id="selectCountry" className="form-control" ref="country" onChange={this.onCountryChange.bind(this)}>
<option defaultValue>{Language[this.props.currentLanguage].selectLocation.toUpperCase()}</option>
{
Object.keys(Country).map((countryKey) => {
const country = Country[countryKey];
return (
<option value={countryKey} key={"loc" + countryKey}>{country.name[this.props.currentLanguage].toUpperCase()}</option>
);
})
}
</select>
</div>
</div>
</div>
<div className="row marginTop25px">
<div className="col-12 text-center">
{this.props.country.name[this.props.currentLanguage].toUpperCase()}
<br />
<address>
<span dangerouslySetInnerHTML={{__html: this.props.country.address}}></span>
<br />
<br />
<img src={phone} alt="Anrufen" className="phoneMain"></img><span> </span>
<a href={this.props.country.phoneHTML}>{this.props.country.phone}</a>
<br />
<img src={fax} alt="Fax" className="phoneMain"></img><span> </span>
<a href={this.props.country.faxHTML}>{this.props.country.fax}</a>
<br />
<img src={email} alt="Email" className="emailMain"></img><span> </span>
<a href={"mailto://" + this.props.country.email}>{this.props.country.email}</a>
</address>
</div>
</div>
<div className="row marginTop25px">
<div className="col-12 text-center">
{Language[this.props.currentLanguage].vatRegistrationNumber + ": " + this.props.country.vatNo}
<br />
{Language[this.props.currentLanguage].registrationOffice + ": "}
<span dangerouslySetInnerHTML={{__html: this.props.country.registrationOffice}}></span>
</div>
</div>
<div className="row marginTop50px">
<div className="col-12 text-center">
<h3>{Language[this.props.currentLanguage].contact}</h3>
</div>
</div>
<div className="row">
{
this.props.contacts.map((contact) => {
return (
<div className="col-12 col-sm-12 col-md-12 col-lg-6 text-center">
<ContactPerson contact={contact} key={"contact" + contact.id} />
</div>
);
})
}
</div>
</div>
);
}
}
export default withRouter(Location);
Footer.js:
import React, { Component } from 'react';
import $ from "jquery";
import { Link } from 'react-router-dom';
import {withRouter} from 'react-router';
import Language from './Language.js';
import phone from './phoneWhite.svg';
import fax from './faxWhite.svg';
require('bootstrap');
class Footer extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<footer className="footer">
<div className="container-fluid borderTop1px footerLayout">
<div className="row">
<div className="col-3">
<address>
<small>
Some text
</small>
</address>
</div>
<div className="col-6 text-center">
<div className="row">
<div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
<small>{Language[this.props.currentLanguage].download}</small>
</div>
<div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
<Link to="/imprint" className="nav-link footerLink"><small>{Language[this.props.currentLanguage].imprint}</small></Link>
</div>
<div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
<Link to="/contact" className="nav-link footerLink"><small>{Language[this.props.currentLanguage].contact}</small></Link>
</div>
<div className="col-12 col-sm-12 col-md-12 col-lg-3 text-center">
<Link to="/termsAndConditions" className="nav-link footerLink"><small>{Language[this.props.currentLanguage].termsAndConditions}</small></Link>
</div>
</div>
</div>
<div className="col-3">
<ul className="list-inline">
<li>
<img src={phone} alt="Anrufen" className="phone"></img> <small><a className="footerLink" href="tel:+49">+49</a></small>
</li>
<li>
<img src={fax} alt="Fax" className="phone"></img> <small><a className="footerLink" href="tel:+49">+49</a></small>
</li>
</ul>
</div>
</div>
</div>
</footer>
);
}
}
export default withRouter(Footer);
What I'm doing wrong? Why it is not working, when I click on a link?
Got it working now.
I needed to change <HashRouter> to <Router>. Then it works fine.
UPDATE:
This solution solves the problem, but then there is a different problem: When I have navigated and refresh the page, then an error (404) is thrown, because there is of course no such a page on the server.
I need to get the HashRouter work.
When you declare your routes in App.js, you should pass the props to the component:
components: props => <Location {...props} <insert other props> />
You should stick to the <Router> solution as having unnecessary hash in the url is ugly.
When I have navigated and refresh the page, then an error (404) is thrown, because there is of course no such a page on the server.
To resolve this, you need to set up a redirect to redirect all requests to the base url for the React app to handle (the url displayed will be preserved).
On Netlify, you can create a _redirects file in your public folder with the content:
/* /index.html 200
On AWS S3, the redirect rules can be set in S3 or CloudFront, see the answers here.
For Google Cloud bucket, see this.
For Github pages, see this.
In your Route component you use component prop to pass the Location component (instead of render or children props available on Route) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function to the component prop, you would create a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. When using an inline function for inline rendering, use the render or the children prop.However in your case it seems you are using it for no reason so you should just pass the component and not an inline function that returns it like so :
const routes = [
{
path: '/',
exact: true,
components: <Home key="home" currentLanguage={this.state.currentLanguage}/>
},
{
path: '/contact',
exact: true,
components: <Location key="locations" currentLanguage={this.state.currentLanguage} country={this.state.countryObject} contacts= {this.state.contacts} onCountryChange={this.handleCountryChange.bind(this)} />
},
]
Make your routes use Component as below
import {IndexRoute, Route} from 'react-router';
<Route component={App}>
<Route path='/locations' component={LocationComponent}/>
</Route>
This is what I am doing in my current project without using HashRouter.
Currently, When you do
<Route key={index} path={route.path} exact={route.exact} component={route.components} />
I don't think {route.components} treats it as a component.
Could be a problem with withRouter().
Have you seen this?
https://github.com/ReactTraining/react-router/issues/5037
I am using the twitter embedded timeline on my application to show the twitter feed of a companies twitter account.
When I get a result from an API, the state changes to the correct values, but the twitter widget does not appear to change
TwitterCard.js
import React from "react";
export class TwitterCard extends React.Component{
render() {
return (
<a className="twitter-timeline" href={this.props.href} data-height="100%">Tweets by {this.props.ticker}</a>
)
}
}
I have attached screenshots of the react plugin output for proof of state change.
The parent class is rather large, so I have posted the render method:
render() {
return (
<div>
<NavBar/>
<div className="container-fluid">
<div className="row">
<NavBarSide clickHandler={(url) => this.handleNavClick(url)}/>
<Dashboard
errorChart={this.state.errorChart}
twitter={this.state.twitter}
status={this.state.status}
/>
</div>
</div>
</div>
)
}
The parent passes these values to the Dashboard:
import React from "react";
import { Switch, Route } from 'react-router-dom';
import { Chart } from "./Chart";
import { TwitterCard } from "./TwitterCard";
export class Dashboard extends React.Component {
render() {
return (
<div className="col-md-9 ml-sm-auto col-lg-10 pt-2 px-3">
<div className="row">
<div className="col-lg-8">
<div className="row">
<div className="col-lg-6">
<div className="card border-0">
<div className="card-body">
<Chart chart={this.props.errorChart}/>
</div>
</div>
</div>
</div>
</div>
<div className="col-lg-4">
<TwitterCard href={this.props.twitter} ticker={this.props.ticker}/>
</div>
</div>
</div>
)
}
}
You can force an update to the twitter component when the ticket changes by keying it with the ticker where you render it in your dashboard component:
<TwitterCard
key={this.props.ticker}
href={this.props.twitter}
ticker={this.props.ticker}
/>
I have a simple app with a home screen, a blog master list, and then blog detail page. Navigating from the home screen to the blog list and back works as expected. When I click on one of the blog posts the app navigates to the page and renders the blog post. But when I go back to the blog master list the blog post is now at the very bottom of the blog list. And if I then navigate back to the home page the same thing, the blog post is in the DOM behind the home page graphic.
I'm using react router 4.1.
Does it have to do with my route setup?
import React, { Component } from 'react'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import NavBar from './components/NavBar'
import Blog from './pages/blog'
import Home from './pages/home'
import Post from './pages/post'
import './css/App.css'
class App extends Component {
render() {
return (
<Router>
<div className="App">
<NavBar />
<Route exact path='/' component={Home} />
<Route path='/blog' component={Blog} />
<Route path='/post/:slug' component={Post} />
</div>
</Router>
)
}
}
export default App
Or with my post component?
import React from 'react'
import fetch from 'isomorphic-unfetch'
import NavBar from '../components/NavBar'
import dateUtils from '../utilities/DateAndTime'
const classNames = require('classnames')
class Post extends React.Component {
constructor(props) {
super(props)
this.state = {
blogObject: null,
fetchBlogObject: false
}
this.fetchBlogObject = this.fetchBlogObject.bind(this)
this.createMarkup = this.createMarkup.bind(this)
this.assignStyleToCoverImage = this.assignStyleToCoverImage.bind(this)
this.formatDateString = this.formatDateString.bind(this)
}
componentWillMount() {
this.fetchBlogObject()
}
componentWillReceiveProps() {
this.fetchBlogObject()
}
fetchBlogObject() {
console.log('fetching')
this.setState({ fetchBlogObject: true })
fetch(`*******************`)
.then(response => response.json())
.then((responseJson) => {
console.log(responseJson)
this.setState({ blogObject: responseJson.object })
})
}
createMarkup(stringToConvertToHtml) {
return { __html: stringToConvertToHtml }
}
assignStyleToCoverImage() {
const style = {
background: `url(${this.state.blogObject.metadata.hero.url})`,
height: '35vh',
backgroundSize: 'cover',
backgroundPosition: 'center'
}
return style
}
formatDateString(dateString) {
console.log(dateString)
const d = `${dateUtils.returnMonthFromISODateString(dateString)} ${dateUtils.returnDateNumberFromISOString(dateString)}, ${dateUtils.returnFullYearFromISOString(dateString)} by Phil Andrews`
return d
}
render() {
const postContainerClasses = classNames({
'post-container': true
})
const postBodyClasses = classNames({
'post-body': true
})
return (
<div>
<NavBar />
{this.state.blogObject ?
<div className={postContainerClasses} >
<div className='header-image' style={this.assignStyleToCoverImage(this.state.blogObject)} />
<div className='post-body-padding'>
<div className='post-header-info'>
<h1 className='post-title'>{this.state.blogObject.title}</h1>
<h4 className='post-date'>{this.formatDateString(this.state.blogObject.created_at)}</h4>
</div>
<div className={postBodyClasses} dangerouslySetInnerHTML={this.createMarkup(this.state.blogObject.content)} />
</div>
</div>
: <div>Loading...</div>
}
</div>
)
}
}
export default Post
Or with my blog list button action for navigating to a post page?
<Link id={this.props.blogObject.slug} to={`/post/${this.props.blogObject.slug}`}>
<button className='blog-button' style={buttonStyle} id={this.props.blogObject.slug} />
</Link>
I'm not sure why it would continue to render the entire component but if I get rid of the <NavBar /> in the post component the issue goes away.
Not working:
return (
<div>
<NavBar />
{this.state.blogObject ?
<div className={postContainerClasses} >
<div className='header-image' style={this.assignStyleToCoverImage(this.state.blogObject)} />
<div className='post-body-padding'>
<div className='post-header-info'>
<h1 className='post-title'>{this.state.blogObject.title}</h1>
<h4 className='post-date'>{this.formatDateString(this.state.blogObject.created_at)}</h4>
</div>
<div className={postBodyClasses} dangerouslySetInnerHTML={this.createMarkup(this.state.blogObject.content)} />
</div>
</div>
: <div>Loading...</div>
}
</div>
)
Working:
return (
<div>
{this.state.blogObject ?
<div className={postContainerClasses} >
<div className='header-image' style={this.assignStyleToCoverImage(this.state.blogObject)} />
<div className='post-body-padding'>
<div className='post-header-info'>
<h1 className='post-title'>{this.state.blogObject.title}</h1>
<h4 className='post-date'>{this.formatDateString(this.state.blogObject.created_at)}</h4>
</div>
<div className={postBodyClasses} dangerouslySetInnerHTML={this.createMarkup(this.state.blogObject.content)} />
</div>
</div>
: <div>Loading...</div>
}
</div>
)