mobx is not tracking observable property - reactjs

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?

Related

Can I use one mobx store in two different components in react-native app?

I have a module with topTabNavigator that wrapped with mobx provider with the store:
export class ModuleTeam extends Component {
render() {
return (
<Provider store={store}>
<TopTabNavigator />
</Provider>
);
}
}
And inside of topTabNavigator I have two screens:
export class TopTabNavigator extends Component {
render() {
return (
<Tab.Navigator>
<Tab.Screen name='Team' component={ScreenTeam}/>
<Tab.Screen name='Invites' component={ScreenInvites}/>
</Tab.Navigator>
)
}
}
Can you tell me please is it a good way to share the store between two screens and inject it like this, separate in every component:
#inject('store')
#observer
export class ScreenTeam extends Component {
}
#inject('store')
#observer
export class ScreenInvites extends Component {
}
I know is it a good practise to divide one store on a few, but in my case there is some data that uses on the both screens, and I don't want repeat myself.
You can create different stores, several. example how to do it
const App = () =>
<Provider FirstStore={FirstStore} SecondStore={SecondStore}>
<app stuff... />
</Provider>
const Button = (({ FirstStore, SecondStore, label, onClick }) =>
<button style={{
color: FirstStore.colors.foreground,
backgroundColor: SecondStore.colors.background
}}
onClick={onClick}
>{label}</button>
));
export default inject("FirstStore", "SecondStore")(observer(Button));
or some advanced example
class stores {
this.ModalsStore = new ModalsStore(this);
this.AuthStore = new AuthStore(this);
...
}
const mobxStores = stores();
const App = () =>
<Provider {...mobxStores}>
<app stuff... />
</Provider>

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)

React. How to pass props inside a component defined on a prop?

If we have the following structure on a React application:
class BasePage extends React.Component {
render() {
return <div>
{this.props.header}
{/*<Header title={this.props.title} />*/}
</div>
}
}
BasePage.defaultProps = {
header: <header>Base Page</header>
}
class Header extends React.Component {
render() {
return <header>
<h1>{this.props.title}</h1>
</header>
}
}
class TestPage extends BasePage {
}
TestPage.defaultProps = {
header: <Header />
}
class Root extends React.Component {
render() {
return <div>
<TestPage
title="Test Page Title"
/>
</div>
}
}
ReactDOM.render(
<Root />,
document.getElementById('root')
)
If we have a common component like <Header /> we can pass a title property easily like <Header title={this.props.title} />.
But how can we pass props inside a component if this component is defined as a prop itself?
For example, how can we do something like:
{this.props.header title={this.props.title}}
So it will render the Test Page Title correctly?
Important note: we could overwrite the render method inside the Test component. But the purpose of this question is to solve this problem without doing this.
Firstly, props are read-only and a component should never be update it's own props, so lines like
componentWillMount() {
this.props.header = <header>Base Page</header>
}
should not be used. defaultProps can do what I think you are trying to do:
class BasePage extends React.Component {
render() {
return <div>
{this.props.header}
{/*<Header title={this.props.title} />*/}
</div>
}
}
BasePage.defaultProps = {
header: <header>Base Page</header>
}
Secondly, inheritance is not often done in React. I'm not saying don't do what your'e doing, but take a read of the docs and see if there is perhaps a simpler way to achieve the same goals.
Finally, setting props on components passed as props. There are a couple different ways to do this.
If you pass the Component rather than the <Component /> you can add props like normal:
ChildComponent = (props) => {
const HeaderComponent = props.header
return <HeaderComponent title="..." />
}
ParentComponent = () => <ChildComponent header={Header} />
You can clone the element to override props:
ChildComponent = (props) => {
const HeaderComponent = React.cloneElement(props.header. { title: "..." })
return <HeaderComponent />
}
ParentComponent = () => <ChildComponent header={<Header />} />
NOTE: I have used functional components instead of class components for brevity, but the concepts are the same.
This seems like a great use case for React.cloneElement.
React.cloneElement(this.props.header, { title: this.props.title });
It returns a clone of the component with the new props included.

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

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.

Resources