I am trying to add a loader icon to my site but it is constantly showing should i put it in a component and import it? The state is set to true as the page is loading when it refreshes maybe its something with this.
This is my app.js file
import React from "react";
import { createBrowserHistory } from "history";
import { Router, Route, Switch } from "react-router-dom";
import "assets/scss/material-kit-react.scss?v=1.4.0";
// pages for this product
import LandingPage from "views/LandingPage/LandingPage.jsx";
import ProfilePage from "views/ProfilePage/ProfilePage.jsx";
import { css } from "#emotion/core";
// First way to import
import { ClipLoader } from "react-spinners";
const override = css`
display: block;
margin: 0 auto;
border-color: red;
`;
var hist = createBrowserHistory();
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true
};
}
render() {
return (
<>
<ClipLoader
css={override}
sizeUnit={"px"}
size={150}
color={"#123abc"}
loading={this.state.loading}
/>
<Router history={hist}>
<Switch>
<Route path="/profile-page" component={ProfilePage} />
<Route path="/" component={LandingPage} />
</Switch>
</Router>
</>
);
}
}
your default state is set to true. so it will show the loading once the component renders
this.state = {
loading: false
};
You can change your Loader to a HOC
import React from 'react';
import { ClipLoader } from "react-spinners";
function Loading(Component) {
return function WihLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return (<Component {...props} />);
return (<ClipLoader
css={override}
sizeUnit={"px"}
size={150}
color={"#123abc"}
loading={isLoading}
/>);
}
}
export default Loading;
Then you can use you global context or redux store to update the state of isLoading
render() {
return (
<Loading isLoading={this.props.isLoading} data={data} />
)
}
Related
I have recently experienced some issues with my react router and redux. Basically, I have a redux value set which let's me know if an item is selected. If the item is selected, then it will allow a URL to be used. One thing that I have noticed. If I add a redirect function. It breaks everything
Authentication function:
import React, { Component, Fragment } from "react";
import { Provider } from "react-redux";
// import store from "./store";
import {
BrowserRouter as Router,
Switch,
Route,
Redirect
} from "react-router-dom";
import { connect } from "react-redux";
import Profile from "./Profile";
import AddDomain from "./AddDomain";
import ChoosePackage from "./ChoosePackage";
import DashboardHome from "./DashboardHome";
import { Elements } from "#stripe/react-stripe-js";
import PropTypes from "prop-types";
import { loadStripe } from "#stripe/stripe-js";
const stripePromise = loadStripe("pk_test_7S0QSNizCdsJdm9yYEoRKSul00z4Pl6qK6");
class index extends Component {
componentDidMount() {
console.log("DOMAIN NAME" + this.props.domain_name);
}
state = {
domain_name: ""
};
static propTypes = {
domain_name: PropTypes.string.isRequired
};
domainCheck = () => {
if (this.props.domain_name != "") {
return <ChoosePackage />;
} else {
console.log("running rediect");
return <Redirect to="/dashboard" />;
}
};
render() {
return (
<React.Fragment>
<Route path="/dashboard/add-domain/choose-package">
{this.domainCheck()}
</Route>
<Route exact path="/dashboard/add-domain">
<AddDomain />
</Route>
<Route exact path="/dashboard/profile">
<Profile />
</Route>
<Route exact path="/dashboard">
<DashboardHome />
</Route>
</React.Fragment>
);
}
}
const mapStateToProps = state => ({
domain_name: state.domain.domain_name
});
index.defaultProps = {
domain_name: ""
};
export default connect(mapStateToProps, { pure: false })(index);
Any help is greatly appreciated
I have a LoadingProvider where I set the state of my Loading component to false and when needed to true. I want to show my Loading component only when the state of loading equals to true.
All my providers, router and app components are loaded in my root.js:
import React, { Component } from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { MuiPickersUtilsProvider } from "material-ui-pickers";
import MomentUtils from "#date-io/moment";
import App from "./App";
import { DeleteDialogProvider } from "/hocs/withDeleteDialog";
import { WarningDialogProvider } from "/hocs/withWarningDialog";
import { LoadingProvider } from "/hocs/withLoading";
import { MuiThemeProvider, createMuiTheme } from "#material-ui/core/styles";
import { StateProvider } from "/hocs/withState";
import { I18nProvider } from "/hocs/withI18n";
const theme = createMuiTheme({});
class Root extends Component {
render() {
return (
<MuiThemeProvider theme={theme}>
<MuiPickersUtilsProvider utils={MomentUtils}>
<I18nProvider>
<DeleteDialogProvider>
<WarningDialogProvider>
<StateProvider>
<Router>
<LoadingProvider>
<App />
</LoadingProvider>
</Router>
</StateProvider>
</WarningDialogProvider>
</DeleteDialogProvider>
</I18nProvider>
</MuiPickersUtilsProvider>
</MuiThemeProvider>
);
}
}
export default Root;
My other providers don't block any other components from rendering. But when I add the LoadingProvider in root.js and check the console with the React Developer Tools I see it doesn't load/render the components that comes after my LoadingProvider component. The problem is that I don't know why it doesn't render any other components.
This is my withLoading file where I define the LoadingProvider:
import React, { Component } from "react";
import Loading from "/components/Loading";
const LoadingContext = React.createContext();
export class LoadingProvider extends Component {
constructor(props) {
super(props);
this.state = {
loading: false
};
}
setLoadingContext = e => {
this.setState({
loading: true
});
};
render() {
return (
<LoadingContext.Provider value={this.setLoadingContext}>
<Loading
loading={this.state.loading}
/>
</LoadingContext.Provider>
);
}
}
export const withLoading = Component => props => (
<LoadingContext.Consumer>
{setLoadingContext => (
<Component {...props} setLoadingContext={setLoadingContext} />
)}
</LoadingContext.Consumer>
)
And this is my Loading.js file where I define my Loading component:
import React, { Component } from 'react';
import CircularProgress from '#material-ui/core/CircularProgress';
class Loading extends Component {
render() {
const loading = this.props;
// TODO: fix weird repetitive loading prop
if (!loading.loading) {
return null;
} else {
return (
<CircularProgress />
);
}
}
}
export default Loading;
I guess it has something to do with returning null when loading is false. But when I comment that rule of code out it says:
Uncaught Invariant Violation: Loading(...): Nothing was returned from
render. This usually means a return statement is missing. Or, to
render nothing, return null.
This is primarily because in your LoadingProvider you are not using props.children.
<LoadingContext.Provider value={this.setLoadingContext}>
<Loading
loading={this.state.loading}
/>
{this.props.children} // add this
</LoadingContext.Provider>
Take note that null don't render anything.
Your <App/> is passed to LoadingProvider in its children property. But LoadingProvider doesn't do anything with its children, so nothing happens.
So return this.props.children when you want them to render.
I have a static component called Item.js
Routes.js
export default () => (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/posts" component={Posts} />
<Route path="/form" component={Postform} />
<Route exact path="/" component={App} />
<Route path="/items" component={Items} />
<Route path="/cart" component={Cart} />
<Route path="/page/:id" component={Page} />
</Switch>
</BrowserRouter>
</Provider>
);
In the above page component, I want to load item.js or any other page depending on whats passed to the URL params in as "id" in the page component.
import React, { Component } from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
class Page extends Component {
componentDidMount() {
const { id } = this.props.match.params;
console.log(id);
}
render() {
return (
<div>
<Navbar />
<div>Hello</div>
</div>
);
}
}
export default Page;
How do I achieve this? I don't know.
Are there any alternative ways of doing it?
Ok, I solved it by following a variation of Johnny Peter's answer.
Page.js
import React, { Component } from 'react'
import Navbar from './Navbar';
import components from './indexPage'
class Page extends Component {
render() {
const { id } = this.props.match.params;
const PageComponent = components.find(comp => comp.id === id).component;
return (
<div>
<Navbar />
<PageComponent/>
</div>
);
}
}
export default Page;
indexPage.js
import Item from './pages/Item'
import Meow from './pages/Meow'
const components = [{
id: 'item',
component: Item
},
{
id: 'meow',
component: Meow
}
]
export default components;
Something like this should work
import React from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
const components = [{
id: 'your/id/passed/in/param'
component: Item
}]
class Page extends React.Component {
state = {
Component: null,
}
componentDidMount() {
const { id } = this.props.match.params;
this.setState({ Component: components.find(comp => comp.id === id).component })
}
render() {
const { Component } = this.state;
return (
<div>
<Navbar />
<Component />
</div>
);
}
}
export default Page;
You can create an index file like so:
index.js:
import Item from './item';
const pages = {
pageId: Item,
};
export default pages;
And in page component:
import React, { Component } from 'react';
import Navbar from './Navbar';
import Item from './pages/Item';
import pages from './pages';
class Page extends Component {
componentDidMount() {
const { id } = this.props.match.params;
console.log(id);
}
render() {
const { id } = this.props.match.params;
if (pages[id]) {
return pages[id];
}
return (
<div>
<Navbar />
<div>Hello</div>
</div>
);
}
}
export default Page;
in order to dynamically load component based on id.
I'm new to React and I've made a <Link>to go to next or previous item from dy datas(for example, if i am on user/2 view, previous link go to user/1 and next link go to user/3), the url is correctly changed but the component is not rendered at all and the datas are not reloaded at all.
I've read that it's due to the component not detecting that the children is not changing state so the parent component does not render.
I've tried to use withRouter but I've got a error : You should not use <Route> or withRouter() outside a <Router> and I'm not understanding what I'm doing so if someone has the solution and some explanation to it I would be grateful :)
App.js :
import React, { Component } from 'react';
import {
Route,
Switch,
withRouter,
} from 'react-router-dom';
import HomePage from './pages/home';
import SinglePage from './pages/single';
class App extends Component {
render() {
return (
<Switch>
<div>
<Route exact path="/" component={HomePage} />
<Route path="/:id" component={SinglePage} />
</div>
</Switch>
);
}
}
export default withRouter(App);
Single.js :
import React, { Component } from 'react';
import Details from '../components/details'
import Header from '../components/header'
import { ProgressBar } from 'react-materialize';
class SinglePage extends Component {
constructor(props) {
super(props);
this.state = {
data: { data: null },
}
}
componentDidMount() {
fetch(`http://localhost:1337/${this.props.match.params.id}`)
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
render() {
const { data } = this.state;
return (
<div>
<h2> SinglePage </h2>
{!data ? (
<ProgressBar />
) : (
<div>
<Header id={this.props.match.params.id} />
<Details item={data} />
</div>
)}
</div>
);
}
}
export default SinglePage;
Header.js :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, withRouter } from 'react-router-dom';
class Header extends Component {
static propTypes = {
item: PropTypes.shape({
data: PropTypes.string.isRequired,
}).isRequired,
}
render() {
const prev = parseInt(this.props.id) - 1
const next = parseInt(this.props.id) + 1
return (
<div>
<Link to="/"> Retour </Link>
<Link to={`/${prev}`}> Précédent </Link>
<Link to={`/${next}`}> Suivant </Link>
</div>
)
}
}
export default Header;
the solution is pretty-simple. All you need to do is make use of componentWillReceiveProps and check if the param updated, if it did fetch the data again
componentDidMount() {
this.getData(this.props.match.params.id);
}
componentWillReceiveProps(nextProps) {
if(this.props.match.params.id !== nextProps.match.params.id) {
this.getData(nextProps.match.params.id);
}
}
getData = (param) => {
fetch(`http://localhost:1337/${params}`)
.then((res) => res.json())
.then((json) => {
this.setState({
data: json,
});
});
}
I am trying to use react-router but I am not able to propagate children components.
index.js
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
import App from './App';
import Login from './containers/Login';
const rootElement = document.getElementById('app');
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
</Route>
</Router>
), rootElement);
App.js
import React, { Component, PropTypes } from 'react';
import { Login } from './containers';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
const { children } = this.props;
return (
<div className="content">
{children}
</div>
);
}
}
App.propTypes = {
children: PropTypes.any,
};
LoginPage.js
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Login } from '../components';
export default class LoginPage extends Component {
constructor(props, context) {
super(props, context);
}
handleSubmit(event) {
event.preventDefault();
}
render() {
const { handleSubmit, redirect } = this.props;
return (
<Login handleSubmit={handleSubmit}
redirect={redirect}
/>
);
}
}
LoginComponent.js
import React, { Component, PropTypes } from 'react';
export default class Login extends Component {
constructor(props, context) {
super(props, context);
this.state = {
email: '',
password: '',
};
}
handleChange(field, event) {
const nextState = this.state;
nextState[field] = event.target.value;
this.setState(nextState);
}
handleSubmit(event) {
event.preventDefault();
this.props.handleSubmit(this.state);
}
render() {
return (
<form onSubmit={event => this.handleSubmit(event)}>
<input
type="text" placeholder="Email"
value={this.state.email}
onChange={this.handleChange.bind(this, 'email')}
/>
<input
type="password" placeholder="Password"
value={this.state.password}
onChange={this.handleChange.bind(this, 'password')}
/>
<input type="submit" value="Submit"/>
</form>
);
}
}
Login.propTypes = {
handleSubmit: PropTypes.func.isRequired,
};
If I just import LoginPage directly into App.js where I try to render {children} it works perfectly fine. On inspection it simply says children is undefined
react#0.14.6
react-dom#0.14.6
react-router#2.0.0-rc5
As a side note, I ran npm list react-router and I got this back
`-- (empty)
npm ERR! code 1
Any help would be great!!
Edit: I edited the first code snippet to be import Login from './containers/Login'; from import { Login } from './containers/Login';
That was a type from simplifying the problem. I had it the other way originally because I am actually using an index.js for containers and was calling import { Login } from './containers';
I have stepped through the code and it shows that Login is NOT undefined in index.js but children is when I get to App.js
Below is a screenshot of a breakpoint in index.js and App.js in the same run. index.js shows Login as being initialized but then children is undefined.
[
Okay I have simplified the whole thing as much as possible now into a single file and it still doesn't work
import 'babel-polyfill';
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
class App extends Component {
constructor(props) {
super(props);
}
render() {
const { children } = this.props;
return (
<div className="content">
{children}
</div>
);
}
}
class Child extends Component {
render() {
return (
<p>I am a child</p>
);
}
}
const rootElement = document.getElementById('app');
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="login" component={Child}/>
</Route>
</Router>
), rootElement);
I then ran it and got the following
Then I added <Child /> directly into the render property of App and got this
So this is not a problem with how I am importing files etc.
The solution is quite simple. Replace
import { Login } from './containers/Login';
with
import Login from './containers/Login';
in your index.js
The reason why your child property was always 'undefined' is because the passed over component was 'undefined':
If you have questions regarding the import syntax i can recommend this SO Question "using brackets with javascript import syntax"
See full code:
index.js
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';
import App from './App';
import Login from './containers/LoginPage';
const rootElement = document.getElementById('app');
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
</Route>
</Router>
), rootElement);
App.js
import React, { Component, PropTypes } from 'react';
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
const { children } = this.props;
return (
<div className="content">
{children}
</div>
);
}
}
App.propTypes = {
children: PropTypes.any,
};
./containers/LoginPage.js
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import Login from '../components/Login';
export default class LoginPage extends Component {
constructor(props, context) {
super(props, context);
}
handleSubmit(event) {
event.preventDefault();
}
render() {
const { handleSubmit, redirect } = this.props;
return (
<Login handleSubmit={handleSubmit}
redirect={redirect}
/>
);
}
}
./components/Login.js
import React, { Component, PropTypes } from 'react';
export default class Login extends Component {
constructor(props, context) {
super(props, context);
this.state = {
email: '',
password: '',
};
}
handleChange(field, event) {
const nextState = this.state;
nextState[field] = event.target.value;
this.setState(nextState);
}
handleSubmit(event) {
event.preventDefault();
this.props.handleSubmit(this.state);
}
render() {
return (
<form onSubmit={event => this.handleSubmit(event)}>
<input
type="text" placeholder="Email"
value={this.state.email}
onChange={this.handleChange.bind(this, 'email')}
/>
<input
type="password" placeholder="Password"
value={this.state.password}
onChange={this.handleChange.bind(this, 'password')}
/>
<input type="submit" value="Submit"/>
</form>
);
}
}
Login.propTypes = {
handleSubmit: PropTypes.func.isRequired,
};
Proof with react 0.14.6 and react-router 2.0.0-rc5
Ok so answering my own question. Basically a really stupid mistake but maybe someone will benefit. I was using localhost/#/child because I thought this is what it was supposed to say and localhost/child hits an registered route on my server. So the fix was to make my server-side route handler
router.get('/*', (req, res) => {
res.render(view);
});
And then navigate to localhost/child