Using React Router from a non-react class - javascript

I have a manager class in my app that is just a regular JS class, it's purely logical with no view. I use an instance of it in several of my components. For one of it's actions I'd like it to change the route in my React based app. I know I can return something to each component and then have the component do the routing, but I'd like to know if there is a way to do it from my non-react class. Is there maybe a way to pass in the history object and push the new route to it?
Thanks for any help.
EDIT: It's more of a theoretical/methodolgy question but for example:
Manager class:
class MyManagerClass {
constructor() {
this.data = {...some internal data};
}
doSomething(param) {
if(this.data[param]) {
this.data[param]();
} else {
//here i'd like to change the route in my react app
}
}
}
Component:
import React, {useContext, useEffect, useRef, useState} from "react";
const MyComponent = props => {
const myManagerClass = new MyManagerClass();
useEffect(() => {
myManagerClass.doSomething('param');
}, [props.something]);
return (
<div>Component</div>
)
}

There are minimum 2 solutions to this
SOLUTION 1:
You can pass the history object to the action as argument from the component
class Util {
static someAction(history) {
history.push('/someurl');
}
}
and call it like
const Comp = () => {
const history = useHistory();
const someHandler = () => {
Util.someAction(history);
}
...
}
SOLUTION 2:
Use a custom history for Router and then you can import history directly into the class file
history.js
import {createBrowserHistory} from 'history';
export const history = createBrowserHistory();
use it for Router like
import { Router } from 'react-router-dom';
import { history } from './path/to/history';
...
return (
<Router history={history}>{/* Routes here */}</Router>
)
and then in your Class file
import {history} from '/path/to/history';
class Util {
static someAction() {
history.push('/someurl');
}
}

Related

Alternative for useDispatch hook of react-redux for Class Component in React

react-redux library has come up with useDispatch, which being a hook can only be used in function component.
import React from 'react';
import {useDispatch} from 'react-redux';
import {action} from './actions';
const funComp = () => {
const dispatch = useDispatch();
const performAction = () => { dispatch(action()) }
return <button onClick={performAction}> Dispatch Action </button>
}
This simplifies a lot of boiler-plate code for functional component, is there something similar which we can use in class component?
react-redux library came with latest useDispatch hook using which we can directly dispatch actions from a functional componentreact community as a whole seems to encourage this pattern of directly dispatching actions from dispatch variable instead of importing those dispatchers as props on the function component, with the help of mapDispatchToPropsWe can achieve similar functionality for class component by importing the store and dispatching actions with its dispatch function, and it's no-brainer since useDispatch hook internally does the same thing. so in a class component same can be achieved like this
An alternative to useDispatch hook for class component
import React from "react";
import { incrementAction } from "./actions";
import store from "./store";
export class StoreFriend extends React.Component {
handleStoreIncrement = () => {
store.dispatch(incrementAction()); // <--- this is the trick.
};
render() {
return (
<div>
<button onClick={this.handleStoreIncrement}>store increment</button>
</div>
);
}
}
Hacky Bonus: narrow use case alternative of useSelector for class component
This is only applicable in case the component is aware when the value it requires from redux store is going to updateSee the hacky solution below
export class StoreFriend extends React.PureComponent {
handleStoreIncrement = () => {
store.dispatch(incrementAction());
this.forceUpdate(); // <---This is the hack, everytime class component dispatches the action, it should be forcefully re-rendered to get new value from store
};
render() {
const s = store.getState().value;
return (
<div>
<p>Comp shows store value --> {s}</p>
<button onClick={this.handleStoreIncrement}>store increment</button>
</div>
);
}
}
code sandbox link https://codesandbox.io/s/reactredux-qp05m?file=/Page.js
Class components should use the React-Redux connect API to interact with the store:
import React from 'react';
import {useDispatch} from 'react-redux';
import {action} from './actions';
const mapState = state => {
return {
todos: state.todos
}
};
const mapDispatch = {action};
class MyClassComponent extends React.Component {
render() {
const {todos} = this.props; // use to render something
return <button onClick={this.props.action}> Dispatch Action </button>
}
}
export default connect(mapState, mapDispatch)(MyClassComponent);

