Converting a class to a functional component - reactjs

I am trying to convert this class to a functional component but no luck. Can anyone
give me direction?
import { Redirect, Route, RouteProps } from 'react-router'
function mapStateToProps (state: AppState, ownProps: RouteProps): MappedProps {
const user = state.auth.user
return {
External: user ? user.External : true,
}
}
type InnerRouteProps = RouteProps & MappedProps
class MyInnerRoute extends React.Component<InnerRouteProps> {
render () {
const {
props: { External, component, render, children, ...rest },
} = this
return (
<Route
{...rest}
render={props => {
if (External) {
return <Redirect to={'/'} />
}
if (component) {
const Component = component
return <Component {...props} />
}
if (render) {
return render(props)
}
return children
}}
/>
)
}
}
export default connect(mapStateToProps)(MyInnerRoute)
It should look like this. Not sure where the const props will be here..
const MyInnerRoute = () => {
return (
<Route
{...rest}
render={props => {
if (External) {
return <Redirect to={'/'} />
}
if (component) {
const Component = component
return <Component {...props} />
}
if (render) {
return render(props)
}
return children
}}
/>
)
}
export default MyInnerRoute

Your props will come in through the function parameters, like so:
const MyInnerRoute = (props: InnerRouteProps) => {...
And then you can use them just like you did in your class before.
const { External, component, render, children, ...rest } = props

This should do the work.
const MyInnerRoute = ({ External, component, render, children, ...rest }) => {
return (
<Route
{...rest}
render={props => {
if (External) {
return <Redirect to={'/'} />
}
if (component) {
const Component = component
return <Component {...props} />
}
if (render) {
return render(props)
}
return children
}}
/>
)
}
export default MyInnerRoute

Related

How to pass state in history.push in React-Router

I am not able to send the parameter through state using useHistory history.push method from react-router dom.
Now suppose I want to pass more than a string to the Paging component i.e. some props too.
My Paging Component which throws error for state value state is not defined
const PAGING = ({ location }) => {
console.log(location);
console.log(location.state);
console.log(location.state.id);
return <div>Hello <div>}
History.push method in another component
const handleDetails = (id,name) => {
console.log(name)
if (id) {
return history.push({
pathname: `/detailing/${name}`,
state: { id }
});
} else {
return history.push("/");
}
};
const Switch = () => {
const { state: authState } = useContext(AuthContext)
return (
<div>
<Router>
<Switch>
<ProtectedSystem
path= "/detailing/:name"
exact
auth={authState.isAuthenticated}
component={PAGING}
/>
</Switch>
</Router>
</div>
);
const ProtectedSystem = ({auth , component: Component, ...rest}) =>{
return(
<Route
{...rest}
render={() => auth ? (<Component/>) : (<Redirect to = '/' /> )}
/>
)
}
If I use simple route without condition based its working fine
<Route path= "/detailing/:name" exact component={PAGING} />
You need to pass on the Route params to the rendered component so that it can use them
const ProtectedSystem = ({auth , component: Component, ...rest}) =>{
return(
<Route
{...rest}
render={(routeParams) => auth ? (<Component {...routeParams}/>) : (<Redirect to = '/' /> )}
/>
)
}
You can do this entirely with React hooks and pure functions, eg.
import React from 'react';
import { useHistory } from 'react-router-dom';
const ProtectedSystem = ({ auth }) => {
const history = useHistory();
if (!authUser) {
history.push("/signin");
}
return (
<div><h1>Authorized user</h1></div>
)
}
export default ProtectedSystem

react-router-dom: How do i get url parems passed, while passing other data?

I would like to have my Clubs get data straight from its parent. In the example below. other props such as data and setData are both available. but not id which should be given by the path.
<AuthRoute path="/club/:id">
<Club data={data} setData={setData} />
</AuthRoute>
const AuthRoute = ({ children, ...props }) => {
const { isAuthenticated } = useAuth();
return (
<Route {...props}>
{isAuthenticated ? children : <Redirect to="/login" />}
</Route>
);
};
export const Club = (props) => {
console.log(props);
return <div>Hello World</div>;
};
I used useParems function in Club that worked.
const { id } = useParams();
You should be able to get the id in Club component by
export const Club = (props) => {
let club_id = props.match.params.id;
console.log('ClubId is::',club_id);
return <div>Hello World</div>;
};

Update context value after API call

I'm using React 16.3 Context API, I'm setting loggedin: bool & user: Object value using context, also using PrivateRoute for logged in user.
Here is a brief code.
// AuthContext JS
import React from "react";
const AuthContext = React.createContext();
class AuthProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoggedIn: false,
user : null
};
this.setAuth = this.setAuth.bind(this);
};
setAuth(isLoggedIn = false, userData = null) {
this.setState({
isLoggedIn: isLoggedIn,
user : userData
});
}
render() {
return (
<AuthContext.Provider
value={ {...this.state, setAuth: this.setAuth} }>
{ this.props.children }
</AuthContext.Provider>
);
}
}
const AuthUser = AuthContext.Consumer;
export {AuthContext, AuthProvider, AuthUser};
function PrivateRoute({component: Component, ...rest}) {
return (
<AuthUser>
{
({isLoggedIn}) => (
<Route
{ ...rest }
render={ props =>
(
isLoggedIn ? (
<Component { ...props } />
) : (
<Redirect
to={ {
pathname: "/login",
state : {from: props.location}
} }
/>
)
)
}
/>
)
}
</AuthUser>
);
}
// App JS
class App extends Component {
render() {
return (
<HashRouter>
<AuthProvider>
<Switch>
<Route exact path="/login" name="Login Page" component={ Login } />
<Route exact path="/register" name="Register Page" component={ Register } />
<Route exact path="/404" name="Page 404" component={ Page404 } />
<Route exact path="/500" name="Page 500" component={ Page500 } />
<PrivateRoute path="/" component={ DefaultLayout } />
</Switch>
</AuthProvider>
</HashRouter>
);
}
}
export default App;
// Login JS
class Login extends Component {
handleSubmit(values) {
const opts = {
"email" : "test#example.com",
"password": "test123"
};
let _this = this;
fetch("API_URL", {
method: "post",
body : JSON.stringify(opts)
})
.then(
(response) => {
return response.json();
}
).then(
(data) => {
_this.setState({
isAuth: true,
user : data.data.user
});
_this.props.history.replace("/dashboard");
}
);
}
render() {
console.log(this.state.isAuth);
return (
<AuthUser>
{
({isLoggedIn, setAuth}) =>
(
<Redirect to="/dashboard" />
) : ( <div > // Login Page </div>
)
}
</AuthUser>
);
}
}
How do I update/call setAuth function of consumer
If I call setAuth from render function, it will give warning & loop over setState
Any Help!
In the handleSubmit function in the Login file, instead of calling
this.setState({
isAuth: true,
user: data.data.user
});
you should call the setAuth function provided by the context and update the user auth and data in the context there:
this.context.setAuth(true, data.data.user)
In order to use this.context, you may need to change from using context consumer to contextType:
static contextType = AuthContext
You have implement a higher order component that help component consume context value as props.
The following withContextAsProps HOC provides an example:
function withContextAsProps(Context, Component) {
function WithContextAsProps(prop) {
return (
<Context>
{value => <Component {...value} />}
</Context>
);
}
const componentName = Component.displayName || Component.name || 'Component';
const contextName = Context.displayName || Context.name || 'Context';
WithContextAsProps.displayName = `With${contextName}Context(${componentName})`;
return WithContextAsProps;
}
In Login component, the HOC can be used to make isAuth and setAuth value from AuthUser context consumer available as props in the Login component.
class Login extends Component {
handleSubmit = values => {
//...
fetch("API_URL", {
method: "post",
body : JSON.stringify(opts)
})
.then(response => response.json())
.then(
data => {
this.props.setAuth(true, data.data.user);
this.props.location.assign("/dashboard");
}
);
}
render() {
return this.props.isAuth ?
<Redirect to="/dashboard" />
: <div>Login Page</div>;
}
}
export default withContextAsProps(AuthUser, Login);

