I'm using Next.js with redux and firebase auth.
What I'm trying to achieve is to check whether a user is logged in before being able to access some private pages.
I have created a component called AdminLayout which is wrapped around every private page. I get the currentUser as props using mapStateToProps but I'm not getting the value in the first run.
// admin-layout.component.jsx
// Imports
class AdminLayout extends React.Component {
componentDidMount() {
console.log(this.props.currentUser);
// if (this.props.currentUser) {
// if (this.props.currentUser.currentUser.role != "admin") {
// Router.push("/admin/login");
// }
// }
// if (!this.props.currentUser) {
// Router.push("/admin/login");
// }
}
render() {
return (
<React.Fragment>
// Irrelevant code here
{this.props.children}
// Irrelevant code here
</React.Fragment>
);
}
}
const mapStateToProps = (state) => ({
currentUser: state.user.currentUser,
});
export default connect(mapStateToProps)(AdminLayout);
Any way to overcome this situation and allow only authenticated users to access the private pages?
Related
I have a small react app. In App.js I have layout Sidenav and Content area. The side nav is shown on some page and hid from others. When I go to some components with sidenav, sidenav flag is set by redux and render the component again, in the componentDidMount I have api call, and it is executed twice.
class App extends Component {
renderSidebar = () => {
const {showNav} = this.props;
return showNav ? (
<TwoColumns>
<Sidenav/>
</TwoColumns>) : null;
};
render() {
const {showNav} = this.props;
const Column = showNav ? TenColumns : FullColumn;
return (
<Row spacing={0}>
{this.renderSidebar()}
<Column>
<Route exact path="/measurements/:id/:token/:locale/measure"
component={MeasurementPage}/>
</Column>
</Row>
)
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(App);
I tried to use shouldComponentUpdate to prevent the second API call
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
props.toggleSidenav(false);
}
shouldComponentUpdate(nextProps) {
return !nextProps.showNav === this.props.showNav;
}
componentDidMount() {
// This is executed twice and made 2 api calls
this.props.getMeasurement(params);
}
render() {
return <h1>Some content here</h1>;
}
}
const mapStateToProps = state => ({
showNav: state.sidenav.showNav
});
export default connect(mapStateToProps)(MeasurementPage);
Did someone struggle from this state update and how manage to solve it?
This props.toggleSidenav(false) might cause side effect to your component lifecycle. We use to do this kind of stuff inside componentWillMount and it has been depreciated/removed for a reason :). I will suggest you move it inside componentDidMount
class MeasurementPage extends Component {
constructor(props) {
// This update the redux "showNav" flag and re-render the component
// props.toggleSidenav(false); // remove this
}
shouldComponentUpdate(nextProps) {
return nextProps.showNav !== this.props.showNav;
}
componentDidMount() {
if(this.props.showNav){ //the if check might not necessary
this.props.toggleSidenav(false);
this.props.getMeasurement(params);
}
}
render() {
return <h1>Some content here</h1>;
}
}
The comparison should be
shouldComponentUpdate(nextProps) {
return !(nextProps.showNav === this.props.showNav)
}
The problem is that !nextProps.showNav negate showNav value instead of negating the role expression value, and that is why you need an isolation operator.
It's No call twice anymore.
componentDidMount() {
if (this.first) return; this.first = true;
this.props.getMeasurement(params);
}
The problem:
I want to have simple boolean flag that will be true when modal is opened and false when it is closed. And I want to update other components reactively depends on that flag
I hope there is a way to do it with relay only (Apollo has a solution for that). I don't want to connect redux of mobx or something like that (It is just simple boolean flag!).
What I already have:
It is possible to use commitLocalUpdate in order to modify your state.
Indeed I was able to create and modify my new flag like that:
class ModalComponent extends PureComponent {
componentDidMount() {
// Here I either create or update value if it exists
commitLocalUpdate(environment, (store) => {
if (!store.get('isModalOpened')) {
store.create('isModalOpened', 'Boolean').setValue(true);
} else {
store.get('isModalOpened').setValue(true);
}
});
}
componentWillUnmount() {
// Here I mark flag as false
commitLocalUpdate(environment, (store) => {
store.get('isModalOpened').setValue(false);
});
}
render() {
// This is just react component so you have full picture
return ReactDOM.createPortal(
<div
className={ styles.modalContainer }
>
dummy modal
</div>,
document.getElementById('modal'),
);
}
}
The challenge:
How to update other components reactively depends on that flag?
I can't fetch my flag like this:
const MyComponent = (props) => {
return (
<QueryRenderer
environment={ environment }
query={ graphql`
query MyComponentQuery {
isModalOpened
}`
} //PROBLEM IS HERE GraphQLParser: Unknown field `isModalOpened` on type `Query`
render={ ({ error, props: data, retry }) => {
return (
<div>
{data.isModalOpened}
<div/>
);
} }
/>);
};
Because Relay compiler throws me an error: GraphQLParser: Unknown field 'isModalOpened' on type 'Query'.
And the last problem:
How to avoid server request?
That information is stored on client side so there is no need for request.
I know there a few maybe similar questions like that and that. But they doesn't ask most difficult part of reactive update and answers are outdated.
If you need to store just one flag as you said, I recommend you to use React Context instead of Relay. You could do next:
Add Context to App component:
const ModalContext = React.createContext('modal');
export class App extends React.Component {
constructor() {
super();
this.state = {
isModalOpened: false
}
}
toggleModal = (value) => {
this.setState({
isModalOpened: value
})
};
getModalContextValue() {
return {
isModalOpened: this.state.isModalOpened,
toggleModal: this.toggleModal
}
}
render() {
return (
<ModalContext.Provider value={this.getModalContextValue()}>
//your child components
</ModalContext.Provider>
)
}
}
Get value from context everywhere you want:
const MyComponent = (props) => {
const { isModalOpened } = useContext(ModalContext);
return (
<div>
{isModalOpened}
</div>
);
};
If you will use this solution you will get rid of using additional libraries such as Relay and server requests.
I have two pages on my webpage. I want the submit button to be disabled until the user completes the first page.
The button is disabled; however, if I try to access the second page using a link the button is enabled and I can access the second page.
Any suggestions?
If you want the user to be unable to access the second page without finishing some task on the first page, you can
Set a key in your redux state that you set only when the user has completed the task and then only render the second page once the key is set. That is, something like
{this.props.taskCompleted === true && <SecondPage />}
If you're using react-router, you can create a sort of protected route like
const PrivateRoute = (props: any) => {
const { component: Component, auth, ...rest } = props;
const renderComponent = (props: any) => {
if (auth.isTaskCompleted) {
return <Component {...props} />;
} else {
return <Redirect to={'auth'} />;
}
};
return <Route {...rest} render={renderComponent} />;
};
const mapStateToProps = (state: any) => ({
auth: state.auth,
});
export default connect(
mapStateToProps,
null,
null,
{ pure: false },
)(PrivateRoute);
and then used like
<PrivateRoute path="/second" component={SecondPage} />
However keep in mind that none of these methods will keep someone determined, since a purely frontend authentication is never secure. You should set it up so that the data for the second page is only sent from the backend server is only sent if a particular key is set in the backend.
you can disable button using react state.
export class Form extends React.Component {
state = {
disableSubmit: true,
}
render() {
return (<button disabled={this.state.disableSubmit}>Submit</button>);
}
}
To disable link you can apply class. check here. https://css-tricks.com/how-to-disable-links/
export class Form extends React.Component {
state = {
disableSubmit: true,
}
render() {
return (<a className={this.state.disableSubmit && "disabledLinkClass"}>Link</a>);
}
}
I am getting username from server and i want to use the same user name from some other component. I know session Storage is one of the way to deal with it but i dont want to use for security reason. How can we create a global object in react?
// most simplistic
window.myAppData = {
userName: 'chad123',
language: 'EN',
// other stuff
};
window.myAppData.userName // 'chad123'
But most apps require something a bit more complex. You could use React context.
https://reactjs.org/docs/context.html
Context provider
// create context provider and consumer
const UserContext = React.createContext();
export default UserContext;
// wrap part of your app (or whole app)
// with Provider that needs access to user
class App extends React.Component {
constructor() {
super();
this.state = {
user: null
};
}
componentDidMount() {
yourUserAPI().then(user => this.setState({ user }));
}
render() {
return (
<UserContext.Provider value={this.state.user}>
<MyComponent />
</UserContext.Provider>
);
}
}
Context consumer
A) Current standard
// use anywhere in your app like this
// PS! must be descendant of Provider
class MyComponent extends React.Component {
render() {
return (
<UserContext.Consumer>
{user => {
// do stuff with 'user'
}}
</UserContext.Consumer>
);
}
}
B) React Hooks (IN ALPHA)
// only works with functional
// components (no classes)
function MyComponent() {
const user = React.useContext(UserContext.Consumer);
// do stuff with 'user'
return 'something';
}
I think to achieve that you need to use "React's context API"
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
Context is designed to share data that can be considered “global” for a tree of React components, such as the current authenticated user, theme, or preferred language.
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
For further info do visit the link React context api
You need a global state management like Redux.
Once you have this setup you can map your global state to your local component props and access it like you do any other prop: this.props.globalUsername.
I recommend you learn Redux by following their example program on the official website https://redux.js.org/basics/exampletodolist
Well you can create a global variable in ReactJS but it doesn't make it more "secure" over Session/Local storage.
I think creating a global variable in React project is not the best practice at all because of this simply reason: Should components track down this variable for any change ? If the answer is yes, what you are looking at should be "How to manage global state in React" not "How to create a Global Variable in React".
You can achieve it with Redux. As official documentation says "Redux is a predictable state container" but you can think it as Global State Container for your app.
You can check redux out from that url: https://redux.js.org/
USE CUSTOM HOOKS
It is very simple
globals.js
let _obj = {}
export const setGlobal = (obj) => {
Object.assign(_obj, obj)
}
export const getGlobal = varName => {
if(_obj[varName] !== undefined){
return _obj[varName]
}
else {
return null
}
}
component1.jsx
import React.....
import { setGlobal } from "./globals";
import.....
setGlobal({ title : "welcome" })
class comp.... {
render{
return(){
<i onClick={()=>setGlobal({location: "House"})}>Cmponent1</i>
}
}
}
module exp...
Component2.jsx
import React.....
import { setGlobal, getGlobal } from "./globals";
import.....
setGlobal({ greet : "Hi"})
class comp.... {
render{
return(){
<i>{getGlobal("greet")}, {getGlobal("title")} to our {getGlobal("location")}</i>
}
}
}
module exp...
I have an app with redux and router where on the first load, all users are loaded. To this end, I've implemented a main component that loads the user when the component is mounted:
class Content extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.load();
}
render() {
return this.props.children;
}
}
The afterwards, if the user chooses to load the details of one user, the details are also obtained through the same lifehook:
class Details extends Component {
componentDidMount() {
this.props.getByUrl(this.props.match.params.url);
}
render() {
const { user: userObject } = this.props;
const { user } = userObject;
if (user) {
return (
<>
<Link to="/">Go back</Link>
<h1>{user.name}</h1>
</>
);
}
return (
<>
<Link to="/">Go back</Link>
<div>Fetching...</div>
</>
);
}
Now this works well if the user lands on the main page. However, if you get directly to the link (i.e. https://43r1592l0.codesandbox.io/gal-gadot) it doesn't because the users aren't loaded yet.
I made a simple example to demonstrate my issues. https://codesandbox.io/s/43r1592l0 if you click a link, it works. If you get directly to the link (https://43r1592l0.codesandbox.io/gal-gadot) it doesn't.
How would I solve this issue?
Summary of our chat on reactiflux:
To answer your question: how would you solve this? -> High Order Components
your question comes down to "re-using the fetching all users before loading a component" part.
Let's say you want to show a Component after your users are loaded, otherwise you show the loading div: (Simple version)
import {connect} from 'react-redux'
const withUser = connect(
state => ({
users: state.users // <-- change this to get the users from the state
}),
dispatch => ({
loadUsers: () => dispatch({type: 'LOAD_USERS'}) // <-- change this to the proper dispatch
})
)
now you can re-use withUsers for both your components, which will look like:
class Content extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
// ... etc
}
const ContentWithUsers = withUsers(Content) // <-- you will use that class
class Details extends Component {
componentDidMount() {
if (! this.props.users || ! this.props.users.length) {
this.props.loadUsers()
}
}
}
const DetailsWithUsers = withUsers(Details) // <-- same thing applies
we now created a re-usable HOC from connect. you can wrap your components with withUsers and you can then re-use it but as you can see, you are also re-writing the componentDidMount() part twice
let's take the actual load if we haven't loaded it part out of your Component and put it in a wrapper
const withUsers = WrappedComponent => { // notice the WrappedComponent
class WithUsersHOC extends Component {
componentDidMount () {
if (!this.props.users || !this.props.users.length) {
this.props.loadUsers()
}
}
render () {
if (! this.props.users) { // let's show a simple loading div while we haven't loaded yet
return (<div>Loading...</div>)
}
return (<WrappedComponent {...this.props} />) // We render the actual component here
}
}
// the connect from the "simple version" re-used
return connect(
state => ({
users: state.users
}),
dispatch => ({
loadUsers: () => dispatch({ type: 'LOAD_USERS' })
})
)(WithUsersHOC)
}
Now you can just do:
class Content extends Component {
render() {
// ......
}
}
const ContentWithUsers = withUsers(Content)
No need to implement loading the users anymore, since WithUsersHOC takes care of that
You can now wrap both Content and Details with the same HOC (High Order Component)
Until the Users are loaded, it won't show the actual component yet.
Once the users are loaded, your components render correctly.
Need another page where you need to load the users before displaying? Wrap it in your HOC as well
now, one more thing to inspire a bit more re-usability
What if you don't want your withLoading component to just be able to handle the users?
const withLoading = compareFunction = Component =>
class extends React.Component {
render() {
if (! compareFunction(this.props)) {
return <Component {...this.props} />;
}
else return <div>Loading...</div>;
}
};
now you can re-use it:
const withUsersLoading = withLoading(props => !props.users || ! props.users.length)
const ContentWithUsersAndLoading = withUsers(withUsersLoading(Content)) // sorry for the long name
or, written as a bit more clean compose:
export default compose(
withUsers,
withLoading(props => !props.users || !props.users.length)
)(Content)
now you have both withUsers and withLoading reusable throughout your app