mapStateToProps passing props as empty object - reactjs

I'm trying to add Redux to my React Native application. There is a Decider app that should control whether or not the <Intro /> is shown or <Content />.
I've never had any issues with linking redux before and have tried different variations so I must be doing something wrong here. FYI The only reason I have a <Decider /> component is because <App /> is the root component and following my understanding you can't have a Provider and connect() on the same component.
Props is coming through as an empty object. What am I doing wrong?
App.js
import Intro from './pages/Intro';
import Content from './pages/Content';
const store = createStore(rootReducer)
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Decider />
</Provider>
)
}
}
class Decider extends Component {
render() {
return this.props.showIntro ? <Intro /> : <Content />
}
}
const mapStateToProps = state => {
return {
showIntro: state.showIntro
}
}
connect(mapStateToProps)(Decider)
./reducers/index.js
[...auth reducer...]
const viewControl = (state = true, action) => {
switch (action.type) {
case ON_INTRO_COMPLETION:
return action.payload
default:
return state;
}
};
export default rootReducer = combineReducers({ auth, viewControl });
./actions/index.js
export const onIntroCompletion = bool => {
return {
type: 'ON_INTRO_COMPLETION',
payload: false
}
}

From redux docs
combineReducers takes an object full of slice reducer functions, and
creates a function that outputs a corresponding state object with the
same keys.
As state is immutable the new state is constructed with the keys you specify in combineReducers, so if
your reducer doesn't fulfill the schema of your state you will lose properties.
You might want to try something like this:
export default rootReducer = combineReducers({
auth,
viewControl,
showIntro: showIntro => showIntro
});

Your action returns: payload: true, and reducer supplies this as the entire state object (true or false).
But, when you connect() your component, you assign try to pull from state.showIntro.
In your reducer, try this:
case ON_INTRO_COMPLETION:
return {showIntro: action.payload};

Related

How to properly ask for props on component

I have a component SinglePost that is called when another component Post is clicked in the main page, and also through path /post/:id. The thing is, in SinglePost, I call an API through Redux on componentDidMount, and then ask for it on componentWillReceiveProps. And when you access the component from the URL /post/:id, the props throws undefined two times after it loads.
I was following a React/Redux tutorial, to make a CRUD web app with API calls. And, in that project, the props loads fine in both cases (through main page navigation, and from URL). I did exactly the same thing and it doesn't work. Is this a problem, or does it work this way?
This is the SinglePost component:
import { connect } from 'react-redux';
import { mostrarPublicacion } from '../../actions/publicacionesActions';
state = {
publicacion: {},
}
componentDidMount() {
const {id} = this.props.match.params;
//this is the action defined on PublicacionesActions
this.props.mostrarPublicacion(id);
}
componentWillReceiveProps(nextProps, nextState) {
const {publicacion} = nextProps;
this.setState({
publicacion
})
}
const mapStateToProps = state => ({
publicacion: state.publicaciones.publicacion
})
export default connect(mapStateToProps, {mostrarPublicacion}) (SinglePost);
PublicacionesActions:
export const mostrarPublicacion = (id) => async dispatch => {
const respuesta = await axios.get(`http://www.someapi.com:8000/api/publicacion/${id}`);
dispatch({
type: MOSTRAR_PUBLICACION,
payload: respuesta.data
})
}
I debugged this and it actually returns the publication. In fact, it renders properly in the SinglePost component. But at first it loads undefined when accessing the component through the url.
RootReducer:
import { combineReducers } from 'redux';
import publicacionesReducer from './publicacionesReducer';
import seccionesReducer from './seccionesReducer';
export default combineReducers({
publicaciones: publicacionesReducer,
secciones: seccionesReducer
});
PublicacionesReducer:
export default function (state = initialState, action) {
switch(action.type) {
case MOSTRAR_PUBLICACION:
return {
...state,
publicacion: action.payload
}
default:
return state
}
}
This is the Router component (wrapped into <Provider store={store}>):
<Switch>
<Route exact path='/post/:id' component={SinglePost} />
</Switch>
I actually ask for (!this.state.publication) and return null, to render the component. It is working, but it is necesary?

Subscribing to single reducer with Redux Connect still delivers all reducers to component