React-router does not render anything if routes are changed in render()

For some reason, my routes only render half the time - seems like a race condition of some sort. It'll print out the "OK" but nothing from the routes, not even the 404. Pretty clear cut.
If I remove the loading bit it'll always render the switch block as intended.
Is there a better / different way to do this?
v4.2.0
render() {
const { hasInitialized } = this.props;
if (!hasInitialized) {
return (
<div>Loading...</div>
);
}
return (
<div style={{ height: '100%', width: '100%' }}>
<Helmet titleTemplate="%s - Resilient" defaultTitle="Resilient" />
<div>OK</div>
<Switch>
<Redirect from="/" to="/auth/check" exact={true} />
<Route path="/auth" component={AuthLayout} />
<AuthenticatedRoute path="/x" component={AdminLayout} />
<Route component={Miss404} />
</Switch>
</div>
);
}
https://github.com/ReactTraining/react-router/issues/5621
I read the react-router docs many times, and the part about Blocked Updates seems relevant. But, when I put a debugger line in <Layout />, location and history always have the right info, and still, none of the routes would render.
I still don't understand what the issue was, but here's the workaround I came up with. The code below wraps my <Layout /> component, which contains all the routes.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import LocalStorageManager from 'utils/LocalStorageManager';
import { selectCurrentUser, selectHasInitialized } from 'client/selectors/authSelectors';
import { setAccessToken, getProfile } from 'shared/api';
import { setHasInitialized, signIn } from 'modules/auth/actions.js';
import SinglePageCard from 'components/layout/SinglePageCard';
const mapStateToProps = (state) => {
return {
currentUser: selectCurrentUser(state),
hasInitialized: selectHasInitialized(state),
};
};
export default (WrappedComponent) => {
class Layout extends Component {
componentWillMount() {
const accessToken = LocalStorageManager.getAccessToken();
if (!accessToken) {
this.props.setHasInitialized();
return;
}
setAccessToken(accessToken);
getProfile().then((response) => {
console.log(response);
const { user } = response.data.data;
this.props.signIn(user);
}).catch((error) => {
console.log(error);
this.props.setHasInitialized();
});
}
render() {
const { currentUser, hasInitialized, ...rest } = this.props;
if (!hasInitialized) {
return (
<SinglePageCard>
<div>Initializing...</div>
</SinglePageCard>
);
}
return (
<WrappedComponent {...rest} />
);
}
}
return withRouter(connect(mapStateToProps, { setHasInitialized, signIn })(Layout));
};

