Using Redux connect() with Layout component being passed children and receiving props - react-native

I'm having some difficulty getting my Layout component to connect because I'm passing children. Basically the way I have my application setup is that app.js houses the provider, persistgate, layout, and react-navigation navigation stack.
The only component which I haven't connected so far is my Layout, and before I started to implement some navigation on the top bar, I didn't really need it. Now I want to be able to pass the routeName to redux so that the Layout knows which route the user is on and can display appropriate buttons.
I've tried a few things, but nothing seems to work, if I manage to get the Layout to load with connecting, it doesn't seem to obtain the routes from redux store even though I've confirmed that it is there on React-Native Debugger.
app.js
export class App extends Component {
render() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<Layout>
<AppWithNavigationState/>
</Layout>
</PersistGate>
</Provider>
)
}
}
Layout.js
This is what I used BEFORE.
class Layout extends Component {
render() {
const currentRoute = this.props.route;
console.log(this.props.route); ////// TESTING TO SEE IF ROUTES SHOWS. IT'S ALWAYS "UNDEFINED" DESPITE BEING IN THE STORE.
headerButton = () => {
if (currentRoute==='Main') {
return (<Icon onPress={() => navigate({routeName: "PostForm"})} style={styles.headericon} name="back"></Icon>);
} else {
return (<Icon style={styles.headericon} name="menu"></Icon>)
}
}
.......
export default ({children}) => (
<Layout>
{children}
</Layout>
)
Layout.js Test (updated but still not receiving store data)
Layout.propTypes = {
children: PropTypes.node,
route: PropTypes.string,
updateRoute: PropTypes.func
};
const mapStateToProps = state => ({
route: state.route.route
});
const mapDispatchToProps = (dispatch) => {
return {
updateRoute: route => dispatch(postActions.updateRoute(route))
}
}
const LayoutTest = ({children}) => (
<Layout>
{children}
</Layout>
)
export default connect(mapStateToProps, mapDispatchToProps)(LayoutTest);

Turns out I do not even need to pass 'children' to the components. I rendered it by changing
export default ({children}) => (
<Layout>
{children}
</Layout>
)
to
export default connect(mapStateToProps, mapDispatchToProps)(Layout)
After doing some testing and realizing that the state was actually able to be read inside of the mapStateToProps function but it was not mapping to Layout. The reason I was so confused was that I really thought I needed to pass children to it the way I had it before. Luckily I never!

Related

Using react-native-router-flux with redux, how to update state in the view component?

The issue:
I have tried to use redux in combination with the routing provided by react-native-router-flux.
In simple words, it is not working:
redux state modifications will not appear in view, but can be logged successfully to console
every time an action is taken, the whole component tree with the scenes will be recreated, which results in lots of warnings in console, that a scene with the key "xyz" was already created.
What i did:
I used the official example application of react-native-router-flux and added a simple counter-example based on redux:
a simple state: "counter" (might be an integer)
reducers and actions (increment, decrement)
Below i will show some code, but also one will find my example application on github, please feel free to check it out: https://github.com/itinance/react-native-router-flux. The "Example" Directory is the original example application. And "ExampleRedux" is a copy of the example with a redux stack and example reducers (a counter), what i am talking about here.
The main application component with provider and store:
import * as reducers from './components/reducers';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(reducers);
const store = createStoreWithMiddleware(reducer);
export default class Example extends React.Component {
render() {
return (
<Provider store={store}>
<ExampleContainer/>
</Provider>
);
}
}
My reducers:
const initialState = {
count: 0
};
export default function counter(state = initialState, action = {}) {
switch (action.type) {
case "INCREMENT":
console.log("do increment", state,
{
...state,
count: state.count + 1
}
);
return {
...state,
count: state.count + 1
};
case "DECREMENT":
return {
...state,
count: state.count - 1
};
default:
return state;
}
}
The inner application container for redux' "connect":
I pass the state and actions to the scene component:
<Scene key="launch" component={Launch} title="Launch" initial={true} state={state} {...actions} />
The whole code of this ExampleContainer:
class ExampleContainer extends React.Component {
constructor(props) {
super(props);
}
render() {
const { state, actions } = this.props;
console.log("Props", this.props, state, actions); // everything ok here
return <Router createReducer={reducerCreate}>
<Scene key="modal" component={Modal} >
<Scene key="root" hideNavBar={true}>
<Scene key="echo" clone component={EchoView} />
<Scene key="register" component={Register} title="Register"/>
<Scene key="home" component={Home} title="Replace" type="replace"/>
<Scene key="launch" component={Launch} title="Launch" initial={true} state={state} {...actions} />
.... lots of other scenes ...
</Scene>
</Scene>
<Scene key="error" component={Error}/>
</Scene>
</Router>;
}
}
export default connect(state => ({
state: state.counter
}),
(dispatch) => ({
actions: bindActionCreators(actions, dispatch)
})
)(ExampleContainer);
And this is the dead easy LaunchScreen having simple counter functionality (and example routes for demonstrating the routing):
class Launch extends React.Component {
constructor(props) {
super(props);
console.log(props);
}
render(){
const { state, increment, decrement } = this.props;
console.log("Props 2: ", this.props, state, increment, decrement);
return (
<View {...this.props} style={styles.container}>
<Text>Launch page</Text>
<Text>{state.count}</Text>
<Button onPress={increment}>Increment</Button>
<Button onPress={()=>Actions.login({data:"Custom data", title:"Custom title" })}>Go to Login page</Button>
<Button onPress={Actions.register}>Go to Register page</Button>
<Button onPress={Actions.register2}>Go to Register page without animation</Button>
<Button onPress={()=>Actions.error("Error message")}>Popup error</Button>
<Button onPress={Actions.tabbar}>Go to TabBar page</Button>
<Button onPress={Actions.pop}>back</Button>
</View>
);
}
}
The actions are perfectly dispatched when clicking on Increment-Button. In the console the new state can be logged successfully. It increments one by one with every click. But not in the view.
When i ommit the router and scenes and only activate the launch screen as a single component, everything works fine.
Question:
How to make this redux application work, that views will update their state correctly?
For the sake of completeness, the whole code can be found on github.
You have probably forgotten to connect the Launch component to the store. What you want to do is similar to what you have done in ExampleContainer, i.e.
export default connect(state => ({
state: state.counter
}),
(dispatch) => ({
actions: bindActionCreators(actions, dispatch)
})
)(Launch);
and then the correct values will show up in your log

Component constantly re-rendering when dispatching action (promise)

When I run the code with this.props.addChannel(payload);
Channel Component keeps re-rendering like its in an infinity loop.
When I replace it with console.log(payload) it works fine.
const mapStateToProps = state => ({
user: state.shared.user,
});
const mapDispatchToProps = dispatch => ({
addChannel: (payload) => dispatch({type:ADD_CHANNEL, payload})
});
class Channel extends Component{
componentDidMount(){
const payload = api.Channels.getAll();
this.props.addChannel(payload);
//console.log("Channels", payload)
}
render(){
return(
<div>
<AddChannel />
<ChannelList channels={[{text:"test"}]} />
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Channel);
The api code:
const Channels = {
getAll: () => requests.get('channels/twitter/')
};
The Reducer:
import {ADD_CHANNEL} from '../constants/ActionTypes';
export default (state={}, action={}) => {
switch(action.type){
case ADD_CHANNEL:
return {...state};
default:
return {...state};
}
};
The Routes Component:
import React,{Component} from 'react';
import {Route, Switch} from 'react-router-dom';
import { connect } from 'react-redux';
import Auth from './containers/Auth';
import Channel from './containers/Channel';
import Messages from './containers/Messages';
import withAuth from './components/Auth/WithAuth';
const mapStateToProps = state => ({
user: state.shared.user,
});
class Routes extends Component{
render(){
const user = this.props.user;
return (
<Switch>
<Route exact path='/' component={Auth} />
<Route path='/messages' component={withAuth(Messages, user)} />
<Route exact path='/channels' component={withAuth(Channel, user)} />
</Switch>
);
}
};
export default connect(mapStateToProps, ()=>({}),null,{pure:false})(Routes);
The reason for the loop is probably the call to your higher-order component withAuth in the component prop of your Route's. (see Route component docs)
This call will return a new component each time Routes is rendered, which will mount a fresh Channel with an accompanying api call and redux store update. Because of {pure: false}, the store update will then trigger a rerendering of Routes (even though user hasn't changed) and start a new cycle of the loop.
If you drop {pure: false} (which doesn't seem useful here) you'll probably end the loop, but the Channel component will still do unnecessary re-mounting if one of its ancestors rerenders, resetting all local component state in Channel and below.
To fix this, you could refactor withAuth to get user as a prop rather than a parameter, and call it on the top level, outside the Routes class:
const AuthMessages = withAuth(Messages);
const AuthChannel = withAuth(Channel);
Now you can pass user to these components by using the render prop of Route:
<Route path='/messages' render={(props) => <AuthMessages {...props} user={user}/>}/>
<Route exact path='/channels' render={(props) => <AuthChannel {...props} user={user}/>}/>
Besides this, you will probably want to keep the channels in the store and handle the api call asynchronously, but I assume this code is more of a work in progress.

Passing function from mapDispatchToProps to Container component in react/redux app

I'm rendering RootComponent:
//RENDERING ROOT COMPONENT-------------------------------------------------------------------------------
ReactDOM.render(
<Provider store={store}>
<RootComponent />
</Provider>,
document.getElementById('app'));
//RENDERING ROOT COMPONENT-------------------------------------------------------------------------------
RootComponent has only one container:
//ROOT COMPONENT----------------------------------------------------------------------------------------------------
const RootComponent = () => (
<div>
<BookListContainer />
</div>
);
//ROOT COMPONENT----------------------------------------------------------------------------------------------------
BooklistContainer:
//BOOKLIST CONTAINER-------------------------------------------------------------------------------------------------------
class BookListContainer extends Component{
componentWillMount(){
console.log('componentWillMount executing...');
() => this.props.ajaxRequestTriggeredMethod();
}
render() {
return (
<div>
<BooksList DataInputParam={this.props.books} BtnClickHandler={this.props.buttonClickedMethod} />
</div>
);
}
}
const mapStateToProps = (state) => {
return{
books: state.BooksReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
buttonClickedMethod: () => dispatch({type: 'BUTTON_CLICKED'}),
ajaxRequestTriggeredMethod: () => console.log('ajaxRequestTriggeredMethod is consoled...')
};
};
connect(mapStateToProps, mapDispatchToProps)(BookListContainer);
//BOOKLIST CONTAINER-------------------------------------------------------------------------------------------------------
All components are in one js file at the moment, so i'm not exporting/importing anything except standard libraries...
Result: i'm getting 'componentWillMount executing...' message in the console, but not getting 'ajaxRequestTriggeredMethod is consoled...' message. Also, no errors in the console are displayed.
Not an expert, but connect() function returns
A higher-order React component class that passes state and action creators into your component derived from the supplied arguments
So my guess would be to do something like this :
const randomNameForComponent = connect(mapStatetoProps, mapDispatchToProps)(BookListContainer);
export default randomNameForComponent;
and in your RootComponent, render randomNameForComponent instead of BookListComponent.
It should do the trick.
Its because you are not executing the arrow function. You can directly call this method instead.
componentWillMount(){
console.log('componentWillMount executing...');
this.props.ajaxRequestTriggeredMethod(); //Invoke directly
}

Using connect() from react-redux makes NavLinks from react-router not work?

I'm learning redux and created a Navbar component that will handle user actions. It uses NavLink to add the 'active' class to my links so I can style the active link. When I navigate my site however the active class doesn't move until I send an action to react (login/logout is all I"ve done so far). If I take out the connect stuff and use the class component directly the NavLinks work fine. Adding a log to componentDidUpdate shows that it isn't being called when connect is used.
Am I missing something? I see there's react-router-redux but I'm building a site for fun to learn the whole deal and I'm just starting to get redux, I don't want to bring in the concept of middleware and I definitely don't want to use combineReducers since at this point it would just mean having the router property and shoving everything else under something like 'main' in the state.
I'm thinking of using context and putting the whole redux state on it...
update: - I added react-router-redux and I can only get it to update on a route change if I add the new 'router' to my mapped props.
I have a NavbarC component class with a render method like this:
render() {
let { props } = this;
return <header className="navbar">
<nav>
<h1><NavLink to="/" exact>Home</NavLink></h1>
<NavLink to="/friends">Friends</NavLink>
</nav>
<div></div>
<div className="center-flex-row">
{ props.busy ? <section className="nav-right"><p>working<span className="spacer"></span><span className="fa fa-spin fa-spinner"></span></p></section> :
props.user ?
<Logout onSignOutClick={props.onSignOutClick} user={props.user} /> :
<Login onSignInClick={props.onSignInClick}/>
}
</div>
</header>
}
And I use connect():
const mapStateToProps = (state) => {
return { user: state.auth.user, busy: state.auth.busy
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSignInClick: () => {
dispatch(userLogin());
},
onSignOutClick: () => {
dispatch(userLogout());
}
}
}
const Logout = ({ user, onSignOutClick }) => {
return <section className="nav-right">
<button className="btn" onClick={ onSignOutClick }>Sign Out</button>
</section>
}
const Login = ({ onSignInClick }) => {
return <section className="nav-right">
<button className="btn" onClick={ onSignInClick }>Sign In</button>
</section>
}
export const Navbar = connect(mapStateToProps, mapDispatchToProps)(NavbarC)
I got it to work in three ways. I think connect is ignoring the props by default and replacing them, the example I based my code on didn't use 'ownProps'. So If I put the component in a route:
<Route path="/" component={Navbar}/>
And merge the properties in my mapStateToProps:
const mapStateToProps = (state, ownProps) => {
console.log('mapping state:', state);
console.log(' ownProps:', ownProps);
return {
user: state.main.auth.user,
busy: state.main.auth.busy,
...ownProps
}
}
That puts in the props from react-router (history, location, match) and causes the component to update because those values change when the route changes.
Another way is to bite the bullet and use react-router-redux, which puts a 'router' property on my state, which I then have to add to my mapStateToProps. Even though I don't care about the value, connect will detect that the props are different and re-render the component:
let store = createStore(
combineReducers({
main,
router: routerReducer
}),
applyMiddleware(middleware),
}
// in Navbar.js
const mapStateToProps = (state, ownProps) => {
return {
user: state.main.auth.user,
busy: state.main.auth.busy,
router: state.router
}
}
I can also specify pure=false in the options to connect() and that should re-render my component every time (at least it updates the right NavLink when I change routes for sure):
export const Navbar = connect(mapStateToProps, mapDispatchToProps,
undefined, { pure: false })(NavbarC)

Redux/React - Why I can't bind state to this.props on component which is in iframe?

I have a specific scenario in my react/redux/express universal project (server-side rendering).
(1)First I defined my routes like so: [ routes.jsx ]
export default (
<Route component={App} path="/">
<Route component={MainView} path="main">
<IndexRoute component={ThemeIndex}></IndexRoute>
</Route>
<Route component={AnotherView} path="preview" />
</Route>
);
As you see, when url route is: localhost:3000/preview, react-router will use AnotherView component.
(2)Now focus on ThemeIndex component: [ ThemeIndex.jsx ]
export default class ThemeIndex extends Component {
render() {
return (
<div>
<h2>Index</h2>
<Frame />
<Control />
</div>
);
}
}
(3)Frame component like so: [ Frame.jsx ]
class Frame extends Component {
render() {
const { text, uid } = this.props.infos;
const themeUrl = `http://localhost:3000/preview?id=${uid}`;
//console.log('Frame theme:', text);
//console.log('Frame uid:', uid);
return (
<div className="col-md-8 panel panel-default">
<div className="embed-responsive embed-responsive-16by9 panel-body">
<iframe src={themeUrl}></iframe>
</div>
<div className="details" >
{text}
</div>
</div>
);
}
}
export default connect(
(state) => {
return {
infos: state.infos
}
}
)(Frame);
Here I use iframe tag, its src is http://localhost:3000/preview?id=xxxx, so it means it will link AnotherView component to be iframe's page.
(4)AnotherView Component like so:
class AnotherView extends Component {
render() {
const { text, uid } = this.props.infos;
//console.log('AnotherView theme:', text);
//console.log('AnotherView uid:', uid);
return (
<div>
<div >Another View</div>
<div>
{text}
</div>
<div>{uid}</div>
</div>
);
}
}
export default connect(
(state) => {
console.log('another view trigger state:', state);
return {
infos: state.infos
}
}
)(AnotherView);
(4)And I have Control component for making dynamic value: [ Component.jsx ]
class Control extends Component {
render(){
var uid = () => Math.random().toString(34).slice(2);
return (
<input
onChange={(event) => this.props.addTodo({text:event.target.value, uid:uid()})
/>
)
}
}
export default connect(
(state) => {
return {
infos: state.infos
}
}
)(Control);
(5)List extra files, Action and Reducer:
[ action.js ]
export function addTodo (attrs) {
return {
type: 'ADD_TODO',
attrs
};
}
[ reducer.js ]
export default (state = {text:'', uid:''}, action) => {
switch(action.type) {
case 'ADD_TODO':
return Object.assign({}, state, action.attrs);
default:
return state;
}
}
Here is Store configuration on server.js:
app.use( (req, res) => {
console.log('server - reducers:', reducers);
const location = createLocation(req.url);
const reducer = combineReducers({infos: infosReducer});
const store = applyMiddleware(promiseMiddleware)(createStore)(reducer);
match({ routes, location }, (err, redirectLocation, renderProps) => {
.......
function renderView() {
const createElement = (Component, props) => (
<Component
{...props}
radiumConfig={{ userAgent: req.headers['user-agent'] }}
/>
);
const InitialView = (
<Provider store={store}>
<RoutingContext
{...renderProps}
createElement={createElement} />
</Provider>
);
const componentHTML = renderToString(InitialView);
const initialState = store.getState();
......
my application state is like :
{
infos:{
text: '',
uid: ''
}
}
(6)Now I key some words on input in Control component. When the input onChange will trigger addTodo action function to dispatch action in reducer, finally change the state. In common, the state changing will effect Frame component and AnotherView component, because I used react-redux connect, bind the state property to this.props on the component.
But in fact, there is a problem in AnotherView component. in Frame component, console.log value display the text you key in input correctly. In AnotherView component, even the connect callback will be trigger (console.log will print 'another view trigger state: ...') , the console.log in render is undefined, like:
console.log('AnotherView theme:', text); //return AnotherView theme: undefined
console.log('AnotherView uid:', uid); //return AnotherView uid: undefined
I found the main reason: AnotherView component is in iframe. Because if I remove iframe, put AnotherView component directly here, like so:
return (
<div className="col-md-8 panel panel-default">
<div className="embed-responsive embed-responsive-16by9 panel-body">
<AnotherView/>
</div>
<div className="details" >
{text}
</div>
</div>
);
then I can bind state properties on this.props in AnotherView component successfully, then insert {text} on JSX html, you can see the value changing real time when you key input value on Control component. if I use iframe to link AnotherView component be its page, you can't see any changing {text} value, because my text default value is empty string value.
How do I bind state properties to this.props in the component which is in iframe when state changing?
Update
I can't get the latest state in iframe (source is React component), when I changing state in another component, and actually the mapStateToProps was triggered!(means iframe source component) but its state is not the latest in mapStateToProps function. it does not really concerns with the react-redux library?
This is the latest state should be in component:
Below is iframe source component, it can't get the latest state:
If you load an app in an iframe from a script tag, it will load a separate instance of the app. This is the point of iframes: they isolate code.
Two separate instances of the app won’t “see” updates from each other. It’s like if you open the app in two separate browser tabs. Unless you add some method of communication between them, they will not share state.
It is not clear why you want to render a frame rather than a component directly. But if you really need frames, a simple option would be to use to render the component into the frame rather than load a separate app there. You can use a library like https://github.com/ryanseddon/react-frame-component for this. Or, simpler, you can just not use frames at all, as it is not clear what purpose they serve. Usually people want them to isolate an app instance but this seems contrary to what you seem to want.

Resources