I have a store with 9 reducers in it. I only want my component to listen to one of them discoverSearch. Using shorthand version of mapStateToProps this is my code. However, the component is still being delivered all reducers in componentWillReceiveProps.
Component
import React from 'react'
import { connect } from 'react-redux'
import { View, Text, Animated, Dimensions } from 'react-native'
const _ = require('lodash')
import colors from '../../Color'
import DiscoverSearchResultChannel from './DiscoverSearchResultChannel'
import DiscoverSearchResultEpisode from './DiscoverSearchResultEpisode'
const { height, width } = Dimensions.get('window')
class DiscoverSearchResultsContainer extends React.Component {
constructor() {
super()
this.generateResultsList = this.generateResultsList.bind(this)
}
generateResultsList(results, type) {
const components = []
for (let i = results.length - 1; i >= 0; i--) {
if (type === 'CHANNEL') {
const result =
(<DiscoverSearchResultChannel
entry={results[i]}
key={`dsearch-${results[i].id}`}
navigation={this.props.navigation}
/>)
components.push(result)
} else if (type === 'EPISODE') {
const result =
(<DiscoverSearchResultEpisode
entry={results[i]}
key={`dsearch-${results[i].id}`}
navigation={this.props.navigation}
/>)
components.push(result)
}
}
return components
}
render() {
const { episodes, channels } = this.props.discoverSearch.results
return (
<Animated.ScrollView
style={styles.shell}
contentContainerStyle={styles.innerContent}
>
<Text style={styles.divider}>Podcasts</Text>
{
_.isUndefined(channels) ? null : this.generateResultsList(channels, 'CHANNEL')
}
<Text style={styles.divider}>Episodes</Text>
{
_.isUndefined(episodes) ? null : this.generateResultsList(episodes, 'EPISODE')
}
</Animated.ScrollView>
)
}
}
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
Store
import { applyMiddleware, createStore } from 'redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import combinedReducers from './reducers/CombineReducers'
const middleware = applyMiddleware(
thunk,
createLogger(),
promise()
)
export default createStore(combinedReducers, middleware)
DiscoverSearchReducer
const initialState = {
results: []
}
const DiscoverSearchReducer = (state = initialState, action) => {
let newState
switch (action.type) {
case 'DISCOVER_SEARCH_REQUEST_OUT':
// TODO
break
case 'DISCOVER_SEARCH_RETURN':
newState = {
...state,
results: action.payload
}
break
default:
return state
}
return newState
}
Reducers
export default combineReducers({
network: NetworkReducer,
audioPlayer: AudioPlayerReducer,
fileHandler: FileHandlerReducer,
currentTrack: CurrentTrackReducer,
trackQueue: TrackQueueReducer,
asyncStatus: AsyncStatusReducer,
makeClip: MakeClipReducer,
userProfile: UserProfileReducer,
scrollListener: ScrollListenReducer,
userClips: UserClipsReducer,
discoverSearch: DiscoverSearchReducer,
})
App Entry
class App extends React.Component {
componentWillMount() {
// TODO
// Initialize Firebase => get UID then...
store.dispatch(fetchUser('7713BNBNPODPIE'))
}
componentDidMount() {
TrackPlayer()
}
render() {
return (
<Provider store={store} >
<View style={{ flex: 1 }}>
<Navigation />
<BottomPlayer />
</View>
</Provider>
)
}
}
export default App
The connect piece of Redux is a little new to me so I might be missing something obvious?
Edits:
Added App.js entry point
Added full DiscoverSearchResultsContainer component minus styles
The error is in your connect function:
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
You have parenthesis wrong, this is equivalent to:
connect(store => {
// this does nothing
{ discoverSearch: store.discoverSearch };
return dispatch => dispatch;
})(...)
Which is actually the same as
connect(state => state)(...)
What you probably meant to write was:
connect(store = ({ discoverSearch: store.discoverSearch }),
dispatch => dispatch)(...)
EDIT: Remove unnecessary dispatch
As commented, mapping the dispatch is useless, you can just do
const mapStateToProps = state => ({ discoverSearch: state.discoverSearch });
connect(mapStateProps)(Component);
The easiest to way use connect is to separate out mapStateToProps and mapDispatchToProps into their own function so as to avoid making syntactical errors and if you are anyways returning dispatch as the second parameter you might as well not use it since if the second parameter to connect is empty, dispatch is returned by default.
According to the connect documentaion
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or
Function): If an object is passed, each function inside it is assumed
to be a Redux action creator. An object with the same function names,
but with every action creator wrapped into a dispatch call so they may
be invoked directly, will be merged into the component’s props.
If a function is passed, it will be given dispatch as the first
parameter. It’s up to you to return an object that somehow uses
dispatch to bind action creators in your own way. (Tip: you may use
the bindActionCreators() helper from Redux.)
If your mapDispatchToProps function is declared as taking two
parameters, it will be called with dispatch as the first parameter and
the props passed to the connected component as the second parameter,
and will be re-invoked whenever the connected component receives new
props. (The second parameter is normally referred to as ownProps by
convention.)
If you do not supply your own mapDispatchToProps function or object
full of action creators, the default mapDispatchToProps
implementation just injects dispatch into your component’s props.
You could use your connect statmeent like
const mapStateToProps = store => {
return { discoverSearch: store.discoverSearch }
}
export default connect(mapStateToProps)(DiscoverSearchResultsContainer)
When you use connect like
export default connect(store => (
{ discoverSearch: store.discoverSearch },
dispatch => dispatch
))(DiscoverSearchResultsContainer)
You actually have your () at the wrong place as you want to return { discoverSearch: store.discoverSearch } and not { discoverSearch: store.discoverSearch }, dispatch => dispatch. It should be
export default connect(store => (
{ discoverSearch: store.discoverSearch })
)(DiscoverSearchResultsContainer)
The above snippet would be the same as
export default connect(store => {
return { discoverSearch: store.discoverSearch }
}
)(DiscoverSearchResultsContainer)
which is what you need. However you must go with the first approach
Is it possible that your DiscoverSearchReducer is returning a new object for each dispatch, even on a no-op?
function reduceSomething(state, action) {
if action.type == "forMe":
return {action.payload}
else
return {...state}
}
rather than:
return state
Because the former will give you a new object for every dispatch, with the expected content, but connect will be unable to suppress passing props to your component.
It's a weird one but it matches your symptoms.

