Use a child's method in a parent component - javascript

This is my code:
NavigationComponent - this is my navigation. I want the button clicked here to fire up a method from the child.
class NavigationComponent extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
firebase.auth().onAuthStateChanged(user => {
if (user) {
console.log(user);
this.setState(
{
user: user
}, () => this.props.checkUserState(this.state.user)
);
}
});
}
render() {
return (
<BrowserRouter>
<React.Fragment>
<Navbar>
<Navbar.Header>
<Navbar.Brand>
<Link id='home' to="/">UczIchApp</Link>
</Navbar.Brand>
</Navbar.Header>
<Nav>
<LinkContainer id='about' to='/about'>
<NavItem>O nas</NavItem>
</LinkContainer>
{
this.state.user ?
<React.Fragment>
<LinkContainer id="questions" to='/questions'>
<NavItem>Zadania</NavItem>
</LinkContainer>
<NavItem onClick={Want to use it here}>Wyloguj się</NavItem>
</React.Fragment>
:
<NavItem onClick={And here}>Zaloguj się</NavItem>
}
</Nav>
</Navbar>
<Switch>
<Route exact path="/about" component={AboutComponent}/>
<Route exact path="/questions" component={QuestionsComponent}/>
<Route exact path="/" component={HomeComponent}/>
<Route path='/question/:id' component={QuestionComponent}/>
</Switch>
</React.Fragment>
</BrowserRouter>
)
}
}
LogoutComponent - I want a method from this component to be fired up
export default class LogoutComponent extends react.Component {
constructor(p) {
super(p);
this.logout = this.logout.bind(this);
console.log('tada');
}
logout() {
console.log('got here');
firebase
.auth()
.signOut()
.then(() => {
this.setState({
user: null
}, function () {
this.props.checkUserState(this.state.user)
});
});
}
}
What I want to do, is to use the logout() function when the button on the navbar is clicked. The problem is I have no idea how to reference it.
I tried something like this LogoutComponent.logout, but no luck. If it's not possible, how could I solve this?

This can be done by using React refs, and a method on the child component.
parent.js
constructor() {
super();
this.child_ref = null;
}
createChildRef(DOMElement) {
this.child_ref = DOMElement;
}
respondToSomeEvent() {
if (this.child_ref) {
this.child_ref.somePublicMethod();
}
}
render() {
return (
<ChildComponent ref={this.createChildRef} />
)
}
child.js
class ChildComponent extends React.Component {
somePublicMethod() {
// The parent can call this method through the React ref
}
}
Sometimes it is necessary to do this, based on the architecture of your application, but you should look into passing around an EventEmitter instance instead, so that your components can respond to changes outside of state that is stored in React.

Related

Unable to pass correct component as props to a custom router

