Redux-router: Link not triggering re-render, but history.change is - reactjs

I'm looking to make this piece of code work and didn't get it through the docs or the example in the source code in the redux-router project.
I've this code (started with root in /frontend for migration reasons):
class App extends Component {
render() {
const links = [
'/frontend',
'/frontend/season',
'/frontend/episode'
].map(l =>
<p>
<Link to={l}>{l}</Link>
</p>
);
console.log('render');
return (
<div>
<h1>App Container</h1>
{links}
{this.props.children}
</div>
);
}
}
App.propTypes = {
children: PropTypes.node
};
function mapStateToProps(state) {
return {
routerState: state.router
};
}
connect(mapStateToProps)(App);
const rootReducer = combineReducers({
episode,
router: routerStateReducer
});
const store = compose(
reduxReactRouter({ createHistory})
)(createStore)(rootReducer,initialState);
class Root extends Component {
render() {
return (
<div>
<ReduxRouter history={history}>
<Route path="/frontend" component={App}>
<Route name="episode" path="episode" component={EpisodeApp} />
<Route name="season" path="season" component={SeasonApp} />
</Route>
</ReduxRouter>
</div>
);
}
}
React.render(
<Provider store={store}>
{() => <Root/>}
</Provider>
, document.getElementById('root'));
The thing is that when I press the links nothing changes and the App doesn't re-renders its children, but when I go back and forth using the browser navigation it does work. Where am I screwing this up?
Thanks a lot!

Update:
Replace this line:
<ReduxRouter history={history}>
by this one (so removing history object):
<ReduxRouter>
Make it work, not sure why.

Related

How to call a state from a component to a page?

I am creating an example dApp which carries the "Header" component at the top of the page for every page. So, I have created a header component and I make people connect to their MetaMask wallet in Header.tsx, which they do successfully and I keep their wallet ID with currentAccount state.
Header.tsx:
const Header: FunctionComponent<{}> = (props) => {
const [currentAccount, setCurrentAccount] = useState("");
async function checkAccount() {
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' })
setCurrentAccount(accounts[0]);
}
return (
<header>
<div className="col-xl-3 col-lg-3 col-md-3 col-sm-3">
<ul>
<li>{!connectHidden && <button className="buy connect-wallet" onClick={connectWallet}><b>Connect Wallet</b></button>}</li>
</ul>{currentAccount}
<ul>
<li>{!disconnectHidden && <button className="buy connect-wallet" onClick={disconnectWallet}><b>Disconnect Wallet</b></button>}</li>
</ul>
</div>
</header>
);
};
export default Header;
But at my homepage, there are no codes for anything about getting user's wallet ID, I don't want to rewrite the code here as it is not the right way. As a newbie in react, I couldn't make the codes I have tried work like trying to import a function or variables. How do I call the currentAccount state in my home page?
Home.tsx:
const HomePage: FunctionComponent<{}> = () => {
useEffect(() => {
onInit()
return () => { }
}, [])
async function onInit() {
}
async function onClickMint() {
alert("mint");
}
return (
<>
<div>xx
</div>
</>
);
};
export default HomePage;
Here is my app.tsx and as you can see, I am seeing all of the components at once. But I want to use the state I have got at Header component in my Home component.
App.tsx:
import Header from './components/Header';
const App: FunctionComponent<{}> = () => {
return (
<Router>
<Header />
<Route exact path="/" component={HomePage} />
<Route exact path="/wallet" component={Wallet} />
<Footer />
</Router>
);
};
export default App;
Quick answer:
simply create your state at the top level (App.tsx) and give currentAccount, setCurrentAccount as props for the other components
App.tsx:
import Header from './components/Header';
const App: FunctionComponent<{}> = () => {
const [currentAccount, setCurrentAccount] = useState("");
return (
<Router>
<Header />
<Route exact path="/">
<HomePage currentAccount={currentAccount} setCurrentAccount={setCurrentAccount}/>
</Route>
<Route exact path="/wallet">
<Wallet currentAccount={currentAccount} setCurrentAccount={setCurrentAccount}/>
</Route>
<Footer />
</Router>
);
};
export default App;
Longer answer:
You need to inform yourself about redux or simply the useContext hook
For instance with the useContext hook you can create a context that will contain your state and that you will be able to access in any child component without using props which can be redundant when you have multiple children and grandchildren ...
Here you can find the documentation about how to use the useContext Hook :
https://reactjs.org/docs/hooks-reference.html#usecontext

How do I do authorization check on ComponentWillMount?

