React/Redux, Reloading a component in route - javascript

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);

Related

skip re-render using shouldComponentUpdate and nextState

I have currently a drop-down select to filter some charts after 'Apply'. It works fine.(See screenshot below).
The problem is that when another timespan gets selected, React does a re-render to all charts before I click 'Apply' button.
I want to avoid this unnecessary re-render by implementingshouldComponentUpdate, but I can't figure out how.
Below what I tried but it did not work(still a re-render):
shouldComponentUpdate(nextState) {
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
But it always return true, because nextState.timespanState is undefined. Why?
Drop-down Select
<Select value={this.state.timespanState} onChange={this.handleTimeSpanChange}>
handleTimeSpanChange = (event) => {
this.setState({ timespanState: event.target.value });
};
constructor(props) {
super(props);
this.state = { timespanState: 'Today'};
this.handleTimeSpanChange = this.handleTimeSpanChange.bind(this);
}
You're on the right track with using shouldComponentUpdate, it's just that the first parameter is nextProps and the second is nextState, so in your case, the undefined value is actually nextProps with the wrong name.
Change your code to this,
shouldComponentUpdate(nextProps,nextState) { // <-- tweak this line
if (this.state.timespanState !== nextState.timespanState) {
return true;
}
return false;
}
Finally, I solve the problem by separating drop-down selectbox and charts into two apart components and made the drop-down component as a child component from its parent component, charts components.
The reason is the following statement
React components automatically re-render whenever there is a change in their state or props.
Therefore, React will re-render everything in render() method of this component. So keeping them in two separate components will let them re-render without side effect. In my case, any state changes in drop-down or other states in Filter component, will only cause a re-render inside this component. Then passing the updated states to charts component with a callback function.
Something like below:
Child component
export class Filter extends Component {
handleApplyChanges = () => {
this.props.renderPieChart(data);
}
render(){
return (
...
<Button onClick={this.handleApplyChanges} />
);
}
}
Parent component
export class Charts extends Component{
constructor(props){
this.state = { dataForPieChart: []};
this.renderPieChart = this.renderPieChart.bind(this);
}
renderPieChart = (data) => {
this.setState({ dataForPieChart: data });
}
render(){
return (
<Filter renderPieChart={this.renderPieChart} />
<Chart>
...data={this.state.dataForPieChart}
</Chart>
);
}
}
If still any question, disagreement or suggestions, pls let me know:)

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

Why react-router fires push and pop action when replacing path?

I've a react app. In a component I have a Link comp from the react-router.
When I click on the link the router fires a push and a pop action?
Is it normal? This way I don't know how can I solve a problem.
I have a route setup like this: example/:param
and when I'm on this path how can I listen properly for the param segments change?
Code:
class Example extends Component {
fetch(param) {
// ajax stuff
}
componentWillMount() {
this.fetch(this.props.param);
}
componentWillReceiveProps(nextProps) {
this.fetch(nextProps.param);
}
render() {
// render stuff
}
}
const stateToProps = (state, ownProps) => {
return {
param: ownProps.params.param,
};
};
export default connect(stateToProps, {})(Profile);
This way componentWillReceiveProps runs twice beacuse router fires push and the pop action.
Thx for any help/advice,
Akos

Find whether a React component is being displayed or not

I want to call a promise based function before dispatching an action to the store.
The problem is that I only want to call the function when the component is going to be displayed. I use a toggle action that turns the component on and off.
Here is a sample of my code:
if ( /*component is going to be displayed*/) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
Action:
export const SomethingActions = {
TOGGLE_SOMETHING: 'TOGGLE_SOMETHING'
};
export function toggleSomething() {
return {
type: SomethingActions.TOGGLE_SOMETHING
};
}
Reducer:
export default function somethingState(state = defaultState, action) {
switch (action.type) {
case somethingActions.TOGGLE_SOMETHING
return Object.assign({}, state, { open: !state.open});
default:
return state;
}
}
part of the React component:
Something.propTypes = {
display: React.PropTypes.bool.isRequired
};
function mapStateToProps(state, ownProps) {
return {
display: state.something.open
};
}
I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not.
I don't want to pollute the render function or store a bool that changes every time I call dispatch.
Is there a way to do that?
By the sounds of it, you'd want to take advantage of React's lifecycle methods. Particularly the componentWillMount and componentWillReceiveProps.
componentWillReceiveProps does not get triggered for the initial render, so you may want to extract out the logic into a separate function so that it can be reused for both hooks:
function trigger(isDisplayed) {
if (isDisplayed) {
init().then(function() {
store.dispatch(toggleSomething());
});
}
else {
store.dispatch(toggleSomething());
}
}
componentWillMount() {
trigger(this.props.display);
}
componentWillReceiveProps(nextProps) {
trigger(nextProps.display);
}
Q1: "The problem is that I only want to call the function when the component is going to be displayed"
A1: This is definitely a problem for react lifecycle methods, in particular, componentWillMount() & componentDidMount()
Q2: "I basically want to know the value of open/display of the component above or another way to know whether the component is being displayed or not."
A2: The componentDidMount() method will be called when the component is rendered. To prevent an infinite loop where the component calls your promise on render just to call the promise again when the state changes, avoid including the toggled state in your component. Dispatch actions on component mounting that toggle the state in the store, but don't use this state in this component. This way you know whether the component is rendered without having the UI update. I hope that helps!
import React from 'react';
class StackOverFlow extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
toggleSomethingOn();
}
componentWillUnmount() {
toggleSomethingOff();
}
render() {
return (
<div>
The component has been rendered!
<br />
</div>
);
}
}
function toggleSomethingOn() {
//dispatches action to toggle state "open"
}
function toggleSomethingOff() {
//dispatches action to toggle state "closed"
}
export default StackOverFlow;
A2: If you are just looking to find out if a component has been rendered (outside of your code) you could go to your browser's developer tools and search the elements/DOM for your component html.

ReactJS: Click on a button to change component on page

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/>
}
)
}

Categories