I am trying to create a <ProtectedRoute /> component, in which I can pass a component like below
<Route exact={true} path={routes.LOGIN} component={Login} />
<ProtectedRoute path={routes.HOME} component={Home} authUser={this.state.authUser} />
<ProtectedRoute path={routes.PROJECT} component={Project} authUser={this.state.authUser} />
my ProtectedRoute.tsx code
interface ProtectedRouteProps {
path: string;
component: React.ElementType;
authUser: any;
}
export class ProtectedRoute extends React.Component<ProtectedRouteProps, {}> {
constructor(props: any) {
super(props);
}
render() {
const Component = this.props.component;
const authUser = this.props.authUser;
console.log(this.props.component);
return authUser ? (
<Component />
) : (
<Redirect to={{ pathname: routes.LOGIN }} />
);
}
}
in the props I have tried React.Component, React.ComponentType<any> too.
However, my app never routes to Project, it always routes to Home.
So, I added a log to see what I am getting as this.props.component.
I always get
class Home extends react__WEBPACK_IMPORTED_MODULE_0___default.a.Component {
constructor(props) {
super(props);
}
render() {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.c…
which makes me think I am not passing the Component properly.
Can anybody help with the mistake or how to pass a Component using a prop.
PS: If I add Project in Route instead of ProtectedRoute, it works fine.
EDIT: Adding Home/Project Component (Both of them have the same form).
export class Project extends React.Component {
constructor(props: any) {
super(props);
}
render() {
return (
<div>
<h1>Project</h1>
</div>
)
}
}
You need to send component
<Route exact={true} path={routes.LOGIN} component={Login} />
<ProtectedRoute path={routes.HOME} component={()=>(<Home/>)} authUser={this.state.authUser} />
<ProtectedRoute path={routes.PROJECT} component={()=>(<Project/>)} authUser={this.state.authUser} />
and the ProtectedRoute
interface ProtectedRouteProps {
path: string;
component: React.ElementType;
authUser: any;
}
export class ProtectedRoute extends React.Component<ProtectedRouteProps, {}> {
constructor(props: any) {
super(props);
}
render() {
const authUser = this.props.authUser;
console.log(this.props.component);
return authUser ? (
this.props.component()
) : (
<Redirect to={{ pathname: routes.LOGIN }} />
);
}
}

React context value is not defined in ComponentDidMount

I'm using React and Context API to store the userId in a Context.
A component Authenticator is under my ContextProvider and store the userId in the context, it's working very well.
However, i want to use this userId from the context in another component MySpace in order to fetch some data about the current user. But in the ComponentDidMount() function of MySpace, this.context.userId is null. I think it's because my ContextProvider is doing a setState() to store userId and MySpace is mounted before the setState() has finished.
Here is my code, i don't know if i need to fetch data in another lifecycle method or so.
App.js
class App extends Component {
render() {
return (
<Router history={history}>
<UserContextProvider>
<Authenticator />
<Navbar />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/register' component={Register} />
<Route path='/login' component={Login} />
<PrivateRoute path='/my-space' component={MySpace} />
<Route component={NotFoundPage} />
</Switch>
</UserContextProvider>
</Router>
);
}
}
UserContext.js
export const UserContext = createContext();
class UserContextProvider extends React.Component {
state = {
userId: null
}
replaceUser = (userId) => {
this.setState({ userId: userId });
}
render() {
return (
<UserContext.Provider value={{...this.state, replaceUser: this.replaceUser}}>
{this.props.children}
</UserContext.Provider>
);
}
}
Authenticator.js
class Authenticator extends Component {
static contextType = UserContext;
componentDidMount() {
const { replaceUser } = this.context;
replaceUser(getUserId());
}
render() {
return (
<></>
);
}
}
MySpace.js
class MySpace extends Component {
static contextType = UserContext;
componentDidMount() {
document.title = "Mon espace - Todo";
this.getMyProjects();
}
getMyProjects = () => {
const { userId } = this.context
console.log(userId) // => null
_getMyProjects(userId)
.then(projects => {
console.log(projects)
})
.catch(err => alert(err))
}
render() {
return (
<p>Mon espace</p>
)
}
}
You could catch when the ContextProvider ahs the userId and is ready to fetch the data just need another prop in your state in my example ive used isReady
export const UserContext = createContext();
class UserContextProvider extends React.Component {
state = {
isReady: false,
userId: null
}
replaceUser = (userId) => {
this.setState({ userId: userId, isReady: true });
}
render() {
return (
<UserContext.Provider value={{...this.state, replaceUser: this.replaceUser}}>
{this.props.children}
</UserContext.Provider>
);
}
}
And in byour MySpace Component you need to check in componentDidUpdate if the isReady flag is true you can fetch the data with the userID:
class MySpace extends Component {
static contextType = UserContext;
componentDidMount() {
document.title = "Mon espace -
}
componentDidUpdate(){
if(this.context.isReady){
this.getMyProjects();
}
}
getMyProjects = () => {
const { userId } = this.context
console.log(userId) // => null
_getMyProjects(userId)
.then(projects => {
console.log(projects)
})
.catch(err => alert(err))
}
render() {
return (
<p>Mon espace</p>
)
}
}
I think that this could solve your issue, othe approach could be instead of creating a new prop just check if the context userId is different from null as null is your initial state

React pass props to children edge case?

My app has the structure below and I would like to pass a prop based in the Header state to the RT component. I can pass it easily with Context but I need to call an API when the prop is a certain value and Context doesn't seem to be designed for this use due to it using the render pattern.
In Header.js I render the children with this.props.children. To pass props I've tried the following patterns but nothing is working. I'm missing a concept here. What is it?
(1) React.Children.map(children, child =>
React.cloneElement(child, { doSomething: this.doSomething }));
(2) {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
(3) <Route
path="/issues"
render={({ staticContext, ...props }) => <RT {...props} />}
/>
Structure:
App.js
<Header>
<Main />
</Header>
Main.js
const Main = () => (
<Grid item xl={10} lg={10}>
<main>
<Switch>
<Route exact path="/" component={RT} />
<Route path="/projects" component={Projects} />
<Route path="/issues" component={RT}/>
<Route path="/notes" component={Notes} />
</Switch>
</main>
</Grid>
);
I would personally recommend using the React Context API to handle the user state rather than manually passing it via props. Here's an example of how I use it:
import React from 'react';
export const UserContext = React.createContext();
export class UserProvider extends React.Component {
constructor(props) {
super(props);
this.state = {
user: false,
onLogin: this.login,
onLogout: this.logout,
};
}
componentWillMount() {
const user = getCurrentUser(); // pseudo code, fetch the current user session
this.setState({user})
}
render() {
return (
<UserContext.Provider value={this.state}>
{this.props.children}
</UserContext.Provider>
)
}
login = () => {
const user = logUserIn(); // pseudo code, log the user in
this.setState({user})
}
logout = () => {
// handle logout
this.setState({user: false});
}
}
Then you can use the User context wherever you need it like this:
<UserContext.Consumer>
{({user}) => (
// do something with the user state
)}
</UserContext.Consumer>

How to pass component reference to routes component in react

In react i want to pass a reference of component to routes component . But when i route to path such as '/' it re render the passed component so starts with 0 again.
Here is a Link of Working example
https://codesandbox.io/s/884yror0p2
Here is a code, whenever i route from Home-->About the about counter starts with 0.
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Dummy extends React.Component {
constructor(props) {
super(props);
this.state = {
c: 0
};
setInterval(() => {
this.setState({ c: this.state.c + 1 });
}, 1000);
}
render() {
return <h1> Counter {this.state.c}</h1>;
}
}
class BasicExample extends React.Component {
constructor(props) {
super(props);
this.state = {
// com: <Dummy /> this technique also not work
};
}
render() {
let com = <Dummy />;
return (
<div>
<Router>
<div>
{com}
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<hr />
<Route
exact
path="/"
// render={() => <Home com={com} />} //this also not work
component={props => <Home {...props} com={com} />}
/>
<Route path="/about"
component={props => <About {...props} com={com} />}
/>
</div>
</Router>
</div>
);
}
}
class Home extends React.Component {
render() {
return (
<div>
{this.props.com}
<h2>Home </h2>
</div>
);
}
}
class About extends React.Component {
constructor(props) {
super(props);
console.log(this.props);
}
render() {
return (
<div>
{this.props.com}
<h2>About</h2>
</div>
);
}
}
ReactDOM.render(<BasicExample />, document.getElementById("root"));
You can’t pass a component by “reference”. However, if you would like to have the same data between all the components you could initialize your state in the parent component (BasicExample) and pass it down or use a state container like Redux.
You can the component down but the component will also go through a mounting process that will invalidate the state that you have stored in it.
If you need to hold the state, it should be stored in the parent component.
const Router = ReactRouterDOM.HashRouter;
const Route = ReactRouterDOM.Route;
const Link = ReactRouterDOM.Link;
class Dummy extends React.Component {
constructor(props) {
super(props);
this.state = {
c: props.c || 0
};
}
componentWillReceiveProps(nextProps) {
if (this.props.c !== nextProps.c) {
this.setState({
c: nextProps.c
});
}
}
render() {
return <h1> Counter {this.state.c}</h1>;
}
}
class BasicExample extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
};
setInterval(() => {
this.setState(prevState => {
return {
counter: prevState.counter + 1
};
});
}, 1000);
}
render() {
let Com = <Dummy c={this.state.counter} />;
return (
<div>
<h2>Main App</h2>
<Router>
<div>
{Com}
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
<hr />
<Route
exact
path="/"
// render={() => <Home com={com} />} //this also not work
render={props => (
<Home {...props} c={this.state.counter} Com={Dummy} />
)}
/>
<Route
path="/about"
render={props => (
<About {...props} c={this.state.counter} Com={Dummy} />
)}
/>
</div>
</Router>
</div>
);
}
}
class Home extends React.Component {
render() {
const Com = this.props.Com;
return (
<div>
<h2>Home </h2>
<Com c={this.props.c} />
</div>
);
}
}
class About extends React.Component {
constructor(props) {
super(props);
}
render() {
const Com = this.props.Com;
return (
<div>
<h2>About</h2>
<Com c={this.props.c} />
</div>
);
}
}
ReactDOM.render(<BasicExample />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router/4.2.0/react-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router-dom/4.2.2/react-router-dom.js"></script>
<div id="root"></div>
Few things to note here are:
Use render property of Route to pass in any props
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component prop, you would create a new component every render. This
results in the existing component unmounting and the new component
mounting instead of just updating the existing component. When using
an inline function for inline rendering, use the render or the
children prop
Use function version of setState to refer to prevState

React router dom show a component after user signs in [duplicate]

I'm trying to write Authentication checking for my DashBoard. But the function itself is not getting called. Can anyone give me some solution for this? I'm developing in ReactJs.
This is the Route part :
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<Route path={"/Dashboard"} component={Dashboard} onEnter={this.requireAuth} />
</div>
</Router>
This is the function :
requireAuth (nextState, replace) {
console.log("?????????????",this.state.getToken);
if(!this.state.getToken) {
replace({pathname: '/'});
}
}
In react-router v4, you can make use of render prop to Route along with the lifecycle methods to replace the onEnter functionality existing in react-router v3.
See this answer for more details:
onEnter prop in react-router v4
However since all you want to do is authentication in the onEnter prop, you could easily create a HOC that does that
const RequireAuth = (Component) => {
return class App extends Component {
componentWillMount() {
const getToken = localStorage.getItem('token');
if(!getToken) {
this.props.history.replace({pathname: '/'});
}
}
render() {
return <Component {...this.props} />
}
}
}
export { RequireAuth }
and use it like
<Route path={"/Dashboard"} component={RequireAuth(Dashboard)}/>
Edit: In case you need to make a network call to find if the use if authenticated of not, you would write the HOC like
const RequireAuth = (Component) => {
return class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
const { isAuthenticated, isLoading } = this.state;
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { RequireAuth }
Update:
In addition to the HOC, you can also go for the PrivateRoute component like
const PrivateRoute = ({component: Component, isAuthenticated, isLoading, ...rest }) => {
if(isLoading) {
return <div>Loading...</div>
}
if(!isAuthenticated) {
return <Redirect to="/login" />
}
return <Component {...this.props} />
}
}
}
export { PrivateRoute };
and you can use it like
class App extends Component {
state = {
isAuthenticated: false,
isLoading: true
}
componentDidMount() {
AuthCall().then(() => {
this.setState({isAuthenticated: true, isLoading: false});
}).catch(() => {
this.setState({isLoading: false});
})
}
render() {
<Router>
<div>
<Route exact path={"/"} component={Home} />
<Route path={"/SignUp"} component={SignUp} />
<Route path={"/SignIn"} component={SignIn} />
<PrivateRoute path={"/Dashboard"} component={Dashboard} isAuthenticated={this.state.isAuthenticated} isLoading={this.isLoading}/>
</div>
</Router>
}
}

Categories

Resources