React Redux Life Cycle Confussion

I have been reading several documents and watching videos regarding React Redux, but since all of them are different I wasn't able to apply that knowledge to some real project.
I will try to enumarate the process in order to use React Redux together.
Directory Structuring
project
src
components
User
index.js (Container component)
page.js (Presentational component)
actions
users.js
index.js (exports actionCreators combination)
reducers
users.js
index.js (exports reducer combination with combineReducers
constants
actionTypes.js
services
users.js
index.js
store.js
public
index.html
Redux Setup
We create constants in project/src/constants/actionTypes.js:
export const CREATE_USER = 'CREATE_USER';
export const DELETE_USER = 'DELETE_USER';
export const UPDATE_USER = 'UPDATE_USER';
We create actionCreators en project/src/actions/users.js y luego se combinan en project/src/actions/index.js:
users.js
import { CREATE_USER } from '../constants/actionTypes';
export default function createUser(user) {
type: CREATE_USER,
user
}
index.js
import { createUser } from './users';
export default {
createUser
}
We create reducers in project/src/reducers/users.js and they are combined in project/src/reducers/index.js using combineReducers():
users.js
import { CREATE_USER, UPDATE_USER, DELETE_USER } from '../constants/actionTypes';
import { createUser } from '../services/users';
const initialState = {
name: '',
password: '',
email: ''
}
export default function users(state = initialState, action) {
switch (action.type) {
case CREATE_USER:
state = createUser(action.user);
return state;
}
}
index.js
import users from './users';
export default combineReducers({
users
})
We create store in project/src/store.js:
import { createStore } from 'redux';
import reducers from './reducers';
export const store = createStore(reducers);
React Redux Setup
We wrap component application <Provider> in project/src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { store } from './store';
const Root = () => (
`
<Provider store={store}>
<App />
</Provider>
`
)
ReactDOM.render(Root, document.getElementById('root');
We transform component state to properties with mapStateToProps in project/src/components/User/index.js:
import React, { Component } from 'react';
import { createUser } from '../../actions/users';
import Page from './page';
class User extends Component {
render() {
return <Page users={this.props.users} />
}
}
const mapStateToProps = state => ({
users: this.props.users
// what is mapped here?
});
const mapDispatchToProops = dispatch => ({
// what about here?
});
export default connect(mapStateToProps, mapDispatchToProps)(User);
So, the question would be, is this React-Redux cycle well formed? What is missing or wrong?
Yes, the folder structure works well. As for the "fetch" or "service" functionality you're talking about, I'll give you an example of what actions and reducers both should, in a basic example, do.
So if you're working with a backend which you're "fetching" anything from, I'd recommend adding that functionality in the action, not the reducer:
import { USERS_FETCHED } from '../constants/actionTypes';
import { baseUrl } from "../constants/baseUrl";
const usersFetched = users => ( { // action to dispatch
type: USERS_FETCHED,
users,
} );
export const fetchUsers = () => ( dispatch ) => { // export for mapDispatchToProps
request( `${ baseUrl }/users` )
.then( response => {
dispatch( usersFetched( response.body ) ); // dispatch the action to reducer
} )
.catch( console.error );
}; // in your case you import createUser(), but it works either way
Now the action is concerned with functionality, in contrast the reducer is only concerned with managing the Redux state:
import { USERS_FETCHED } from "../constants/actionTypes";
export default ( state = null, action = {} ) => {
switch ( action.type ) {
case USERS_FETCHED:
return action.users;
default:
return state;
}
};
Functionality in the reducer is fine, but it should only be concerned with managing state. You can imagine how cluttered the code could get if you start fetching any data here, not to mention problems with asynchronicity. Of course, this is just one way to do it, but it works solidly. Hope this helps you in some way.

Use react-ga for tracking the visitor of your home page

i need to integrate my react application using google analytics. I found the react-ga library which looks reliable and "easy" to use. But i didn't use google analytics in the past and i'm having some dificulties. First of all i use the withTracker component i the demo page of react-ga github project and then in my router i wrap my homepage component with this wrapper. I need to make a pretty simple task but i can't find how. I need to track the number of visitors that hit the homepage. The withTracker component is this:
import React, { Component } from 'react';
import ReactGA from 'react-ga';
const withTracker = (WrappedComponent, options = {}) => {
const trackPage = (page) => {
ReactGA.set({
page,
options
});
ReactGA.pageview(page);
};
class HOC extends Component {
componentDidMount() {
const page = this.props.location.pathname;
trackPage(page);
}
componentWillReceiveProps(nextProps) {
const currentPage = this.props.location.pathname;
const nextPage = nextProps.location.pathname;
if (currentPage !== nextPage) {
trackPage(nextPage);
}
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return HOC;
};
export default withTracker;
and my homePage is this:
import React, { PropTypes } from 'react';
import Footer from './header/footer';
const Main= props => (
<div>
<Footer/>
</div>
);
export default MainExperienceComponent;
Can you help me deal with this issue? Thanks a lot

MobX + React Native : way to inject stores

I'm trying to work with MobX for a new project.
I started it on May 2017, and everything was working well. I had to stop, and now I go on developing it. I had to make an npm install to manage making it working, but now, I have some problem with stores...
I rely on this tutorial for the structure : https://blog.callstack.io/write-react-native-apps-in-2017-style-with-mobx-e2dffc209fcb
This is my structure :
Main index.js
import { Provider } from 'mobx-react';
import Stack from './router';
import stores from './stores';
export default class App extends Component {
render() {
return (
<Provider {...stores}>
<Stack />
</Provider>
);
}
}
Index.js of my stores in ./stores/index.js
import ChatStore from './ChatStore';
import UserStore from './UserStore';
export default {
UserStore: new UserStore(),
ChatStore: new ChatStore(),
};
./stores/UserStore.js (important parts)
import { observer, inject } from 'mobx-react';
import {autobind} from 'core-decorators';
...
#inject(['ChatStore'])
#observer
#autobind
export default class UserStore {
#observable isAuthenticated = false;
#observable isConnecting = false;
#observable user = null;
#observable messages = [];
#observable hasMoreMessages = false;
#observable skip = 0;
...
login() {
const payload = {
strategy: 'local',
material_id: DeviceInfo.getManufacturer(),
password: DeviceInfo.getManufacturer()
};
return this.authenticate(payload);
}
...
Now, for components part :
Router.js
import { StackNavigator } from 'react-navigation';
import Home from './containers/Home';
const stackNavigatorConfig = {
initialRouteName: 'Home',
};
export default StackNavigator({
Home: {
screen: Home,
},
}, stackNavigatorConfig);
./containers/Home.js
import React, { Component } from 'react';
import { AsyncStorage } from 'react-native';
import { observable } from 'mobx';
import { observer, inject } from 'mobx-react';
#inject('UserStore')
#observer
export default class Home extends Component {
props: Props;
...
render() {
this.props.UserStore.login().catch(error => {
console.log('LOGIN', 'ERROR', JSON.stringify(error), error.message);
});
return {
...
}
}
And then, I get an error :
So, I sum up :
I use <Provider> from MobX, to give all my stores to my app
Then, I get the Store I want in my component with #inject
I use it as a props, using this.props.UserStore...
But it does not work. I rely on this tutorial for the structure : https://blog.callstack.io/write-react-native-apps-in-2017-style-with-mobx-e2dffc209fcb
Maybe there was an update between May 2017 and today, that makes things different... It was working well on May 2017.
I think this is a dummy error, but I can't find which one...
Everything looks good except the decorators on your UserStore class: #inject(['ChatStore']) #observer #autobind. #inject(['ChatStore']) #observer is used on React components, #autobind might still work as intended.
It should work if you remove those.
maybe worth using #action from mobx

How to manually invoke Link in React-router?

I have a component that receives through props a <Link/> object from react-router. Whenever the user clicks on a 'next' button inside this component I want to invoke <Link/> object manually.
Right now, I'm using refs to access the backing instance and manually clicking on the 'a' tag that <Link/> generates.
Question: Is there a way to manually invoke the Link (e.g. this.props.next.go)?
This is the current code I have:
//in MasterPage.js
var sampleLink = <Link to="/sample">Go To Sample</Link>
<Document next={sampleLink} />
//in Document.js
...
var Document = React.createClass({
_onClickNext: function() {
var next = this.refs.next.getDOMNode();
next.querySelectorAll('a').item(0).click(); //this sounds like hack to me
},
render: function() {
return (
...
<div ref="next">{this.props.next} <img src="rightArrow.png" onClick={this._onClickNext}/></div>
...
);
}
});
...
This is the code I would like to have:
//in MasterPage.js
var sampleLink = <Link to="/sample">Go To Sample</Link>
<Document next={sampleLink} />
//in Document.js
...
var Document = React.createClass({
render: function() {
return (
...
<div onClick={this.props.next.go}>{this.props.next.label} <img src="rightArrow.png" /> </div>
...
);
}
});
...
React Router v6 - React 17+ (updated 01/14/2022)
import React, {useCallback} from 'react';
import {useNavigate} from 'react-router-dom';
export default function StackOverflowExample() {
const navigate = useNavigate();
const handleOnClick = useCallback(() => navigate('/sample', {replace: true}), [navigate]);
return (
<button type="button" onClick={handleOnClick}>
Go home
</button>
);
}
Note: For this answer, the one major change between v6 and v5 is useNavigate is now the preferred React hook. useHistory is deprecated and not recommended.
React Router v5 - React 16.8+ with Hooks
If you're leveraging React Hooks, you can take advantage of the useHistory API that comes from React Router v5.
import React, {useCallback} from 'react';
import {useHistory} from 'react-router-dom';
export default function StackOverflowExample() {
const history = useHistory();
const handleOnClick = useCallback(() => history.push('/sample'), [history]);
return (
<button type="button" onClick={handleOnClick}>
Go home
</button>
);
}
Another way to write the click handler if you don't want to use useCallback
const handleOnClick = () => history.push('/sample');
React Router v4 - Redirect Component
The v4 recommended way is to allow your render method to catch a redirect. Use state or props to determine if the redirect component needs to be shown (which then trigger's a redirect).
import { Redirect } from 'react-router';
// ... your class implementation
handleOnClick = () => {
// some action...
// then redirect
this.setState({redirect: true});
}
render() {
if (this.state.redirect) {
return <Redirect push to="/sample" />;
}
return <button onClick={this.handleOnClick} type="button">Button</button>;
}
Reference: https://reacttraining.com/react-router/web/api/Redirect
React Router v4 - Reference Router Context
You can also take advantage of Router's context that's exposed to the React component.
static contextTypes = {
router: PropTypes.shape({
history: PropTypes.shape({
push: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired
}).isRequired,
staticContext: PropTypes.object
}).isRequired
};
handleOnClick = () => {
this.context.router.push('/sample');
}
This is how <Redirect /> works under the hood.
Reference: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Redirect.js#L46,L60
React Router v4 - Externally Mutate History Object
If you still need to do something similar to v2's implementation, you can create a copy of BrowserRouter then expose the history as an exportable constant. Below is a basic example but you can compose it to inject it with customizable props if needed. There are noted caveats with lifecycles, but it should always rerender the Router, just like in v2. This can be useful for redirects after an API request from an action function.
// browser router file...
import createHistory from 'history/createBrowserHistory';
import { Router } from 'react-router';
export const history = createHistory();
export default class BrowserRouter extends Component {
render() {
return <Router history={history} children={this.props.children} />
}
}
// your main file...
import BrowserRouter from './relative/path/to/BrowserRouter';
import { render } from 'react-dom';
render(
<BrowserRouter>
<App/>
</BrowserRouter>
);
// some file... where you don't have React instance references
import { history } from './relative/path/to/BrowserRouter';
history.push('/sample');
Latest BrowserRouter to extend: https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/BrowserRouter.js
React Router v2
Push a new state to the browserHistory instance:
import {browserHistory} from 'react-router';
// ...
browserHistory.push('/sample');
Reference: https://github.com/reactjs/react-router/blob/master/docs/guides/NavigatingOutsideOfComponents.md
React Router 4 includes a withRouter HOC that gives you access to the history object via this.props:
import React, {Component} from 'react'
import {withRouter} from 'react-router-dom'
class Foo extends Component {
constructor(props) {
super(props)
this.goHome = this.goHome.bind(this)
}
goHome() {
this.props.history.push('/')
}
render() {
<div className="foo">
<button onClick={this.goHome} />
</div>
}
}
export default withRouter(Foo)
In the version 5.x, you can use useHistory hook of react-router-dom:
// Sample extracted from https://reacttraining.com/react-router/core/api/Hooks/usehistory
import { useHistory } from "react-router-dom";
function HomeButton() {
const history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
}
https://github.com/rackt/react-router/blob/bf89168acb30b6dc9b0244360bcbac5081cf6b38/examples/transitions/app.js#L50
or you can even try executing onClick this (more violent solution):
window.location.assign("/sample");
Answers here are outdated.
React Router 6
useHistory is deprecated v6 uses the useNavigate hook instead.
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
navigate(`/somewhere`, { replace: true })
Ok, I think I was able to find a proper solution for that.
Now, instead of sending <Link/> as prop to Document, I send <NextLink/> which is a custom wrapper for the react-router Link. By doing that, I'm able to have the right arrow as part of the Link structure while still avoiding to have routing code inside Document object.
The updated code looks like follows:
//in NextLink.js
var React = require('react');
var Right = require('./Right');
var NextLink = React.createClass({
propTypes: {
link: React.PropTypes.node.isRequired
},
contextTypes: {
transitionTo: React.PropTypes.func.isRequired
},
_onClickRight: function() {
this.context.transitionTo(this.props.link.props.to);
},
render: function() {
return (
<div>
{this.props.link}
<Right onClick={this._onClickRight} />
</div>
);
}
});
module.exports = NextLink;
...
//in MasterPage.js
var sampleLink = <Link to="/sample">Go To Sample</Link>
var nextLink = <NextLink link={sampleLink} />
<Document next={nextLink} />
//in Document.js
...
var Document = React.createClass({
render: function() {
return (
...
<div>{this.props.next}</div>
...
);
}
});
...
P.S: If you are using the latest version of react-router you may need to use this.context.router.transitionTo instead of this.context.transitionTo. This code will work fine for react-router version 0.12.X.
React Router 4
You can easily invoke the push method via context in v4:
this.context.router.push(this.props.exitPath);
where context is:
static contextTypes = {
router: React.PropTypes.object,
};
If you'd like to extend the Link component to utilise some of the logic in it's onClick() handler, here's how:
import React from 'react';
import { Link } from "react-router-dom";
// Extend react-router-dom Link to include a function for validation.
class LinkExtra extends Link {
render() {
const linkMarkup = super.render();
const { validation, ...rest} = linkMarkup.props; // Filter out props for <a>.
const onclick = event => {
if (!this.props.validation || this.props.validation()) {
this.handleClick(event);
} else {
event.preventDefault();
console.log("Failed validation");
}
}
return(
<a {...rest} onClick={onclick} />
)
}
}
export default LinkExtra;
Usage
<LinkExtra to="/mypage" validation={() => false}>Next</LinkExtra>
again this is JS :) this still works ....
var linkToClick = document.getElementById('something');
linkToClick.click();
<Link id="something" to={/somewhaere}> the link </Link>

Categories