ReactJS: Click on a button to change component on page - javascript

Say I am rendering a signup component on a page. And I have a button that says submit and login. When I click on the login button I want it to replace the signup component without having to go another page. Just wondering, conceptually how would I implement the onclick handler. Would I need to use react router?

You could use a ternary statement to conditionally render components.
Fist declare some sort of variable in state to handle the component switch:
constructor(props) {
super(props);
this.state = {
login: false
}
}
Then in your click handler for your button you would handle the switching of this variable:
onClick = event => {
this.setState({login: !this.state.login})//sets it to opposite of previous value
}
Then in your render you would implement the ternary statement
render() {
return(
this.state.login ? //if login is true
<SomeComponent
onClick={this.onClick.bind(this)}
/>
: //else
<AnotherComponent
onClick={this.onClick.bind(this)}
/>
)
}
And in both components you would have to have a button:
<button onClick={this.props.onClick}>Switch Component</button>

I would use the react-router-dom Link:
import { Link } from 'react-router-dom';
and the button would look something like this:
<button component={Link} to="/yourpage">

Using Router Library is the right way to navigate between components. Since, you do not want to use routing, you can try something like this:
maintain a state variable to check wether the user is logged in, in the parent component where you want to replace the Signup screen with another screen i.e.
constructor(props) { /* Parent Component Constructor */
super(props);
this.state = {
isLoggedIn : false
}
}
and the onLoginClick in Parent Component method will be:
onLoginClick = () => {
this.setState({ isLoggedIn : true })
}
This value will be set to true, when you click on the Login button in the Login Component(Child Component), The onLoginClick method will be passed as props from parent component i.e.
<Button onClick={this.props.onLoginClick}>Login</Button>
Now use this isLoggedIn state variable in the render function of Parent Component like this:
render() {
return(
{!this.state.isLoggedIn &&
<LoginComponent
onClick={this.onLoginClick.bind(this)}
/>
}
{this.state.isLoggedIn &&
<SecondComponent/>
}
)
}

Related

How to prevent modal from re-rendering when I update the state?

I have a react-bootstrap modal with two inputs inside.
Modal is displayed when showModal property in the state changes to true.
I update the fieldOne property (also inside the state) when input value changes.
So, whenever I enter something in the input, modal flashes (re-renders) as well.
How to I prevent Modal from re-rendering when I update the state?
Maybe you should split your modal with the inputs into two seperate components. That should fix your rerender issues.
If you don't want a re-render use a variable other than state to hold your data:
constructor (props) {
super(props);
this.state = {
input: ''
};
this.holder = '';
}
handleInput(input) {
this.holder = input;
}
submitInput() {
this.setState({input: this.holder})
}
render () {
return (
<input type="text" onChange={(e) => this.handleInput(e.target.value)} onBlur={() => this.submitInput()} />
)
}
The purpose of state is for React to evaluate if the DOM needs to change, and if it does it re-renders.
I hit the same problem - putting a form in a modal resulted in the modal rerendering on each keypress.
I could probably get around this by splitting out the form from the modal, BUT I wanted the modal and the form in the same component because the modal buttons trigger the form save. Yes there's other ways to handle that too like passing the save function between the split modal and the form, but now it's getting messy.
So my solution was to make the form in the modal uncontrolled. This means that changing the field values does not modify state and therefore the modal does not rerender.
Set the Modal's view condition with separate states solves this issue.
Herewith a demo example, I used two seperate states i.e, firstView & secondView
import Modal from 'react-bootstrap/Modal'
import ModalBody from 'react-bootstrap/ModalBody'
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
demoModal: true,
firstView: true,
secondView: false
};
};
render() {
return (
<div>
<Modal scrollable={true} show={this.state.demoModal} fade={false} style={{ display: "block"}}>
<ModalBody>
<div>
{
this.state.firstView ?
<div>
..code
</div>
:
<></>
}
{
this.state.secondView ?
<div>
..code
</div>
:
<></>
}
</div>
</ModalBody>
</Modal>
</div>
);
}
}

how to call functional component in class based component using onClick event?

