I am a bit new to react and redux, but made quite a lot of progress.
I am using redux connect to map state to props. Was working like charm, until I got this situation:
Parent component is using mapStateToProps, and here is the source code for it:
import React from 'react';
import { connect } from 'react-redux'
import { NestedComponent } from './NestedComponent'
class ParentElement extends React.Component {
render() {
return (
<div className="App">
<NestedComponent/>
</div>
);
}
}
const mapStateToProps = state => {
const { questions } = state
return {
questions
}
}
ParentElement.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default connect(mapStateToProps)(ParentElement)
Here is the code for the nested element:
import React from 'react';
import { connect } from 'react-redux'
class NestedComponent extends React.Component {
render() {
return (
<div> I am nested</div>
);
}
};
const mapStateToProps = state => {
const { questions } = state
return {
questions
}
}
NestedComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default connect(mapStateToProps)(NestedComponent)
When I try to show the parent element, I get this error:
Warning: React.createElement: type should not be null, undefined,
boolean, or number. It should be a string (for DOM elements) or a
ReactClass (for composite components). Check the render method of
ParentElement.
What am I missing / doing wrong?
Import header file like this:-
import NestedComponent from './NestedComponent'
Without braces.
When a class is exported as a default then it is imported without braces because there is only one default class in a file but, when the class is exported without default you have to import it with braces because there can be more than one class with export keywords in a file (es6 conventions).
Related
I'm trying to export a component without the decorators (connect() in this case)
for unit testing with jest.
So, how could I do this:
import React, { Component } from 'react';
import { connect } from 'react-redux';
export class Header extends Component {
render(){
return <pre>Header</pre>
}
}
export default connect()(Header);
With this component (the export at the beginning doesn't work, it stills exports the connected component)
export let Header = props => {
render(){
return <pre>Header</pre>
}
}
Header = connect()(Header);
export default Header;
Use different variable for your connected component as the following code:
export let Header = props => {
render(){
return <pre>Header</pre>
}
}
let HeaderConnected = connect()(Header);
export default HeaderConnected;
Now you can import your Header freely without using connect()
This can be done without even changing default export:
export let Header = props => {
render(){
return <pre>Header</pre>
}
}
export default connect()(Header);
There may be no need to export original component for connect alone because most well-designed HOCs expose original component:
import Header from '...';
const OriginalHeader = Header.WrappedComponent;
I am trying to use redux on my react project and I have an issue when the "connect()" method from "react-redux" is used.
I create the store and the reducer I need but when I try to connect my component, nothing displays and it gives me the error.
When I try to connect my component App:
import React from "react";
import PrivateRoute from "react-private-route";
import { connect } from "react-redux";
import { Router, Route, Switch } from "react-router-dom";
import { createBrowserHistory } from "history";
import { Auth } from "aws-amplify";
import indexRoutes from "routes/index";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (<Router>[...]</Router>);
}
}
function mapStateToProps(state) {
console.log(state);
return {
myInfo: state
};
}
const mapDispatchToProps = dispatch => {
return {
test: () => dispatch({ type: "TEST", payload: {} })
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
I get the error:
Element type is invalid: expected a string (for built-in components)
or a class/function (for composite components) but got: object.
As mentioned in the comment the solution is to wrap your app in <Provider></Provider>, which makes the store available to nested components that use connect(). If you are using react router v4 you should also use <ConnectedRouter> instead of the simple router.
A good working example can be found in their Github docs
import { ConnectedRouter } from 'connected-react-router'
import { Provider } from 'react-redux'
<Provider store={store}>
<ConnectedRouter history={history}> { }
...
</ConnectedRouter>
</Provider>
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
I'm trying to setup a project architecture using MobX and React and was wondering if doing this following would be considered "not bad". I don't want this question to end up being another "this is a matter of personal preference and so this question doesn't belong here... We can all agree that some things really are bad.
So I'm thinking of only having a single Store.js file that looks something like this:
import { observable, action, useStrict } from 'mobx';
useStrict(true);
export const state = observable({
title: ''
});
export const actions = {
setTitle: action((title) => {
state.title = title;
})
};
Note: that all application state will be in state, there will only be a single store.
I then use state in my root component a.k.a App.js like so:
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { state } from './Store';
import DisplayTitle from './components/DisplayTitle/DisplayTitle';
import SetTitle from './components/SetTitle/SetTitle';
class App extends Component {
render() {
return (
<div className="App">
<DisplayTitle title={state.title}/>
<SetTitle />
</div>
);
}
}
export default observer(App);
I'll obviously have a whole bunch of components in my app, but none of the components will ever read state directly from Store.js. Only my App.js will import the state and pass it down the component tree.
Another note: I'm not so sure anymore why other components can't read the state directly from Store.js...
This is the case with the DisplayTitle component:
import React, { Component } from 'react';
class DisplayTitle extends Component {
render () {
return (
<h1>{this.props.title}</h1>
);
}
}
export default DisplayTitle;
But, even though no other components can directly import state (except App.js), any component can import actions from Store.js in order to mutate the state.
For example in the SetTitle component:
import React, { Component } from 'react';
import { actions } from './../../Store';
class SetTitle extends Component {
updateTitle (e) {
actions.setTitle(e.currentTarget.value);
}
render () {
return (
<input onChange={this.updateTitle} type='text'/>
);
}
}
export default SetTitle;
Are there any flaws or other obvious reasons why this approach wouldn't be the best route to go? I'd love any and all feedback!
you are missing a few things
at root level:
import { Provider } from 'mobx-react'
...
<Provider state={state}>
<Other stuff />
</Provider>
At component level:
import { inject } from 'mobx-react'
#inject('state')
class Foo ... {
handleClick(){
this.props.state.setTitle('foo')
}
render(){
return <div onClick={() => this.handleClick()}>{this.props.state.title}</div>
}
}
You can stick only the interface actions={actions} in your provider and inject that, ensuring children can call your API methods to mutate state and have it flow from bottom up. Though if you were to mutate it direct, no side effects will happen because all components willReact and update in your render tree - flux is cleaner to reason about.
I'm quite new to both React and Redux, and I'm unsure about both best practices and technical solution to a case I'm working on. I'm using "component" and "container" as defined by Dan Abramov here.
The component I'm working on is a small collection of filter components: One input text field and two buttons, all filtering a list of entities. I've tried two approaches:
First approach: Single component containing three instances of two types of containers, containers connected to corresponding components.
This was what I first made. Here, the root component looks like the following:
import React, { PropTypes, Component } from 'react';
import Config from '../../config';
import FilterInput from '../containers/FilterInput';
import FilterLink from '../containers/FilterLink'
class FilterController extends Component {
render() {
return (
<div className='filterController'>
<FilterInput displayName="Search" filterName={Config.filters.WITH_TEXT} />
<FilterLink displayName="Today" filterName={Config.filters.IS_TODAY} />
<FilterLink displayName="On TV" filterName={Config.filters.ON_TV} />
</div>
)
}
}
export default FilterController;
The two containers referenced here look pretty much as expected, as do the connected components. I'll show the FilterLink as an example:
import React, { PropTypes, Component } from 'react';
import {connect} from 'react-redux';
import {toggleFilter} from '../actions';
import FilterButton from '../components/filterbutton'
const mapStateToProps = (state, ownProps) => {
return {
active: !!state.program.filters[ownProps.filterName]
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(toggleFilter(ownProps.filterName, ownProps.input))
}
}
}
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(FilterButton)
export default FilterLink
And the corresponding FilterButton component:
import React, { PropTypes, Component } from 'react';
class FilterButton extends Component {
render() {
return (
<button className={this.props.active ? 'active' : ''}
onClick={this.props.onClick}>
{this.props.displayName}
</button>
)
}
}
FilterButton.propTypes = {
active: PropTypes.bool.isRequired,
displayName: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
filterName: PropTypes.string.isRequired
};
export default FilterButton;
This approach works, but I'm thinking that it shouldn't be necessary to create two different containers. Which again lead me to my second attempt.
Second approach: single container containing multiple components.
Here, I made a larger container:
import React, { PropTypes, Component } from 'react';
import Config from '../../config';
import {connect} from 'react-redux';
import {toggleFilter} from '../actions';
import FilterButton from '../components/filterbutton'
import FilterInput from '../components/filterinput'
class FilterContainer extends Component {
render() {
const { active, currentInput, onChange, onClick } = this.props;
return (
<div className='filterController'>
<FilterInput displayName="Search" filterName={Config.filters.WITH_TEXT} currentInput={currentInput} onChange={onChange} />
<FilterButton displayName="Today" filterName={Config.filters.IS_TODAY} active={active} onClick={onClick}/>
<FilterButton displayName="On TV" filterName={Config.filters.ON_TV} active={active} onClick={onClick}/>
</div>
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
active: !!state.program.filters[ownProps.filterName],
currentInput: state.program.filters[ownProps.filterName] ? state.program.filters[ownProps.filterName].input : ''
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(toggleFilter(ownProps.filterName, ownProps.input))
},
onChange: newValue => {
dispatch(toggleFilter(ownProps.filterName, newValue.target.value))
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(FilterContainer);
Here, all state interaction for the entire component is gathered in a single compoment. The components are the same here as in the first approach. This doesn't, however, really work: ownProps is empty in both mapStateToProps and mapDispathToProps. I may have misunderstood how the react-redux connection works.
So, given these things I have two questions: What's the best way to structure this component, in terms of containers and components? And secondly, why won't ownProps work similarily in the second approach as in the first?
Thank you for your time.
Not sure I have a specific answer regarding structure at the moment. As for "ownProps", that represents props that are specifically being passed in to a given component by its parent. Since you're connect()-ing FilterController, that means that "ownProps" would be coming from wherever you render that component, like: return <FilterController prop1="a" prop2={someVariable} />.
Based on how you have those map functions written, it looks like you really want to be connecting the FilterInput and FilterButton components rather than FilterController.