My website have a few pages that is protected by login. My current solution to this is:
in app.js:
<div className="app">
<Provider store={store}>
<Router history={appHistory} onUpdate={fireTracking}>
<Route name="main" component={AppHandler}>
<Route name="home" path="/" component={HomePageHandler}/>
</Route>
</Router>
</Provider>
</div>
And then my HomePageHandler is:
export default class HomePageHandler extends BaseAuthorizedComponent {
render() {
return (
<div>hello</div>
)
}
}
As the HomePageHandler extends BaseAuthorizedComponent, which is defined as:
class BaseAuthorizedComponent extends Component {
componentWillMount() {
if (!this.props.user.signed_in) {
this.context.router.push('/signin')
}
}
}
HomePageHandler.contextTypes = {
router: React.PropTypes.object.isRequired,
}
function select(state) {
return {
user: state.user,
}
}
export default connect(select)(BaseAuthorizedComponent)
The redux's user object has a flag that indicates if the user is logged in or not. The idea is that on the homepage, before the component is mounted, the BaseAuthorizedComponent would have checked and redirect to signin page if user is not logged in. My idea is to let every page that requires authorization to extend BaseAuthorizedComponent.
However the following error happens when trying to load the homepage:
Error: Could not find "store" in either the context or props of "Connect(BaseAuthorizedComponent)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(BaseAuthorizedComponent)".
No idea how can I fix the problem while keeping the advantage of a single place to check authorization. Any thoughts? Thanks!
First of all you is better to use composition instead of inheritance https://reactjs.org/docs/composition-vs-inheritance.html
Next, you can add "push" action creator from react-router-redux (https://github.com/reactjs/react-router-redux) to mapDispatchToProps function:
function composeAuth = (ComposedComponent) => {
class BaseAuthorizedComponent extends React.Component {
// We use componentDidMount instead of componentWillMount, cause componentWillMount is deprecated https://medium.com/#baphemot/whats-new-in-react-16-3-d2c9b7b6193b
componentDidMount() {
if (!this.props.user.signed_in) {
this.props.push('/signin');
}
}
render() {
if (!this.props.user.signed_in) {
return null;
}
return <ComposedComponent {...this.props} />
}
}
return connect(state => ({user: state.user}), {push})(BaseAuthorizedComponent);
}
class HomePageHandler extends React.Component {
render() {
return (
<div>hello</div>
)
}
}
export default composeAuth(HomePageHandler);
How about this:
class CheckAuth extends React.Component{
state = {
auth: false
}
render(){
return(
{this.state.auth ? <div>Authorized user</div> : <div>Unauthorized user</div>}
)
}
}
function mapStateToProps(state){
return{
auth: state.auth
}
}
export default connect(mapStateToProps)(CheckAuth);
And then include it in your other components like so:
import CheckAuth from './CheckAuth';
...
class Home extends React.Component{
render(){
return(
<div>
<CheckAuth />
Hello world!!
</div>
)
}
}
export default Home;
After more research, the easiest way to satisfy my requirement is:
in an util file:
export function requireAuth(nextState, replace) {
// use your own method to check if user is logged in or not
if (!isLoggedIn()) {
replace({pathname: '/signin'});
}
}
and then import this method in the app.js file and use it:
<div className="app">
<Provider store={store}>
<Router history={appHistory} onUpdate={fireTracking}>
<Route name="main" component={AppHandler}>
<Route name="home" path="/" component={HomePageHandler} onEnter={requireAuth}/>
</Route>
</Router>
</Provider>
</div>
In this way if the user requires auth(isLoggedIn() is false), then it will redirect the page to /signin.
after some research, the best way I've seen is this:
<Route name="name"
path="/path"
component={THeWorkHandler}
onEnter={requireAuth}/>
And the requireAuth is put in a helper file:
export function requireAuth(nextState, replace) {
if (!(//logic to see if user is logged in )) {
replace({pathname: '/user/signin'});
}
}
This way if a onEnter requireAuth determines that the user is not authenticated, it will redirect to /user/signin page.

React-Redux Provider not working

My project uses React-Redux Provider.
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
and
class App extends Component {
componentDidMount(){
API.getCategories().then((categories)=>{
this.props.dispatch(addCategories(categories))
})
API.getAllPosts().then(posts => {
console.log('getAllPosts', posts)
})
}
render() {
return (
<div className="App">
<Route exact path="/" render={()=>{
return (
<div>
{
this.props.categories.map((category)=>{
return (
<Link key={category.name} to={`/category/${category.name}`} params={{category: category.name}} >{category.name}</Link>
)
})
}
</div>
)
}}
/>
<Route path="/category/:category" component={Category} />
</div>
);
}
}
function mapStateToProps(x) {
return {
categories: x.categories
}
}
// export default App;
export default withRouter(connect(
mapStateToProps,
)(App))
From the above code and based on my experience from a previous project, the Category component's this.props should have a dispatch method that I can call the actions with but for some reason it is not there.
This is my Category Component:
class Category extends Component {
componentDidMount(){
console.log('this.props of Category', this.props)
var category = this.props.match.params.category
API.getPosts(category).then((posts)=>{
console.log('after getPosts', posts)
this.props.dispatch(addAllPosts(posts))
})
}
render(){
return <p>Category</p>
}
}
export default Category
What am I missing here???
You need to use the connect function from react-redux on your Category component so it has access to dispatch.
export default connect()(Category)
Also, it might just be simplified for SO, but App does not need to be wrapped in withRouter. This is only required if you need the router props injected into the component. Route does this automatically for any component it renders, which is why you don't need it on Category.
export default connect(mapStateToProps)(App)

How to build a multi-step form flow with react-redux, that changes URLs per step

I'm working to build a multistep form flow where the user first loads: /welcome, which should then take the user through the following
step 0: `/welcome` (Should redirect to step 1)
step 1: `/welcome/workplace`
step 2: `/welcome/profile`
Here is what I have so far:
App.jsx:
import Welcome from '../containers/welcome/Welcome';
const App = ({store}) => {
return (
<StoreProvider store={store}>
<ConnectedRouter history={history}>
<Switch>
<PrivateRoute exact path="/welcome" layout={MainLayout} component={Welcome} />
<WithMainLayout exact path="/" component={Home} />
<AuthRoutes path={`/${clientResourceName}`} wrapper={WithMainLayout} />
<WithMainLayout component={NotFound} />
</Switch>
</ConnectedRouter>
</StoreProvider>
);
};
Welcome.js
import React from 'react';
import WorkPlacePage from '../../components/welcome/WorkPlacePage';
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {
step: 1
};
}
showStep() {
const {history} = this.props
console.log('showStep');
console.log({history})
switch (this.state.step) {
case 1:
return <WorkPlacePage history={history} />
default:
return (
<div>
<h1>Case: Default</h1>
</div>
);
}
}
render() {
var style = {
width : (this.state.step / 4 * 100) + '%'
}
return (
<main>
<span className="progress-step">Step {this.state.step}</span>
<progress className="progress" style={style}></progress>
{this.showStep()}
</main>
)
}
}
export default Welcome;
WorkPlacePage.js
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
class WorkPlacePage extends React.Component {
render() {
console.log('this.props.history')
console.log(this.props.history)
return (
<div>
<h1>Workplace</h1>
<span>
survey things
<button onClick={() => this.props.history.push("/confirmation")}>next page</button>
</span>
</div>
);
}
}
export default WorkPlacePage;
As a react, redux newbie, I could use your advise on the following:
Am I structuring this multi-step form correctly?
If a user loads /welcome in their browser, what is the right way to auto-redirect the browser to step 1 /welcome/workplace
On WorkplacePage.js, I'm getting the following JS error when I click the NEXT button: =Uncaught TypeError: Cannot read property 'push' of undefined --- Why is history not defined?
Thank you for any and all expert help!
yes sure this structure looks ok. I think you'll want to create a separate route component for each step because i'm sure that each step will grow with more rules and validations.
To redirect you can render <Redirect to="/somewhere/else"/> - https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Redirect.md
you need to pass props into the <WorkPlacePage />
something like:
class Welcome extends React.Component {
constructor(props) {
super(props);
this.state = {
step: 1
};
}
render() {
const {history} = this.props
switch (this.state.step) {
case 1:
return <WorkPlacePage history={history} />
case 2:
return (
<div>
<h1>WIP</h1>
</div>
);
default:
return (
<div>
<h1>Case: Default</h1>
</div>
);
}
}
}