React Router 4 Match returns undefined

I'm using react router 4 and I'm having trouble accessing the id from a url using params. I've followed the react router 4 documentation however when I console.log match.params.id it returns Cannot read property 'params' of undefined. The URL contains the id so I'm lost. You can find the console.log in Path: Container
What am I doing wrong?
Path: App
const App = appProps => (
<Router>
<div className="bgColor">
<NavBar {...appProps} />
<Grid className="main-page-container">
<Switch>
<Admin exact path="/admin/candidate_profile/:id" component={AdminCandidateProfileContainer} {...appProps} />
</Switch>
</Grid>
</div>
</Router>
);
App.propTypes = {
loggingIn: PropTypes.bool,
authenticatedCandidate: PropTypes.bool,
authenticatedAdmin: PropTypes.bool
};
export default createContainer(() => {
const loggingIn = Meteor.loggingIn();
return {
loggingIn,
authenticatedCandidate: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'Candidate'),
authenticatedAdmin: !loggingIn && !!Meteor.userId() && !!Roles.userIsInRole(Meteor.userId(), 'Admin')
};
}, App);
Path: AdminRoute
const Admin = ({ loggingIn, authenticatedAdmin, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />;
return authenticatedAdmin ?
(<Component loggingIn={loggingIn} authenticatedAdmin={authenticatedAdmin} {...rest} />) :
(<Redirect to="/login" />);
}}
/>
);
Admin.propTypes = {
loggingIn: PropTypes.bool,
authenticatedAdmin: PropTypes.bool,
component: PropTypes.func
};
export default Admin;
Path: Container.js
export default CandidateProfileContainer = createContainer(({ match }) => {
console.log('match', match.params.id);
const profileCandidateCollectionHandle = Meteor.subscribe('admin.candidateProfile');
const loading = !profileCandidateCollectionHandle.ready();
const profileCandidateCollection = ProfileCandidate.findOne({ userId: Meteor.userId() });
const profileCandidateCollectionExist = !loading && !!profileCandidateCollection;
return {
loading,
profileCandidateCollection,
profileCandidateCollectionExist,
profileCandidate: profileCandidateCollectionExist ? profileCandidateCollection : {}
};
}, CandidateProfilePage);
You're not passing props from render
const Admin = ({ loggingIn, authenticatedAdmin, component: Component, ...rest }) => (
<Route
{...rest}
render={(props) => {
if (loggingIn) return <div />;
return authenticatedAdmin ?
(<Component
loggingIn={loggingIn}
authenticatedAdmin={authenticatedAdmin}
{...rest}
{...props} <--- match, location are here
/>) :
(<Redirect to="/login" />);
}}
/>
);

Resources