Dispatch Action right after state variable is set - reactjs

I have an initial redux state like this:
{
loggedInUserId: null,
comments: []
}
Here's how my React App looks like:
class App extends Component {
componentWillMount() {
this.props.getLoggedInUserId();
}
render() {
return (
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/comments" component={Comments} />
</Switch>
);
}
}
In my App, I dispatch an action getLoggedInUserId() which asynchronously fills the loggedInUserId in the state.
The HomePage is a dumb component showing some text. I start the app (route is now '/'), see the HomePage component, then I navigate to the Comments page, which has:
componentWillMount() {
this.props.fetchComments(this.props.loggedInUserId); // Dispatch action to do API call to fetch user's comments
}
render() {
// Show this.props.comments nicely formatted
}
Everything works, I see the list of comments in the Comments component.
But if I refresh the page on the route /comments, then by the time the Comments runs componentWillMount, the loggedInUserId has not been loaded yet, so it will call fetchComments(null).
Right now, to fix this, I'm doing in my Comments component:
componentWillMount() {
if (!this.props.loggedInUserId) return;
this.props.fetchComments(this.props.loggedInUserId);
}
componentWillReceiveProps(nextProps) {
if (!this.props.loggedInUserId && nextProps.loggedInUserId) {
nextProps.fetchComments(nextProps.loggedInUserId);
}
}
which works well. But I'm doing this in 10+ components, and it seems like a lot of work which can be factorized, but I didn't find an elegant way to do it.
So I'm asking you how do you generally deal with this kind of situation? Any idea is welcome:
HOC
side-effects
other libraries

I'm using wrapper around Route, which checks if users are logged in and if not, redirect them to login page. Wrapped routes are rendered only after userId of authenticated user is fetched.
import * as React from 'react'
import { Route, Redirect } from 'react-router-dom'
import URLSearchParams from 'url-search-params'
class AuthRoute extends React.Component {
componentDidMount() {
if (!this.props.isLoading) {
this.props.getLoggedInUserId()
}
}
render() {
if (this.props.isLoading) {
// first request is fired to fetch authenticated user
return null // or spinner
} else if (this.props.isAuthenticated) {
// user is authenticated
return <Route {...this.props} />
} else {
// invalid user or authentication expired
// redirect to login page and remember original location
const search = new URLSearchParams({
next: this.props.location.pathname,
})
const next =
this.props.location.pathname !== '/' ? `?${search.toString()}` : ''
return <Redirect to={`/login${next}`} />
}
}
}
You need to update your reducer which handle getLoggedInUserId action to store also isLoading state.

You probably want the initial state to be rendered by the server into 'index.html' (or what have you) and hydrated on the client.
This initial state would include loggedInUserId and data for the /comments page.
Check out https://redux.js.org/docs/recipes/ServerRendering.html

