How to call React's render method() from another component? - javascript

A client request a feature to implement dashboard switching. I'm working on it:
Dashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
// components
import UserDashboard from '../components/dashboard/user-dashboard/UserDashboard.js';
import NewUserDashboard from '../components/new-dashboard/user-dashboard/NewUserDashboard.js';
#connect((state) => {
return {
identity: state.identity.toJS().profile
};
})
export default class Dashboard extends Component {
render() {
const msisdn = this.props.location.state ? this.props.location.state.msisdn : null;
return (
<UserDashboard msisdn={ msisdn }/>
);
}
}
Dashboard.js is the dashboard controller. I have 2 dashboards: UserDashboard, and NewDashboard.
Let's say an user is viewing another screen, and in that screen there's a button. If that button is clicked, the Dashboard will call it's render method, returning NewDashboard instead. And NewDashboard will be automatically displayed. Is this possible?

Calling render method programmatically not possible.
You have to do state update of that particular component if you want to call render method of that component.
Say,if you want to call render method of Dashboard Component,you must call setState on this component. You can do some dummy state lifting for that.

Imagine you have this dashboard:
function DashBoard({index}) {
return index == 0 ? <UserDashBoard /> : <SecondDashBoard />;
}
Without a router:
class ParentComponent extends ReactComponent {
state = {
dashboardIndex: 0
}
changeDashboard() {
this.setState({
dashBoardIndex: (state.dashboardIndex + 1) % 2
})
}
render() {
return (
<div>
<button onclick={() => this.changeDashboard()}>Change dashboard</button>
<Dashboard index={this.state.dashboardIndex} />
</div>
)
}
}
With a router:
<Switch>
<Route match="/component1" component={UserDashboard} />
<Route match="/component2" component={SecondDashboard} />
</Switch>
Also you can use redux.

You can use conditional rendering using state.
You can keep track of currently active tab and use that state to render the desired component.

More often than not, in order to change page views, you would make use of Router. You can configure Routes corresponding to Dashboard
import UserDashboard from '../components/dashboard/user-dashboard/UserDashboard.js';
import NewUserDashboard from '../components/new-dashboard/user-dashboard/NewUserDashboard.js';
#connect((state) => {
return {
identity: state.identity.toJS().profile
};
})
export default class Dashboard extends Component {
render() {
const msisdn = this.props.location.state ? this.props.location.state.msisdn : null;
return (
<BrowserRouter>
<Route path="/dashboard/user" render={(props) => <UserDashboard msisdn={ msisdn } {...props}/>} />
<Route path="/dashboard/new" render={(props) => <NewUserDashboard msisdn={ msisdn } {...props}/>} />
</BrowserRouter>
);
}
}
and on button click you can use a link.
Or else you can conditionally render component based on state change
// components
import UserDashboard from '../components/dashboard/user-dashboard/UserDashboard.js';
import NewUserDashboard from '../components/new-dashboard/user-dashboard/NewUserDashboard.js';
#connect((state) => {
return {
identity: state.identity.toJS().profile
};
})
export default class Dashboard extends Component {
state = {
userDashboard: true
}
onToggle=(state)=> {
this.setState(prevState => ({
userDashboard: !prevState.userDashboard
}))
}
render() {
const msisdn = this.props.location.state ? this.props.location.state.msisdn : null;
return <div>{userDashboard? <UserDashboard msisdn={ msisdn }/>
: <NewUserDashboard msisdn={ msisdn }/>}
<button onClick={this.onToggle}>Toggle</button>
</div>
);
}
}

Probably something like:
class NewDashboard extends React.Component {
static triggerRender() {
this.forceUpdate();
}
// or
static altTriggerRender() {
this.setState({ state: this.state });
}
render() {...}
}
Force React Component Render
Though, it's better to show/hide other components by conditional rendering.
Update:
"This" is not accessible inside a static method. Ignore the code.

Related

Switch child components by state