Why isn't my state correctly mapped to props in Redux?

I created this component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
export class Todos extends Component {
render() {
//todo remove
console.log('testing this.props.todos', this.props.todos);
//todo remove
debugger;
return (
<div>hello from todos</div>
);
}
}
const mapStateToProps = function (store) {
return {
todos: store.todos
}
}
export default connect(mapStateToProps)(Todos)
I am using Redux and this is the reducer:
const initialState = {
todos: [
{
name:'first todo'
}
]
}
const Todos = (state = initialState, action) => {
switch (action.type) {
case "ADD_TODO":
var newState = Object.assign({}, state, {todos: [...state.todos, action.data]});
return newState;
default:
//todo remove
debugger;
return state;
}
}
export default Todos;
For some reason the this.props.todos is undefined and my state is not mapped to my props correctly. How can I hook up the Redux store to my component?
The problem is that you're not passing the store correctly down to your components! If you want to use connect to connect your component to the global state, you have to use the <Provider> component provided by react-redux. So, first you create your store with createStore as you've done, then pass it into the provider:
import store from './store.js';
import { Provider } from 'react-redux';
const App = () => (
<Provider store={store}>
<Todos />
</Provider>
);
ReactDOM.render(
<div className="container">
<App />
</div>
, document.getElementById('root'));
Notice how the <Provider> is used to provide the store and make it available for your connect calls. Also, <Todos> shouldn't take any props. Also, your import:
import { Todos } from './components/todos.jsx';
Is wrong. Your todos.jsx, you're exporting the connected component like this, as the default export:
export default connect(mapStateToProps)(Todos)
So you have to import the default instead:
import Todos from './components/todos.jsx';

How can I store and subscribe component state to redux store?