I think using HOC will be clean here. As all the common logic will be at the same place. Use composition here
Let say you have components A, B, C, D
Now you want to write some common function on the componentWillReceiveProps lifecycle of all the components.
Write a HOC like:
class HOC extends React.Component {
componentWillReceiveProps(nextProps) {
//Your commomn logic
}
render() {
const childrenWithProps = React.Children.map(this.props.children,
child => React.cloneElement(child, {
...this.props,
})
return (
<div>
{childrenWithProps}
</div>
)
}
}
Write your components like this:
class A extends React.Component {
componentWillReceiveProps(nextProps) {
//your uncommone logic
}
render(){
return (
<HOC {...this.props}>
<div>
//Your page jsx
</div>
</HOC>
)
}
}
same way write for component B, C, and D. This pattern is useful when there is lot common among components. So better have a look at your usecase

OP writing. After reading nice ideas here, I decided to go with a custom HOC:
import React, { Component } from 'react';
const requireProp = (As, propsSelector, propsToDispatch) =>
class Wrapper extends Component {
componentWillMount() {
if (!propsSelector(this.props) && typeof propsToDispatch === 'function') {
propsToDispatch(this.props);
}
}
render() {
const { ...props } = this.props;
return !!propsSelector(this.props) && <As {...props} />;
}
};
export default requireProp;
To see how I use it, see this gist.

Related

I have to redirect the page based on location of the page. May i know which method is best practice

lifecycleMethodName (){ const presentPage = 'home/page1';if (this.props.location!==presentPage){this.pros.redirectTo(presentPage);}}
FYI. I have tried component will receive props and component did update but no luck. can some one help me ?
Use it in this way :-
import React, { useState } from 'react'
import { Redirect } from 'react-router-dom'
const Home = () => {
constructor(props){
this.state={
isRedirect : false,
presentPage : "home/page1"
}
}
const renderRedirect = () => {
if (this.state.isRedirect && this.props.location!==this.state.presentPage) {
return (
<Redirect to={{
pathname: '/home/page1'
}}
/>
);
}
}
const clicked = () => {
console.log('clicked');
this.setState({
isRedirect :true
})
}
return (
<div>
Home
{renderRedirect()}
<button onClick={() => clicked()}>click</button>
</div>
)
}
export default Home;
Or you want use lifeCycle method then call clicked() function in componentDidMount like below:-
componentDidMount(){
this.clicked()
}
This is a common use case. The React life cycle method you're looking for is componentDidMount.
componentDidMount (){ <<your code to redirect >>}
The above will trigger when the component is mounted. It will also work for server-side rendered components since the method only runs clientside.
However if your app is client-side only and your component is a Class component you can also run your code in the constructor itself, since it will have access to the props.
If you're in a function component, you can run it inside the component right away, there's no need to use any hook, since the function will have access to the props right away.

React context update does not cause component rerender

I'm creating a code splitting solution for react app using react router, webpack and dynamic imports. The idea is to map all the routes and corresponding components to different app contexts and split code according to app contexts. When user visits some route the whole code chunk of related app context is being loaded.
Code examples:
App.tsx:
class App extends React.Component<Props, State> {
render() {
if (this.props.data.loading || this.props.localData.loading) {
return <Loader />
}
return (
<IntlProvider locale={this.props.localData.session.locale} messages={this.state.translations}>
<Router history={history}>
<Route
exact
path={`/screens/:action?`}
render={() => <ComponentLoader contextName={Context.Screens} componentName={'ScreenList'} />}
/>
</Router>
</IntlProvider>
)
}
}
export default withData(App)
ComponentLoader.tsx:
export enum Context {
Screens = 'Screens',
Channels = 'Channels'
}
const CONTEXT_LOADERS: { [name: string]: ComponentChunkLoader } = {
[Context.Screens]: () => import('../../routerContexts/screens'),
[Context.Channels]: () => import('../../routerContexts/channels'),
}
const loadedContexts: ContextsCollection = {}
class ComponentLoader extends React.PureComponent<Props, State> {
state: State = {
Component: null
}
async componentDidMount() {
this._updateComponent(this.props)
}
async componentDidUpdate(prevProps: Props, prevState: State) {
if (this.props.componentName !== prevProps.componentName || this.props.contextName !== prevProps.contextName) {
this._updateComponent(this.props)
}
}
_updateComponent = async (props: Props) => {
let module = loadedContexts[props.contextName]
? loadedContexts[props.contextName]
: await CONTEXT_LOADERS[props.contextName]()
if (!loadedContexts[props.contextName]) loadedContexts[props.contextName] = module
let ComponentClass = module[props.componentName]
this.setState({
Component: ComponentClass
})
}
render() {
if (this.state.Component !== null) {
return <this.state.Component />
}
return <Loader />
}
}
export default ComponentLoader
So when I switch to different routes, I see all the components correctly, and code splitting works correctly.
The problem is: when the data in react context updates, ScreenList component in the example doesn't get updated. When I pass ScreenList directly to react router Route everything works well. So the problem is in my ComponentLoader component that I use for code splitting.
Any ideas what can be the reason?
UPDATE:
Alrite, what i figured out now: if I wrap my ComponentLoader in a HOC that injects some data from context (like export default withRouter(ComponentLoader)) , everything works well and components rerender as expected. Why is happening like this?

Route authorization HOC cause to remount children 3-times

I'm using an HOC component to restrict access to the route for non-logged users. The problem that this HOC remount children components while mounting or re-rendering when access this route directly from url(on the app first load). For example I have a 3 times did mount in the PaperWorkProgress component.
Route definition:
<Route path="/paperwork/progress" component={RequireAuth(PaperWorkProgress)}/>
Here the HOC code:
import React, {Component} from 'react';
import {connect} from 'react-redux';
export default function(ComposedComponent) {
class Authentication extends Component {
// check if token exists in storage
componentWillMount() {
const token = localStorage.getItem('token');
if (!token) {
const {pathname, search} = this.props.location;
this.props.history.push({
pathname: '/signin',
search: `?redirect_to=${pathname}${search}`,
});
}
}
// additional check
componentWillUpdate(nextProps) {
if (!nextProps.loggedIn) {
const {pathname, search} = this.props.location;
this.props.history.push({
pathname: '/signin',
search: `?redirect_to=${pathname}${search}`,
});
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return {loggedIn: state.session.loggedIn};
}
return connect(mapStateToProps)(Authentication);
}
Any ideas?
This question may be from a while ago already, but I just encountered the same problem.
In the end I found out that my HOC function was actually called on every route change.
What helped for me was to create the authorized component only once on initialization:
const AuthorisedDashboard = requireLogin(Dashboard);
and then later just use it
<Route path="/dashboard" component={AuthorisedDashboard} />
Or, you know, I guess you could just export the component with the HOC function already applied if it is only ever used in authorised mode...
I'm not sure this will make a difference about the re-rendering problem, but your code feels wrong.
First, you seems to have 2 source of truth, your redux store and the localStorage, which complicates things. If you want to "hydrate" your store from previous navigation information, you should use the createStore "preloadedState" argument, not checking everytime in your component. Cf Redux doc and this video from the creator of Redux himself Video for persisting and rehydrating State. Once your state comes only from your store it starts to be more simple.
Second,
When you push to the history object inside the component, It feels like you are mutating the component own props (as history is a prop). That feels weird to me and could be the root of your problem.
Why not use the Redirect component inside your render method like this instead ? cf React router docs. The component will looks like this (obviously you would need to change your Login component too, like in the docs)
import React, { Component } from "react";
import { connect } from "react-redux";
import { Redirect } from "react-router-dom";
export default function(ComposedComponent) {
class Authentication extends Component {
render() {
return !this.props.loggedIn ? (
<Redirect
to={{
pathname: "/login",
state: { from: this.props.location }
}}
{...this.props}
/>
) : (
<ComposedComponent {...this.props} />
);
}
}
function mapStateToProps(state, ownProps) {
return { loggedIn: state.session.loggedIn, ...ownProps };
}
return connect(mapStateToProps)(Authentication);
}

Prevent routing in React when user manually changes url in browser tab

I am stuck in a issue that happens when user manually changes the route in browser tab and presses enter. This forces my react router to navigate to the state entered by user. I want to prevent this and allow routing only through the flow I have implemented by button clicks in my website.
Some of my screens need data that will be available only if the user navigates the site using the flow expected. If user directly tries to navigate to a particular route by manually changing the route in url then he may skip the desired flow and hence the app will break.
Other scenario, in case I want to restrict some users from accessing some routes but the user knows the path and manually enters that in browser url then he will be presented with that screen but should not be.
What I do is use a prop from previous page, if that prop is undefined(meaning user did not follow due process :) hehe ) I simply send the user back to the landing page or wherever.
You can create a route guard using HOC. For example, you don't want unauthorized user to pass route /profile, then you can do the following:
// requireAuthorized.js (HOC)
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {connect} from 'react-redux'
import {Redirect} from 'react-router-dom'
const connector = connect(
state => ({
isAuthorized: state.profile !== null // say, you keep user profile in redux
})
)
export default (WrappedComponent) => {
return (
connector(
class extends Component {
static propTypes = {
isAuthorized: PropTypes.bool.isRequired
}
render () {
const {isAuthorized, ...clearedProps} = this.props
if (isAuthorized) {
return <WrappedComponent {...clearedProps} />
} else {
return <Redirect to={{pathname: '/login'}} />
}
}
}
)
)
}
// ProfilePage.jsx
import React from 'react'
...
import requireAdmin from '../hocs/requireAdmin' // adjust path
class ProfilePage extends React.Component {
...
render () {
return (
<div>
...
</div>
)
}
}
export default requireAdmin(ProfilePage)
Pay attention to the export statement in my ProfilePage.js
I'd suggest using this library for cleanest solution (or at least make personal similar implementation of it).
Then you'd create authentication check HOC:
export const withAuth = connectedReduxRedirect({
redirectPath: '/login',
authenticatedSelector: state => state.user.isAuthenticated, // or whatever you use
authenticatingSelector: state => state.user.loading,
wrapperDisplayName: 'UserIsAuthenticated'
});
And you could easily create flow HOC:
export const withFlow = (step) = connectedReduxRedirect({
redirectPath: '/initial-flow-step',
authenticatedSelector: state => state.flow[step] === true,
wrapperDisplayName: 'FlowComponent'
});
Then initialize your component
const AuthenticatedComponent = withAuth(Dashboard)
const SecondStepComponent = withFlow("first-step-finished")(SecondStep)
const ThirdStepComponent = withFlow("second-step-finished")(ThirdStep)
You can easily create authenticated flow step by composing HOC:
const AuthSecondStepComponent = withAuth(withFlow("first-step-finished")(SecondStep))
Only thing that is important is that you update your redux state correctly as going through your step flow. When user finishes first step you'd set
state.flow["first-step-finished"] = true // or however you manage your state
so that when user navigates manually to specific page, he wouldn't have that redux state because its an in-memory state and would be redirected to redirectPath route.
Something like this is suitable. You make HOC Route with a wrap to function that deals with authentication/context props.
Note: this deals with direct access to the route, not to the menu items and such. That must be treated in a simmilar way on the menu / menuItem components.
import requireAuth from "../components/login/requireAuth";
class Routes extends React.Component<RoutesProps, {}> {
render() {
return (
<div>
<Switch>
<Route exact={true} path="/" component={requireAuth(Persons, ["UC52_003"])} />
<Route path="/jobs" component={requireAuth(Jobs, ["UC52_006"])} />
</Switch>
</div>
)
}
}
export default function (ComposedComponent, privileges) {
interface AuthenticateProps {
isAuthenticated: boolean
userPrivileges: string[]
}
class Authenticate extends React.Component<AuthenticateProps, {}> {
constructor(props: AuthenticateProps) {
super(props)
}
render() {
return (
isAuthorized(this.props.isAuthenticated, privileges, this.props.userPrivileges) &&
<ComposedComponent {...this.props} /> || <div>User is not authorised to access this page.</div>
);
}
}
function mapStateToProps(state) {
return {
isAuthenticated: state.userContext ? state.userContext.isAuthenticated : false,
userPrivileges: state.userContext ? state.userContext.user ? state.userContext.user.rights : [] : []
};
}
return connect(mapStateToProps, null)(Authenticate);
}
you can put the condition in useEffect of the given page/screen and push it back if it doesnt have the required values.. example below

React router 1.0 pass multiple props to children routes

I'm using react-router 1.0 and react-redux on an app, and I'm wondering what strategy is best to pass props to children on larger apps. Here's a basic situation:
Let's say I have a route /admin/users/edit/:id with the following structure on its components:
Routes:
<Router>
<Route path="admin" component={Admin}>
<Route path="users" component={Users}>
<Route path="edit/:id" component={Edit}/>
</Route>
</Route>
</Router>
Admin:
class Admin extends React.Component {
render() {
return (
{this.props.children}
)
}
}
Users:
class User extends React.Component {
edit = (id, params) => {
const { dispatch } this.props;
dispatch(edit(id, params));
}
other functions (add, remove) ...
render() {
return (
{this.props.children}
)
}
}
function mapStateToProps(state) {
const { users } = state;
return { users };
}
export default connect(mapStateToProps)(User);
Edit:
class Edit extends React.Component {
submit () => {
const { id } = this.props.params;
const { firstName } = this.refs;
this.props.edit(id, {firstName: firstName.value});
}
render() {
const { id } = this.props.params;
const user = this.props.users[id];
return (
<form>
<input ref='firstName' defaultValue={user.firstName}/>
<button onClick={this.submit}>Submit</button>
</form>
)
}
}
How would I pass the users & edit function props down to the children?
I know about React.cloneElement() (as in https://github.com/rackt/react-router/tree/master/examples/passing-props-to-children), but if I have multiple routes like /users/add, /users/remove/:id, etc, I would be passing and exposing all the functions (edit, add, remove...) to all children. That solution doesn't seem to work very well when you have more than one children.
I would like to keep my children as dumb as possible, and use this same structure across the application (/images/add, /images/remove/:id, etc).
Any suggestions?
You have a few options:
First level children:
Use React.cloneElement(), that's something you are already aware of. I wouldn't use it for deeply nested Components though.
To all routes:
Use createElement():
<Router createElement={createElement} />
// default behavior
function createElement(Component, props) {
// make sure you pass all the props in!
return <Component {...props}/>
}
// maybe you're using something like Relay
function createElement(Component, props) {
// make sure you pass all the props in!
return <RelayContainer Component={Component} routerProps={props}/>
}
Check more on that in the React Router docs.
Use context:
Note:
Context is an advanced and experimental feature. The API is likely to change in future releases.
See how to use it in the React docs.
There is also the whole thread about it in the react-router#1531.

Resources