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

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

Related

Side effect function is not getting called in Cmd.run using redux-loop

I am working on a react redux application where in, on a button click I need to change my window location.
As of now, I am dispatching the button click action and trying to achieve the navigation in reducer using redux-loop.
Component js
class Component {
constructor() {
super()
}
render() {
return (
<button onClick={() => this.props.onButtonClick()}>Navigate</button>
)
}
}
const mapDispatchToProps = (dispatch) => {
return {
"onButtonClick": () => dispatch(handleClick())
};
}
Action js
export const handleClick = () => ({
type: NAVIGATE
});
Reducer js
export default (state = {}, action) => {
if (action.type === NAVIGATE) {
return loop(state, Cmd.run(navigateTo));
}
};
Effect js
export const navigateTo = () => {
window.location = "https://www.stackoverflow.com";
}
Apart from this action, I have lot many actions that involve side effect as well as state manipulation, hence redux-loop.
I have two questions:
Control is not going into navigateTo() on button click. What am I doing wrong?
I feel reducer is not a right place for it as we are not manipulating state here.
What would be the best place to put this piece of code when button click action is dispatched?
the code you have looks correct. Did you use the store enhancer when creating your redux store? Did you try setting a breakpoint in your reducer and verifying it gets called as you expect? https://redux-loop.js.org/docs/tutorial/Tutorial.html

Is it correct to use window.location.reload with React?

When you open reactjs.org, under "Declarative" header, there is a sentence: React will efficiently update and render just the right components when your data changes.
For a couple of my apps, I'm using the following structure:
App
| AppContainer(all the app logic, protected before login)
| Login(Login form)
This structure works well if you return 2 different components inside App's render, according to the user's credentials.
render(){
if(isUserLoggedIn()){
return <AppContainer />;
}
return <Login />;
}
Inside the Login component, I'm refreshing the page with window.location.reload so the App's render will be triggered, and I'll get the AppContainer component.
But it feels a little like jQuery + Angular. Is there a better (more React) way to trigger render function, or is this how things should be?
Is there a better(more React) way to trigger render function...
The usual way is to have state, in this case at minimum a boolean for whether the user is logged in, and update that state when the user logs in successfully or logs out. Updating state triggers rendering.
In your case, since you're using Redux, you'd probably have your state there.
I don't use Redux (yet?), this is vaguely what it would look like without, roughly (if you're using a class component as you seem to be):
class App extends Component {
constructor(props) {
super(props);
this.state = {
loggedIn: /*...initial value, perhaps from web storage or cookie...*/;
};
this.onLogin = this.onLogin.bind(this);
this.onLogout = this.onLogout.bind(this);
}
onLogin() {
// ...probably stuff here, then:
this.setState({loggedIn: true});
}
onLogout() {
// ...probably stuff here, then:
this.setState({loggedIn: false});
}
render() {
if (this.state.logedIn) {
return <AppComponent onLogout={this.onLogout}/>;
}
return <Login onLogin={this.onLogin}/>;
}
}
or with hooks:
const App = () => {
const [loggedIn, setLoggedIn] = useState(/*...initial value, perhaps from web storage or cookie...*/);
const onLogin = useCallback(() => {
// ...probably stuff here, then:
setLoggedIn(true);
}, [loggedIn]);
const onLogout = useCallback(() => {
// ...probably stuff here, then:
setLoggedIn(false);
}, [loggedIn]);
if (this.state.logedIn) {
return <AppComponent onLogout={onLogout}/>;
}
return <Login onLogin={onLogin}/>;
}
(again, roughly)
If you need to update the component state, then you can pass an observable and listen for changes or use some state management library.
Here is one possible solution:
Create observable class
declare type IObserverHandler = (event: any) => void;
export class Observable {
private observers: IObserverHandler[] = [];
public subscribe(observer: IObserverHandler) {
if (!this.observers.includes(observer)) {
this.observers.push(observer);
}
}
public unsubscribe(observer: IObserverHandler) {
this.observers = this.observers.filter(o => o !== observer);
}
public publish(event: any) {
for (const observer of this.observers) {
observer(event);
}
}
}
Create Login class that will publish events on actions such as login or logout
class Login extends Observable {
public login() {
this.publish({ value: true });
}
public logout() {
this.publish({ value: false });
}
}
In component subscribe to observer and update component state using event value
export abstract class Component extends React.Component<any, any> {
private observer: IObserverHandler;
private observable: Login;
constructor(props: any) {
super(props);
this.observable = this.props.observable;
this.state = { isAuthenticated: false }
this.observer = (event) => {
this.setState({ isAuthenticated: event.value })
}
this.observable.subscribe(this.observer);
}
componentWillUnmount() {
this.observable.unsubscribe(this.observer);
}
}
You can use useNavigate and navigate to the same url you are on. For example, instead of window.location.reload(), you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error
Creating some extra state and tracking them for re-rendering your page might unnecessarily complicate your code.

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 call specific function when page refreshed or closed

I have a component like:
import React, { PropTypes, Component } from 'react'
class MyView extends Component {
componentDidMount() {
window.addEventListener('onbeforeunload', this.saveState())
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.saveState())
}
saveState() {
alert("exiting")
}
render() {
return (
<div>
Something
</div>
)
}
}
export default MyView
Here when a user refresh the page I want to call a specific funtion and when call is finished I want the page to be refreshed. Same when user closes the page.
In my above code I am adding an event listener onbeforeunload and calling a saveState function.
But here my componentDidMount is working normally. onbeforeunload and saveState function is called normally when page is loaded not when page is refreshed or exited.
What is wrong in here and how can I call specific funcitn or give alert when someone exits or refresh the page in react ?
In the above code
Attach beforeunload event on top level component and on beforeunload event make render empty which will trigger componentWillUnmount of all child components.
import React, { PropTypes, Component } from 'react'
class App extends Component {
componentDidMount() {
window.addEventListener('beforeunload', () =>{
this.setState({appended:true});
});
}
render() {
if(this.state.appended){
return false;
}else{
return (<MyView />)
}
}
}
class MyView extends Component {
componentWillUnmount() {
this.saveState()
}
saveState() {
alert("exiting")
}
render() {
return (
<div>
Something
</div>
)
}
}
export default MyView
Hope this work for you.
According to this, try this syntax :
componentDidMount() {
window.addEventListener('onbeforeunload', this.saveState)
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.saveState)
}

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