i want to show my functional component in class base component but it is not working. i made simpletable component which is function based and it is showing only table with some values but i want to show it when i clicked on Show user button.
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.userList = this.userList.bind(this);
}
userList = () => {
//console.log('You just clicked a recipe name.');
<SimpleTable/>
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList} >
Show User List
</Button>
</div>
);
}
}
export default ShowUser;
Why your code is not working
SimpleTable has to be rendered, so you need to place it inside the render method. Anything that needs to be rendered inside your component has to be placed there
On Click can just contain SimpleTable, it should be used to change the value of the state variable that controls if or not your component will be shown. How do you expect this to work, you are not rendering the table.
Below is how your code should look like to accomplish what you want :
import React ,{Component} from 'react';
import Button from '#material-ui/core/Button';
import SimpleTable from "../userList/result/result";
class ShowUser extends Component{
constructor(props) {
super(props);
this.state = { showUserList : false }
this.userList = this.userList.bind(this);
}
showUserList = () => {
this.setState({ showUserList : true });
}
render() {
return (
<div>
<Button variant="contained" color="primary" onClick={this.showUserList} >
Show User List
</Button>
{this.state.showUserList ? <SimpleTable/> : null}
</div>
);
}
}
export default ShowUser;
You can also add a hideUserList method for some other click.
Or even better a toggleUserList
this.setState({ showUserList : !this.state.showUserList});
If you're referring to the method userList then it appears that you're assuming there is an implicit return value. Because you're using curly braces you need to explicitly return from the function meaning:
const explicitReturn = () => { 134 };
explicitReturn(); <-- returns undefined
const implicitReturn = () => (134);
implicitReturn(); <-- returns 134
The problem lies with how you are trying to display the SimpleTable component. You are using it inside the userList function, but this is incorrect. Only use React elements inside the render method.
What you can do instead is use a state, to toggle the display of the component. Like this:
const SimpleTable = () => (
<p>SimpleTable</p>
);
class ShowUser extends React.Component {
constructor(props) {
super(props);
this.state = {showSimpleTable: false};
this.toggle= this.toggle.bind(this);
}
toggle = () => {
this.setState(prev => ({showSimpleTable: !prev.showSimpleTable}));
}
render() {
return (
<div>
<button variant = "contained" color = "primary" onClick={this.toggle}>
Show User List
</button>
{this.state.showSimpleTable && <SimpleTable />}
</div>
);
}
}
ReactDOM.render(<ShowUser />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
The functionality you are looking for is called Conditional Rendering. The onClick prop function is an event handler and events in react may be used to change the state of a component. That state then may be used to render the components. In normal vanilla javascript or jQuery we call a function and modify the actual DOM to manipulate the UI. But React works with a virtual DOM. You can achieve the functionality you are looking for as follows:
class ShowUser extends Component {
constructor(props) {
super(props)
// This state will control whether the simple table renders or not
this.state = {
showTable: false
}
this.userList.bind(this)
}
// Now when this function is called it will set the state showTable to true
// Setting the state in react re-renders the component (calls the render method again)
userList() {
this.setState({ showTable: true })
}
render() {
const { showTable } = this.state
return (
<div>
<Button variant="contained" color="primary" onClick={this.userList}>
Show User List
</Button>
{/* if showTable is true then <SimpleTable /> is rendered if falls nothing is rendered */}
{showTable && <SimpleTable />}
</div>
)
}
}

setState is not changing view

I have a component "BulkActionPanel" that renders some buttons. Buttons are enabled or disabled based on the array property "selectedJobIds" passed as a props from its parent component "Grid". Precisely, if length of props "selectedJobIds" is greater than 0 then buttons are enabled else they are disabled.
I have a callback on "onClick" of all the buttons inside BulkActionPanel component, that sets the selectedJobIds to '0' by calling actionCreator "this.props.removeSelectedJobIds([rowData.id])" and it ensures that buttons are disabled.
Since action creator takes a lot of time (does heavy processing on grid), I am maintaining a local state "disable" inside BulkActionPanel to ensure button gets disabled first and then selectedJobIds state is updated in redux store.
I wrote the code below but buttons are not getting disabled until action creator " this.props.removeSelectedJobIds([rowData.id]);" finishes.
export default class Grid extends Component {
render() {
<BulkActionPanel
actions={this.bulkActions}
selectedJobIds={this.getFromConfig(this.props.config, [SELECTED_ROWS_PATH_IN_GRID_CONFIG])}
/>
<SlickGrid/>
}
}
export default class BulkActionPanel extends Component {
constructor() {
super();
this.state = {
disable: true
}
}
componentWillReceiveProps(nextProps){
if(nextProps.selectedJobIds && nextProps.selectedJobIds.length > 0){
this.setState({disable:false});
}
}
shouldComponentUpdate(nextProps) {
return nextProps.selectedJobIds !== undefined && nextProps.selectedJobIds.length
}
#autobind
onActionButtonClick(action) {
this.setState({disable:true}
, () => {
// Action creator that takes a lots of time
this.props.removeSelectedJobIds([rowData.id]);
}
);
}
#autobind
renderFrequentActions() {
return this.props.actions.frequentActions.map((frequentAction) => (
<button
className="btn btn-default"
key={frequentAction.DISPLAY_NAME}
onClick={() => this.onActionButtonClick(frequentAction)}
disabled={this.state.disable}
>
{frequentAction.DISPLAY_NAME}
</button>
));
}
render() {
const frequentActions = this.renderFrequentActions();
return (
<div className="btn-toolbar bulk-action-panel">
{frequentActions}
</div>
);
}
}
Does it has something to do with parent child relation of Grid and BulkActionPanel component? Leads here is appreciated.
Thanks!
I think your component is not passing this
if(nextProps.selectedJobIds && nextProps.selectedJobIds.length > 0){
this.setState({disable:false});
}
you have in your componentWillReceiveProps
if callback from removeSelectedJobIds isn't fired, state won't be changed, try set state of button like you did, and use reducer to dispatch action when removeSelectedJobIds finished, catch that action and rerender or change what you need.
OR
Use reducer for everything. onclick call actin type that let's you know data in table is rendering, use initail state in reducer to disable btn, when data in table finishes calucating fire action in reducer that send new data to component state

Push state updates directly to conditionally rendered React component?

I'm building a React app and have a tab section, where clicking on a tab will render a specific component.
First, my parent component:
class Interface extends Component {
constructor(props) {
super(props);
this.chooseTab = this.chooseTab.bind(this);
this.state = {
current: 'inventory',
inventory: [],
skills: [],
friends: [],
notifications: {}
};
}
chooseTab(tabID) {
this.setState({ current: tabID });
chooseComponent(tabID) {
if (tabID === 'skills') return Skills;
else if (tabID === 'inventory') return Inventory;
else if (tabID === 'friends') return FriendsList;
}
render() {
const tabID = this.state.current;
const CustomComponent = this.chooseComponent(tabID);
return (
<div className='column' id='interface'>
<div className='row' id='tabs'>
<ActiveTab
current={this.state.current}
tabID='skills'
chooseTab={this.chooseTab}
/>
<ActiveTab
current={this.state.current}
tabID='inventory'
chooseTab={this.chooseTab}
/>
<ActiveTab
current={this.state.current}
tabID='friends'
chooseTab={this.chooseTab}
/>
</div>
<TabBody>
<CustomComponent
data={this.state[tabID]}
notifications={this.state.notifications}
/>
</TabBody>
</div>
);
}
}
Which renders three ActiveTab's and one TabBody:
const ActiveTab = (props) => {
const isActive = props.tabID === props.current ? 'active' : 'inactive';
return (
<button
className={`active-tab ${isActive}`}
onClick={() => props.chooseTab(props.tabID)}
>{props.tabID}
</button>
);
};
const TabBody = (props) => {
return (
<div className='tab-body'>
{props.children}
</div>
);
};
This works fine, and it's clearly an intended way of handling this issue. However, I'd like to be able to move the notifications state object into my FriendsList component (since it's unique to friends) and also trigger a setState in it from another component even if FriendsList is not the component currently rendered by the TabBody (i.e., unmounted).
I'm currently triggering remote state changes using a globally available actions closure where a specific action and setState is defined in the ComponentWillMount() lifecycle method of the target element, and it's executed from whatever component is activating the remote state change. I've left those out of Interface for brevity.
How would you handle this? Is my only option to leave notifications in Interface, define actions there, and let React handle passing props down? Or is there a way to build my tab components and conditional rendering so I can trigger state changes from a separate component to a non-displayed component in one of the tabs, i.e move notifications and its corresponding action to FriendsList?
I've passed through a problem similar than yours weeks ago, if you are not decided to adopts some state manager like Redux, MobX or even Flux I think you should pass props down to their child's.

React/Redux, Reloading a component in route

I have the typical list of rows in a basic CRUD screen, each row, as usual with the link:
<Link to={"/appointment/"+appointment.id+"/"}>Edit</Link>
My route is:
<Route path="/appointment/:id" component={AppoModal} />
when I click "Edit" in any row a Modal dialogue appears:
If I do click in the first "Edit" link all works fine. But if I push the "Close" button in the dialogue and try to click any "Edit" link again, the modal dialogue is not launched, I guess this is happening because the component is already "up".
The hide/show behaviour of the dialogue is controlled by this.state.showModal value in the AppoModal component:
constructor(props) {
super(props);
this.state = { showModal: true };
}
So I don't know how to "reload" or "re run" the component. Can I run a dispatch(action) every time I click in the "Edit" link? I heard about a "static method", but I'm too newbie with React to know if that is the path.
Thx!
The problem arises because when you click Close, you're changing the component state, but you're not changing the application state.
Since your modal opens with a route change, it should also close with a route change.
You could take a different approach and avoid the route change all together. Since you are using redux, you could have a global state which could contain a modal name as a constant or maybe contain the reference to the Component.
Now you can have a modal component that would render the component depending on the global state change and you can call this component somewhere in the root Component.
so your reducer looks like
export function modalState(state=null, action) {
if(action.payload.name == "CLOSE_MODAL") return null;
else if([/*modal names*/].includes(action.payload.name) {
return {modal: action.payload.name, .data: action.payload.data}
} else return {...state}
}
and you have an action like
export function openModal(name, data) {
return {
type: "MODAL_NAME",
payload: { name, data }
}
export function closeModal() {
return { type: "CLOSE_MODAL", payoad: null }
}
and your component could look like
const componentMaps = {
[MODAL_1] : import MODAL_1 from "./modals/Modal_1.jsx"
}
cont Modal = React.createClass({
render: function() {
let Component = componentMaps[this.props.modal.name]
if(Component) {
return <Component {...this.props.modal.data}/>
} else {
return null;
}
}
});
export connect(select)(Modal);

Categories