I have a parent component:
import React, { Component, Fragment } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
SignUpClicked: null
};
this.openSignUp = this.openSignUp.bind(this)
}
openSignUp() {
this.setState({
SignUpClicked: true
})
console.log('signup clicked')
}
render() {
return (
<div>
<SignUp
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignUp = {this.openSignUp} />
<SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange} />
</div>)
}
}
export default Parent;
and then two child components SignUp & SignIn in different files.
In each of them there's a link like <p>Sign Up Instead?<a href="#" onClick = {this.props.openSignUp}> Sign Up </a></p> and the other way for Sign In.
However, I can't get them to switch between the two components - what am I doing wrong here?
You can easily control which component should be rendered by putting condition with them. If this.props.authState is true, show SignUp component else show SignIn component
<div>
{!this.props.authState && (<SignUp
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignUp = {this.openSignUp} />) }
{this.props.authState && (<SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange} />)}
</div>
There is a concept called conditional rendering you can use that can solve your problem. Simply put in conditional rendering you display a component only when a condition is met. In your case, you can try the following.
import React, { Component, Fragment } from "react";
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
SignUpClicked: false
};
}
//changed this to arrow function that binds "this" automatically
toggleSignUp = () => {
this.setState({
SignUpClicked: !this.state.SignUpClicked
})
console.log('signup clicked')
}
render() {
return (
<div>
{ this.state.SignUpClicked &&
<SignUp
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignIn = {this.toggleSignUp} />
}
{!this.state.SignUpClicked &&
<SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange}
openSignUp = {this.toggleSignUp} />
/>
}
</div>
)}
}
export default Parent;
NOTE: Pay attention to the changes I have done
Changed the function to arrow function by using them you don't have to bind this in constructor. It automatically does that for you.
I have changed the name of function openSignUp to toggleSignUp because we will use a single function to display signup component and than hide it if we want. (because I assume you will implement "sign in instead" in <SignUp/> component to get back to sign in
I have passed the same toggleSignUp function reference to both the components so that you can show or hide either of them.
Do it this way
import React, { Component, Fragment } from "react";
class Parent extends Component {
constructor(props) {
super(props);
// Set state of the component
this.state = {
// Show sign in page by default
show: "signin"
};
}
showSignup = () => {
// Show sign up page by changing the show variable
this.setState({
show: "signup"
});
console.log('Showing Signup Page');
}
showSignin = () => {
// Show sign in page by changing the show variable
this.setState({
show: "signin"
});
console.log('Showing Signin Page');
}
render() {
// Render the component as per show state variable
if(this.state.show === "signin") {
return <SignIn
authState={this.props.authState}
onStateChange={this.props.onStateChange}
onSignup={this.showSignup}
/>
}
else {
return <SignUp
authState={this.props.authState}
onSignup={this.showSignin}
/>
}
}
export default Parent;
So basically, export onClick event from both the child components and set show variable of state in parent component. Then depending upon the state, return only the component you want.
Please let me know if there is any question or confusion. Would love to answer.

React pass object by routing?

What I want to do is set routing to my object. For example:
I set routing for:
http://localhost:3000/projects
It displays all my projects list (it works pretty ok)
Then I want to choose project and see details, but it doesn't work properly:
http://localhost:3000/projects/3
How it looks like:
When I click to Details button, it sends me to /projects:id but I get an error, that items in project are undefined.
My code. I store all routing in main App.js file:
class App extends Component {
render() {
return (
<div>
<BrowserRouter>
<div>
<Route exact path="/projects" component={ProjectsList} />
<Route exact path="/projects/:id" component={ProjectDetails} />
</div>
</BrowserRouter>
</div>
);
}
}
export default App;
I have ProjectsList.js component (i will include code of it if needed), in ProjectsList.js i have listgroup with Project.js that looks like this:
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name</ListGroupItem>
<Link to={`/projects/${this.props.project.id}`}>
<Button>Details</Button>
</Link>
</ButtonToolbar>
);
}
}
export default Project;
By Link to my browser pass me to proper URL (projects/2... etc) but i dont know how to pass object of project to ProjectDetails.js component. Code of it below:
class ProjectDetails extends Component {
render() {
return <li>{this.props.project.description}</li>;
}
}
export default ProjectDetails;
Could you tell me, how to pass object of project by Link to into ProjectDetails.js? For now, i get description as undefined (its obviouse because i pass nothing to component with details).
Use the render method in your route and pass the props.
<Route exact path="/projects/:id" render={() => (
<ProjectDetails
project = {project}
/>
)}/>
You need to use mapStateToProps here. and wrap your component in the conncet from react-redux.
It should be like:
import {connect} from 'react-redux';
class ProjectDetails extends Component {
render() {
return <li>{this.props.project.description}</li>;
}
}
function mapStateToProps(state, ownProps){
const projectInstance = DATA //the Data you are getting or fetching from the ID.
return { project : projectInstance }
}
export default connect((mapStateToProps)(ProjectDetails))
This is what it will look like..!!
class Project extends Component {
render() {
return (
<ButtonToolbar>
<ListGroupItem>{this.props.project.name</ListGroupItem>
<Link to={`/projects/${this.props.project.id}`}>
<Button>Details</Button>
</Link>
</ButtonToolbar>
);
}
}
function mapStateToProps(state, ownProps){
return { project : { id: ownProps.params.id } }
}
export default connect((mapStateToProps)(Project))

this.props out of date inside componentDidUpdate() using Higher Order Component

I have a higher order component which sets some values and then passes those as props to a wrappedComponent, however within that wrapped component when I access "this.props" from componentDidMount() the values are blank. If I place logs "this.props" from the render method in the wrappedComponent however I get the desired results, though i assume this is because of a re-render. What am i doing wrong here?
Home.js
import React, { Component } from 'react'
// eslint-disable-next-line
import { BrowserRouter as Router } from 'react-router-dom'
import { Route, Switch } from 'react-router-dom'
import BlogSummaryContainer from './utility/BlogSummaryContainer'
import BlogPost from './utility/BlogPost'
import EditableBlogPost from './utility/EditableBlogPost'
function withBlogPostData (WrappedComponent) {
return class BlogPostContainer extends React.Component {
constructor () {
super()
this.state = { title: '', content: '', catchPhrase: '' }
}
componentDidMount () {
fetch(`/api/posts/${this.props.match.params.id}`)
.then(res => {
return res.json()
})
.then(blogPost => {
// this setState doesnt reach the wrappedComponent in time even if i dont do a fetch and simply hard code a value, whats going on?
this.setState({
title: blogPost.title,
content: blogPost.content,
catchPhrase: blogPost.catchPhrase
})
})
}
render () {
return (
<WrappedComponent
id={this.props.match.params.id}
title={this.state.title}
content={this.state.content}
catchPhrase={this.state.catchPhrase}
/>
)
}
}
}
class Home extends Component {
... other code
render () {
return (
<Switch>
<Route
exact
path={`${this.props.match.url}`}
render={() => {
return <BlogSummaryContainer posts={this.state.blogPosts} />
}}
/>
<Route
exact
path={`${this.props.match.url}/:id`}
component={withBlogPostData(BlogPost)}
/>
<Route
exact
path={`${this.props.match.url}/:id/edit`}
component={withBlogPostData(EditableBlogPost)}
/>
<Route
exact
path={`${this.props.match.url}/new/post`}
render={() => {
return <EditableBlogPost isNew />
}}
/>
</Switch>
)
}
}
export default Home
EditableBlogPost.js
componentDidMount (props) {
const { title, catchPhrase, content } = this.props
console.log('this.props', this.props) // this.props = {title: "", content: "", ... }
}
I think this is just an asynchronous problem - when your HOC mounts it is calling fetch() which isn't resolved instantly so that is why on the first render this.state.x are their initial empty values.
When the Promise is resolved, the values are set and the subsequent render will have the expected values.
You could conditionally render to avoid rendering the wrapped component until the fetch() has resolved:
render () {
if(this.state.title.length === 0) {
return <div>Loading...</div>; //or some nice <Loading> component
}
return (
<WrappedComponent
id={this.props.match.params.id}
title={this.state.title}
content={this.state.content}
catchPhrase={this.state.catchPhrase}
/>
)
}

Defining and exporting HOC in React

I've been research Higher Order Components in react. My requirement is that I have a set components which I need to extend to give them more functionality without rewriting the entire component. In this case, I found out the concept HOC in react where one could extend the component using a pure function. My question is, can I export the extended component as a normal component. For an example
Component which needs to be extended
class foo extends React.Component {
render(){
//something
}
}
export default foo;
HOC component
function bar(foo) {
render() {
return <foo {...this.props} {...this.state} />;
}
}
export default bar;
Am I able to use the component that way? or am I doing it wrong?
A HOC would take a component, add some more functionality and return a new component and not just return the component instance,
What you would do is
function bar(Foo) {
return class NewComponent extend React.Component {
//some added functionalities here
render() {
return <Foo {...this.props} {...otherAttributes} />
}
}
}
export default bar;
Now when you want to add some functionality to a component you would create a instance of the component like
const NewFoo = bar(Foo);
which you could now use like
return (
<NewFoo {...somePropsHere} />
)
Additionally you could allow the HOC to take a default component and export that as a default component and use it elsewhere like
function bar(Foo = MyComponent) {
and then create an export like
const wrapMyComponent = Foo();
export { wrapMyComponent as MyComponent };
A typical use-case of an HOC could be a HandleClickOutside functionality whereby you would pass a component that needs to take an action based on handleClickOutside functionality
Another way could be like this:
Make a Foo Component
class Foo extends React.Component {
render() {
return ( < h1 > hello I am in Foo < /h1>)
}
}
Make a HOC component.
class Main extends React.Component {
constructor(props) {
super(props);
}
render() {
const {
component, props
} = this.props;
//extract the dynamic component passed via props.
var Component = component;
return ( < div >
< h1 > I am in main < /h1>
< Component {...props} > < /Component>
</div > );
}
}
ReactDOM.render( < Main component = {
Foo
} > < /Main>,
document.getElementById('example')
);
Working code here
Yes you can
const bar = (Foo) => {
return class MyComponent extend Component {
render() {
return <Foo {...this.props} />
}
}
}
//Our Foo Component Code Here
export default bar(Foo)
But again it depends on the functionality. Eg: suppose you're using react router and want to check if user is present before rendering the component don't pass the HOC. eg:
<Route path="/baz" component={auth(Foo)} />
Instead use an new component.
Note: NewComponent is connected to redux and user (state) is passed as props
class NewRoute extends Component{
render(){
const {component:Component, ...otherProps} = this.props;
return(
<Route render={props => (
this.props.user? (
<Component {...otherProps} />
):(
<Redirect to="/" />
)
)}
/>
);
}
}
Then on the routes
<NewRoute path='/foo' component={Foo} />

Setup react route to use URL parameters and props' function

I've got a parent component with react-router, setup like this :
constructor(props){
super(props);
this.state = {
diner: false
};
this.updateFromInvite = this.updateFromInvite.bind(this);
}
updateFromInvite(Souper) {
this.setState({diner: Souper});
}
I can't figure out how to setup the route to have both URL parameters and be able to pass a function to update the parent's state from the children component...
<Route path="/Invitation/:NomParam1?/:NomParam2?"
component = {() => (<Invitation updateApp = {this.updateFromInvite} />)} />
I think it's the closest I got...
From children's component :
class Invite extends Component {
constructor(props){
super(props);
this.state = {
diner: this.props.match.params.NomParam1 ,
JSONInfo: this.props.match.params.NomParam2
};
}
componentDidMount() {
const { diner } = this.state;
const { JSONInfo } = this.state;
const { updateApp } = this.props;
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
export default Invite;
The component property of the route takes a component Class, not an instance of the component. I believe you are looking to use the render property, which takes a rendered component. Your visual component shouldn't be concerned with the routing details, so you can pass that in in the Route configuration like so:
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={({match}) => (
<Invitation
updateApp={this.updateFromInvite}
diner={match.params.NomParam1}
JSONInfo={match.params.NomParam2}
/>
)}
/>
Then, in the component, don't utilize state, as that's not really what it is for:
class Invite extends Component {
componentDidMount() {
const { diner, JSONInfo, updateApp } = this.props;
// Not exactly sure what is going on here... how you
// will use JSONInfo, etc
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
Also, I'm not exactly sure what the parent component is doing, and why it is passing both the route params and the function down to the child, only to have the child call it back... but that is probably out of the scope of the question.
Enjoy!
If finally got it (thanks to that answer and the official documentation):
I needed to add props as parameter of my render and
use it with {...props} inside the children element!
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={ (props) =>
(<Invitation updateApp = {this.updateFromInvite} {...props} />)
}
/>
With that, I have access to BOTH :
my custom props
generic props (match, location and history)

Categories