I have a container "HomeIndexView" and component "Table"
And I have both global and local component state of table.
Global table states are like below,
const initialState = {
allTables: [],
showForm: false,
fetching: true,
formErrors: null,
};
and local component state of table is like below,
componentWillMount() {
this.setInitialState();
}
setInitialState() {
this.setState({ tableBusy: false });
}
When a user logs in, in HomeIndexView, it shows all tables from data base through fetching.
So what I want to do is that connecting local component state to redux store so that when it changes state false to true, it changes background color of table. How should I connect local state to redux store? and should I create separate reducer and action for the local component's state?
Thanks in advance
--EDIT 1
import Table from '../../components/tables/table';
I am importing LocalComponent (Table) to HomeIndexView to show.
In my HomeIndexView, it renders all tables from database,
_renderAllTables() {
const { fetching } = this.props;
let content = false;
if(!fetching) {
content = (
<div className="tables-wrapper">
{::this._renderTables(this.props.tables.allTables)}
</div>
);
}
return (
<section>
<header className="view-header">
<h3>All Tables</h3>
</header>
{content}
</section>
);
}
_renderTables(tables) {
return tables.map((table) => {
return <Table
key={table.id}
dispatch={this.props.dispatch}
{...table} />;
});
}
render() {
return (
<div className="view-container tables index">
{::this._renderAllTables()}
</div>
);
}
The 'react-redux' library contains binding methods between React and Redux. If you haven't done so already, I really recommend checking out
Dan Abramov's: 'Getting into Redux' series of videos.
He goes into a good amount of detail about how to build a working Redux application from scratch and then how to do the same in conjunction with React (again from scratch).
He finalises on the use of the 'react-redux' helper library to make wiring up React with Redux easier.
The resulting solution for you would be to:
Use the connect method in Redux to create a Redux container component (just a term for a React component with Redux bindings)
mapStateToProps receives updates on the current state of the store which you can map to the target components props. Yo'd use this to get the current state of the store for use in your component
mapDispatchToProps which gets the store's dispatch action which you can use to bind action creators to (to update the store). You'd use this to connect the action creators that update the state of your store.
I assume you've already setup your reducers and actions. Now the only thing you need to do is dispatch an action from your LocalComponent.
Lets say you've a method called toggleTableState for updating the state tableBusy of your LocalComponent.
LocalComponent.js
import React, { PureComponent, PropTypes } from 'react';
import { connect } from 'react-redux';
import * as actions from './actions`;
#connect(
(state, props) => ({
isTableBusy: state.isTableBusy,
}),
{
toggleTableGlobalState: actions.toggleTableGlobalState,
})
export default class LocalComponent extends PureComponent {
static propTypes = {
toggleTableGlobalState: PropTypes.func,
isTableBusy: PropTypes.bool,
}
...
toggleTableState() {
this.setState({ tableBusy: !this.state.tableBusy });
this.props.toggleTableGlobalState(!this.state.tableBusy);
}
...
}
actions.js
export const TOGGLE_TABLE_STATE = 'TOGGLE_TABLE_STATE';
export function toggleTableGlobalState(tableState) {
return { type: TOGGLE_TABLE_STATE, tableState };
}
reducer.js
import { TOGGLE_TABLE_STATE } from './actions';
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
...
case TOGGLE_TABLE_STATE:
return { ...state, isTableBusy: action.tableState };
break;
...
}
}

My Redux store is an array

I am integrating Redux into my React Native app.
I've been having trouble passing the state down into my components, and realised its because the initial state begins with a key of '0', e.g.
{
'0': {
key: 'value'
}
}
I.e. it seems to be an array.
So that if I have in my connect export:
export default connect(state => ({
key: state.key,
reduxState: state
}),
(dispatch) => ({
actions: bindActionCreators(MyActions, dispatch)
})
)(MyReduxApp);
key is always undefined, however, reduxState gets passed down successfully. (Of course it is not advised to send the whole state down)
The root component:
import React, { Component } from 'react-native';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import DataReducer from '../Reducers/Data';
import MyRedApp from './JustTunerApp';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers([DataReducer]); // ready for more reducers..
// const store = createStoreWithMiddleware(reducer);
const store = createStore(reducer);
export default class App extends Component {
render() {
console.log ("store.getState:")
console.log (store.getState())
return (
<Provider store={store}>
<MyReduxApp />
</Provider>
);
}
}
My reducer looks like:
const initialState = {
key : "value"
};
export default function key(state = initialState, action = {}) {
switch (action.type) {
// ...
default:
return state;
}
}
Is the store returned as an array? How should I pass it into the connect method?
If I pass it in as key: state[0].key then it works, but this doesn't seem right according to all the examples I've seen..
I should have posted the root component earlier... that held the clue.
const reducer = combineReducers([DataReducer])
should have been
const reducer = combineReducers({DataReducer})
the lesson here is don't rush and look at the docs more closely!

Resources