mobx is not tracking observable property

I searched many and did everything I see that look necessary to make that obseravle stuff works but somehow mobx lack of watching observable property(menuItems).
I check execution order although fetched data loaded to field fine component renders method not re-running
useStrict(true);
export class AppState {
#observable public menuItems: MenuModel[] = [];
constructor(){
this.setItems()
}
#action setItems(): void {
fetch(`..`).then((response: { value: MenuModel[] }): void => {
debugger
this.menuItems = [
{ Id: 1, itemName: 'test-item1', childItems: [] }
];
}, (error: any): void => {
});
}
root file;
const appState = new AppState();
ReactDOM.render(
<Provider appState={appState}>
<Router history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Empty} />
</Route>
</Router>
</Provider>,
document.getElementById('root') as HTMLElement
);
app component;
#observer
export class App extends React.Component<{children: any, params: any}, {}> {
render() {
return (
<div className="container">
<header className="main-header"></header>
<main>
<aside>
<Menu params = {this.props.params} />
</aside>
</main>
</div>
);
}
};
and Menu Component, I expect the render method below whenever observable field updated.. but it doesnt care.
#inject('appState')
#observer
export class Menu extends React.Component<{params?:MenuModel[], appState?: AppState}, {}> {
render() {
debugger
var menuJSX : JSX.Element[] = this.props.appState ? this.props.appState.menuItems.map((item:MenuModel, i:number)=>{
debugger
return (<li key={item.Id}>{item.itemName}</li>)
}):[];
return (
<div id="menuWrapper">
Data: {menuJSX}
</div>
);
}
}
I dont know if it helps but I share how "menuItems" property looks when it comes to it, after this debugger step it goes to AppState file and set menuItems after this point execution ends, but I expect it to re-render in Menu component?

Resources