I've got react-toastify element in my App.js component implemented this way:
class App extends Component {
componentDidUpdate(prevProps) {
const { toast } = this.props
if(toast.id !== prevProps.toast.id) {
this.notify(toast)
}
}
notify = (data) => {
switch(data.type) {
case TOAST.TYPE.ERROR:
...
return toast.show()
}
}
render() {
return (
<BrowserRouter>
<div className="app">
<Switch>
<Route path={ getRoutePath('password.set') } component={ PasswordSet } />
<Route path={ getRoutePath('password.reset') } component={ PasswordReset } />
<Route path={ getRoutePath('login') } component={ LoginSection } />
<Route path={ getRoutePath('home') } component={ AuthenticatedSection } />
</Switch>
<ToastContainer
className="custom-toastify"
autoClose={ 5000 }
hideProgressBar={ true }
closeButton={ <CloseButton /> }
/>
</div>
</BrowserRouter>
)
}
}
function mapStateToProps({ toast }) {
return { toast }
}
Now consider the following scenario: I've got a UsersAdmin PureComponent inside AuthenticatedSection where you can enable/disable users. When you click on enable/disable button, the UsersAdmin component re-renders because of users redux state change and then it also re-renders, because I'm showing toast on success/error api call.
toggleUsersDisabled = (user) => () => {
const { modifyUser, showToast } = this.props
modifyUser(user.id, {
disabled: user.disabled === 0 ? 1 : 0
}).then((response) => {
showToast(`${response.value.name} has been ${response.value.disabled ? 'disabled' : 'enabled'}`)
}).catch(_noop)
}
The showToast dispatches new message to redux state for toasts. Is it possible to somehow prevent re-rending of child components when the toast is shown?
Edit:
added UsersAdmin redux connection including selector
// users selector
import { createSelector } from 'reselect'
const getUsers = state => state.users.get('data')
const getIsFulfilled = state => state.users.get('isFulfilled')
export const getFulfilledUsers = createSelector(
[getUsers, getIsFulfilled],
users => users
)
// UsersAdmin
const mapStateToProps = (state) => {
return {
users: getFulfilledUsers(state)
}
}
UsersAdmin.propTypes = {
users: PropTypes.object.isRequired,
fetchUsersList: PropTypes.func.isRequired,
modifyUser: PropTypes.func.isRequired,
deleteUser: PropTypes.func.isRequired,
showToast: PropTypes.func.isRequired
}
export default connect(mapStateToProps, { fetchUsersList, modifyUser, deleteUser, showToast })(UsersAdmin)
I don't really get why you added all the toasts login inside your App.js
if you look over the docs in:
https://github.com/fkhadra/react-toastify#installation
the only thing you need to do is adding <ToastContainer /> to your app and you are done, exactly like you have in your example.
Now for calling toasts you just import:
import { toast } from 'react-toastify';
in to any component you like in the system, and now you just run the toast function and you got yourself a toast.
ie:
import { Component } from 'react';
import { toast } from 'react-toastify';
class SomeComponent extends Component {
showToast() {
toast('you now see a toast');
}
render() {
return <button onClick={()=>this.showToast()}>toast it</button>;
}
}
Related
I'm learning to use React JS.
I have the following page.
Home
Login
Note
Create Note
My case is as follows.
Home can be accessed without logging in
Note and create notes cannot be accessed without logging in
How to make the case above work?
Here's the code snippet I made:
index.js
import App from "./App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<BrowserRouter> // from "react-router-dom"
<App />
</BrowserRouter>,
document.getElementById("root")
);
serviceWorker.unregister();
App.js as entry home page
import React, { Component } from "react";
import AuthService from "./services/auth.service";
import Routes from "./config/routes";
// Lot of import bootstrap dan font-awesome and css
class App extends Component {
constructor(props) {
super(props);
this.logOut = this.logOut.bind(this);
this.state = {
currentUser: undefined,
backendSupportInformation: undefined,
};
}
componentDidMount() {
const user = AuthService.getCurrentUser();
if (user) {
this.setState({
currentUser: user,
backendSupportInformation: user.backend,
});
}
}
logOut() {
AuthService.logout();
}
render() {
const { currentUser, backendSupportInformation } = this.state;
return (
<div>
<header>
<nav className="navbar navbar-expand-sm navbar-dark bg-dark">
// some of link here
</nav>
</header>
<main role="main" className="container-fluid mt-3">
<Routes /> // use react-route-dom
</main>
</div>
);
}
}
export default App;
Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
const Routes = () => {
return (
<Switch>
<Route exact path={["/", "/home"]} component={Home} />
<Route exact path="/login" component={Login} />
<Route exact path="/note" component={Note} />
<Route exact path="/note/create" component={NoteCreate} />
<Route exact path="/profile" component={Profile} />
</Switch>
);
};
export default Routes;
Now i am doing in NoteComponent like this.
NoteComponent
export default class Note extends Component {
state = {
redirect: null,
userReady: false,
};
componentDidMount() {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) this.setState({ redirect: "/home" });
this.setState({ currentUser: currentUser, userReady: true });
this.retrieveAll();
}
render() {
if (this.state.redirect) {
// pass message that you need login first to access this note page
return <Redirect to={this.state.redirect} />;
}
}
I dont want to repeat my self into NoteCreate Component?
Any advice it so appreciated.
Just as a note to start, not sure which resources you're using to learn React, but as of now I would highly recommend you look into a modern course which teaches React with Hooks, aside from to get error boundaries (which with react-error-boundary) there is no reason to be writing class components.
Regarding the issue at hand, you didn't specifically mention any errors so this seems to be a question of "how should I go about this" as opposed to actually fixing something? Let me know if theres specific errors and I'll try to adjust my answer to help further.
I would recommend refactoring the logic you have in your Note component into a component of itself, so that you can wrap your routes with it. Store the information for whether they're authenticated into a context, and then wrap your routes with that context provider so you can consume that context in your child components, without duplicating that logic on each page.
You need to create a RouterWithAuth Component and use that instead of using Router directly, something like this:
export default class RouteWithAuth extends Component {
state = {
redirect: null,
userReady: false,
};
componentDidMount() {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) this.setState({ redirect: "/home" });
this.setState({ currentUser: currentUser, userReady: true });
this.retrieveAll();
}
render() {
const { redirect, userReady } = this.state;
if (redirect) {
// pass message that you need login first to access this note page
return <Redirect to={this.state.redirect} />;
} else if (userReady) {
return (
<Route
exact={props.exact}
path={props.path}
component={props.component}
/>
);
} else {
return <div>Loading....</div>;
}
}
}
which a cleaner way of creating RouteWithAuth might be to use React Function Component like this:
export default function RouteWithAuth() {
const [redirect, setRedirect] = useState(null);
const [userReady, setUserReady] = useState(false);
useEffect(() => {
const currentUser = AuthService.getCurrentUser();
if (!currentUser) {
setRedirect("/home");
return;
}
//Do Something with the currentUser such as storing it in redux store or in context for later use cases
setUserReady(true);
}, []);
if (redirect) {
return <Redirect to={redirect} />;
} else if (userReady) {
return (
<Route
exact={props.exact}
path={props.path}
component={props.component}
/>
);
} else {
return <div>Loading....</div>;
}
}
I have two react components, a Layout class and a HomePage class:
HomePage is a component which needs to have a products prop.
HomePage.js
import React, { Component } from 'react';
export class HomePage extends Component {
render() {
if (!this.props.products) {
return (<div>Products not loaded yet</div>);
}
return (<div>Products loaded!</div>);
}
}
Layout is a component that displays children coming from routes established with react-router.
This class is in charge to pass the products prop to children using React.cloneElement
Layout.js
import React, { Component } from 'react';
import { NavMenu } from './NavMenu';
import { Footer } from './Footer';
export class Layout extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
// Make an api call when the component is mounted in order to pass
// additional props to the children
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading) {
return (<div>App loading</div>);
}
const childrenWithProps = React.Children.map(this.props.children, child => {
const props = { products: this.state.products };
if (React.isValidElement(child)) {
return React.cloneElement(child, props);
}
return child;
});
return (
<div>
<NavMenu />
{childrenWithProps}
<Footer />
</div>
);
}
}
The routing is made in an App component:
App.js
export default class App extends Component {
render () {
return (
<Layout>
<Route exact path='/'
component={HomePage}/>
</Layout>
);
}
Hence, I am expecting to
Have a page with the App loading message while the API call hasn't been made
Have a page with the Products not loaded yet message while the prop hasn't been passed to the Layout children
Have a page with the Products loaded! message
However, the application is stuck at step two: the products prop is never received by the children components. The code compiles, there are no runtime errors, and the back-end Api is triggered and sends a valid response.
Why the product props will never be available in the render() method of the child HomePage component?
EDIT:
Following #Nikita Chayka's answer, the props should be passed at routing:
Layout.js
export class Layout extends Component {
render() {
return (
<div>
<NavMenu />
{this.props.children}
<Footer />
</div>
);
}
}
App.js
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
products: null,
loading: true
};
}
componentDidMount() {
this.populateProductsData();
}
async populateProductsData() {
const response = await fetch('/api/products/all');
const data = await response.json();
this.setState({ products: data, loading: false });
}
render() {
if (this.state.loading)
return (<div>App loading</div>);
return (
<Layout>
<Route exact path='/'
render={(props) => (<HomePage {...props} products={this.state.products}/>)}/>
</Layout>
);
}
}
Your Layout component will pass products prop to Route component, not Home component, basically you will have
<Route products={} component={Home} path="/" exact/>
But you need to pass it down to Home, you could check for ideas here - https://ui.dev/react-router-v4-pass-props-to-components/
EDIT
You should not provide component property to Route, only render.
I wanted to set state for logged in user if user not logged in then navigate is true by this.setstate() method. but problem its showing error at console and not working the process here is error bellow
0.chunk.js:219252 Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.
in Header (at App.js:9)
in App (at src/index.js:17)
in ErrorBoundary (at src/index.js:16)
in Router (created by BrowserRouter)
in BrowserRouter (at src/index.js:15)
Here bellow my app.js code
import React from 'react';
import './App.scss';
import Header from './components/Header/Header';
import Sidebar from './components/Sidebar/Sidebar';
import Routes from './routes/Routes';
function App() {
return (
<>
<Header />
<Sidebar />
<Routes />
</>
);
}
export default App;
Signin.js code
export default class SignIn extends Component {
constructor(props) {
super(props)
this.state = {
redirect: false,
};
}
}
render() {
if (this.state.redirect) {
return <Redirect to="/" />;
}
}
onSignInHandler = () => {
.......
this.setState({
....
redirect: true,
});
}
At Header.js where logout button have. I wanted to give this link onclick() handler. here below are code
export default class Header extends Component {
constructor(props) {
super(props);
this.state = {
navigate: false
}
}
onLogoutHandler = () => {
....
this.setState({
navigate: true,
});
};
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
return(<header></header>)
}
}
At header.js I have logout link which will trigger the onLogoutHandler and state will then will redirect to signin page. please help me for this issue
Please check if this works
onLogoutHandler = async () => {
....
await this.setState({
navigate: true,
});
};
You should have return statement at the end of your render() method from your class component
render() {
const { navigate } = this.state;
if (navigate) {
return <Redirect to="/signin" push={true} />;
}
// you should also have return statement in this line here
// return something like
return null;
}
I have written below code,
1.I want to use Connect for storing usernamein local storage
2.I am using HOC component for logging purpose (callInfoLogger and callErrorLogger)
3.If I use connect and HOC together then this.props.history.push is not working (Its not redirecting to MyDashboard page)
Could you please let me know what do I need to do to fix the code?
App.js
import { BrowserRouter as Router, Route, Switch, } from "react-router-dom";
class App extends Component {
render() {
return (
<Router>
<Switch>
<Route path="/login" component={Login} />
<Route path="/dashboard" component={MyDashboard} />
</Switch>
</Router>
)
}
}
export default App;
Login.js
import React, { Component } from 'react';
import { withRouter } from "react-router-dom";
import { connect } from 'react-redux';
import HighLevelComponent from './HighLevelComponent';
class Login extends Component {
state = {
username: '',
password: '',
loginsuccess: true
}
callOnSubmit = (e) => {
e.preventDefault();
this.props.callErrorLogger("Inside call on Submit");
if (this.state.loginsuccess === true) {
this.props.callInfoLogger("Calling Info logger ");
this.props.onLoginSuccess(this.state.username);
this.props.history.push('/dashboard');
}
};
render() {
return (
<body>
<form className="login-form" onSubmit={this.callOnSubmit}>
<input
type="text" onChange={e => {
this.setState({
...this.state,
username: e.target.value
})
}}
/>
<input type="password"
onChange={e => {
this.setState({
...this.state,
password: e.target.value
})
}}
/>
<input type="submit" className="btnSbumit" value="LOG IN" />
</form>
</body>
)
}
}
const mapDispatchToProps = dispatch => {
return {
onLoginSuccess: (username) => dispatch({ type: "LOGIN_SUCCESS", username: username })
}
}
export default withRouter(HighLevelComponent(connect(null, mapDispatchToProps)(Login)));
MyDashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class MyDashboard extends Component {
render() {
return (
<body>
<h1>Welcome to React.. {this.props.username}</h1>
</body>
)
}
}
const mapStateToProps = state => {
return {
username: state.username
}
}
export default connect(mapStateToProps, null)(MyDashboard);
HighLevelComponent.js
import React from 'react';
const HighLevelComponent = (WrapperComponent) => {
class NewComponent extends React.Component {
callInfoLogger = (infomsg) => {
console.info(infomsg);
}
callErrorLogger = (errmsg) => {
console.error(errmsg);
}
render() {
return <WrapperComponent callInfoLogger={this.callInfoLogger} callErrorLogger={this.callErrorLogger} />
}
}
return NewComponent;
}
export default HighLevelComponent;
In the HOC names HighLevelComponent pass the props to the wrapper component as follows:
const HighLevelComponent = (WrapperComponent) => {
class NewComponent extends React.Component {
callInfoLogger = (infomsg) => {
console.info(infomsg);
}
callErrorLogger = (errmsg) => {
console.error(errmsg);
}
render() {
return <WrapperComponent callInfoLogger={this.callInfoLogger} callErrorLogger={this.callErrorLogger} {...props} />
}
}
return NewComponent;
}
Please note the {...props} on the wrapper component. In this way all the props will be further passed.
I am trying to create a PrivateRoute(HOC) to test if a user has been authenticated(check is 'auth' exist in redux store) before sending them to the actual route. The issue is the privateroute finishes before my auth shows up in redux store.
The console.log runs twice, the first time, auth doesnt appear in the store, but it does the second time, but by that time, its already routed the user to the login screen.... How can I give enough time for the fetch to finish? I know how to do this condition when I simply want to display something conditionally(like login/logout buttons) but this same approach does not work when trying to conditionally route someone.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route } from 'react-router-dom'
class PrivateRoute extends Component {
render() {
const { component: Component, ...rest } = this.props
console.log(this.props)
return (
<Route {...rest} render={(props) => (props.auth ? <Component {...props} /> : props.history.push('/login'))} />
)
}
}
function mapStateToProps({ auth }) {
return { auth }
}
export default connect(mapStateToProps)(PrivateRoute)
I didn't use redux here, but I think you would get the main point. Hope this will help and feel free to ask any questions!
import React, { Component } from "react";
import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom";
import Dashboard from "path/to/pages/Dashboard";
class App extends Component {
state = {
isLoggedIn: null,
};
componentDidMount () {
// to survive F5
// when page is refreshed all your in-memory stuff
// is gone
this.setState({ isLoggedIn: !!localStorage.getItem("sessionID") });
}
render () {
return (
<BrowserRouter>
<Switch>
<PrivateRoute
path="/dashboard"
component={Dashboard}
isLoggedIn={this.state.isLoggedIn}
/>
<Route path="/login" component={Login} />
{/* if no url was matched -> goto login page */}
<Redirect to="/login" />
</Switch>
</BrowserRouter>
);
}
}
class PrivateRoute extends Component {
render () {
const { component: Component, isLoggedIn, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
isLoggedIn ? <Component {...props} /> : <Redirect to="/login" />
}
/>
);
}
}
class Login extends Component {
state = {
login: "",
password: "",
sessionID: null,
};
componentDidMount () {
localStorage.removeItem("sessionID");
}
handleFormSubmit = () => {
fetch({
url: "/my-app/auth",
method: "post",
body: JSON.strigify(this.state),
})
.then(response => response.json())
.then(data => {
localStorage.setItem("sessionID", data.ID);
this.setState({ sessionID: data.ID });
})
.catch(e => {
// error handling stuff
});
};
render () {
const { sessionID } = this.state;
if (sessionID) {
return <Redirect to="/" />;
}
return <div>{/* login form with it's logic */}</div>;
}
}
When your action creator return the token, you need to store it in localStorage. and then you can createstore like below,
const store = createStore(
reducers,
{ auth: { authenticated : localStorage.getItem('token') }},
applyMiddleware(reduxThunk)
)
if user already logged in then token will be there. and initial state will set the token in store so you no need to call any action creator.
Now you need to secure your components by checking if user is logged in or not. Here's the HOC for do that,
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default ChildComponent => {
class ComposedComponent extends Component {
componentDidMount() {
this.shouldNavigateAway();
}
componentDidUpdate() {
this.shouldNavigateAway();
}
shouldNavigateAway() {
if (!this.props.auth) {
this.props.history.push('/');
}
}
render() {
return <ChildComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { auth: state.auth.authenticated };
}
return connect(mapStateToProps)(ComposedComponent);
};