How to use Redux and React navigation to make a global counter - reactjs

I am making a React-Native app in which I have a navigator from React Navigation and I also want to implement Redux. I am trying to create a global counter that updates based on an argument.
Here is the actions:
export const setFlags = (value) => {
return {
type: 'SETFLAGS',
value
}
}
export const setNonFlags = (value) => {
return {
type: 'SETNONFLAGS',
value
}
}
Here is the reducer, because its two things that have identical functionality I thought one would work (I am new to Redux):
const initialState = {
flags:0,
nonFlags:0,
}
const AllFlagReducer = (state = initialState, action) =>{
switch(action.type){
case 'SETFLAGS':
return state.flags = state.flags + action.value
case 'SETNONFLAGS':
return state.nonFlags = state.nonFlags + action.value
}
return state
}
export default AllFlagReducer
And here is the button where I would like to send the local state of the "flag" and "nonFlag" to the redux global states. After which I reset the local states and move to the next screen.
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags(in redux):this.state.flags
// dispatch nonFlags(in redux): this.state.nonFlags
() =>{this.resetAll();
navigation.navigate('Specific Scams')
}}>
Help would be greatly appreciated.
UPDATE 1:
The entire component:
class ScamTree extends React.Component {
constructor(props){
super(props)
this.state = {
flags : 0,
nonFlags: 0,
qAnswered:0
}
}
functions that might matter:
resetAll = () =>{
this.setState({flags:0})
this.setState({nonFlags:0})
this.setState({qAnswered:0})
}
the button, (I did not make a separate component for just the button):
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags:this.state.flags
// dispatch nonFlags: this.state.nonFlags
() =>{this.resetAll();store.dispatch({type:"SETFLAGS",value:5})
navigation.navigate('Specific Scams')
}}>
<Text style={{paddingHorizontal:40}}>NEXT</Text>
</TouchableOpacity>
the export to make React Navigation Work:
export default function(props) {
const navigation = useNavigation();
return <ScamTree {...props} navigation={navigation} />;
}

In your .js class you have to bind your action like this
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux';
<TouchableOpacity style={styles.resetButton}
onPress= {
// dispatch something like flags(in redux):this.state.flags
// dispatch nonFlags(in redux): this.state.nonFlags
() =>{
this.resetAll();
this.props.commanAction.setFlags(your value);
this.props.commanAction.setNonFlags(your value);
navigation.navigate('Specific Scams')
}}>
const mapDispatchToProps = dispatch => {
return {
commanAction: bindActionCreators(commanAction, dispatch)
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Your .js className);
Other code are looks good.

If I understand a comment of yours in another answer, and the snippet of your question update, I believe you need to decorate this functional component:
export default function(props) {
const navigation = useNavigation();
return <ScamTree {...props} navigation={navigation} />;
}
You want to decorate this component with the connect HOC to connect it to your redux store.
First I'd convert that function to a new withNavigation HOC:
const withNavigation = WrappedComponent => props => {
const navigation = useNavigation();
return <WrappedComponent{...props} navigation={navigation} />;
};
withNavigation.displayName = `withNavigation(${WrappedComponent.displayName || 'Component'})`;
Now you can decorate ScamTree as follows:
export default withNavigation(ScamTree);
But now you also need to connect your action creators to your redux store, decorate it with react-redux's connect HOC:
const mapDispatchToProps = {
setFlags,
setNonFlags,
};
export default withNavigation(
connect(null, mapDispatchToProps)(ScamTree),
);
Note: I see it seems you store in local state some flag values, not sure if that is related, but you can map in initial state for this with a mapStateToProps as the first parameter for connect instead of null.
By now you've probably noticed that each new HOC creates some nesting, and this will get worse with the more HOC's used. The solution is to use redux's compose HOC. That's right, it's not just for composing middleware for the store. It can compose all the decorators into a single HOC to wrap your exported component, or in other words, it flattens/eliminates the nesting.
Tips
All compose does is let you write deeply nested function
transformations without the rightward drift of the code. Don't give it
too much credit!
...
import { compose } from 'redux';
...
class ScamTree extends Component { ... }
const mapDispatchToProps = {
setFlags,
setNonFlags,
};
export default compose(
withNavigation,
connect(null, mapDispatchToProps),
)(ScamTree);
Note: Your reducers need to always return new state object references and never mutate existing state. Also, although your returns do work correctly, the reducer pattern is to return unhandled action types in the default switch case:
const AllFlagReducer = (state = initialState, action) => {
switch(action.type){
case 'SETFLAGS':
return { ...state, flags: state.flags + action.value };
case 'SETNONFLAGS':
return { ...state, nonFlags: state.nonFlags + action.value };
default:
return state;
}
}

In your reducer cases right now you writing new state over the whole state instead just in the fit place at state object, it can be written like
const AllFlagReducer = (state = initialState, action) =>{
switch(action.type){
case 'SETFLAGS':
return { ...state, flags: state.flags + action.value }
case 'SETNONFLAGS':
return { ...state, nonFlags: state.nonFlags + action.value }
default: return state
}
}
export default AllFlagReducer

Related

react-redux dispatch undefined

In my App.js :
const initialAppState = {
nums: {},
};
const reducer = (state = initialAppState, action) => {
if (
action.a === undefined ||
action.b === undefined ||
action.c === undefined
) {
return state;
}
if (state.nums[action.a][action.b] === undefined) {
state.nums[action.a][action.b] = {};
}
return {
nums: (state.nums[action.a][action.b] =
action.c),
};
};
const store = createStore(reducer);
Then passed it with Provider.
When in my Test.js I'm trying to change the value it give me error :
const mapStateToProps = (state) => {
return {
nums: state.nums,
};
};
function mapDispatchToProps(dispatch) {
return {
add: (a, b, c) =>
dispatch({a: a, b: b, c: c}),
};
}
export default connect(mapStateToProps, mapDispatchToProps())(Test);
And the Test functional component looks like this :
function Test({nums, dispatch})
...
function add(data, count) {
dispatch.add('1','2','3');
}
It give me error saying : TypeError: undefined is not an object (evaluating 'dispatch.add')
Shouldn't it recognize dispatch as dispatch is Test's parameter, and add is another function of that functional component ?
It’s not dispatch.add() its props.add()
// add comes in as a prop. that’s why it’s called map dispatch to props.
function Test({nums, add: doAdd})
...
function add(data, count) {
doAdd('1','2','3');
}
I would recommend you organize your code. Try to move actions and reducers into separate files like actions.js, reducers.js.
REDUCER NEED SOME FIXES:
In your example of reducer in the last return you are trying to mutate state.
Also as you can see in your second if there are no return.
PROPOSAL OF ACTIONS AND REDUCERS SEPARATION
Inside your App.js you will createStore:
//App.js
import { reducer } from "./reducers"
const store = createStore(reducer);
<Provider store={ store }>
...
</Provider>
Now let's do actions.js file:
//actions.js
export const ADD_ACTION='ADD_ACTION' // action type
export function addAction(data) {
return { type: ADD_ACTION, data }
}
In reducers you'll move part of code from your App.js. I recommend you to use switch inside your reducer like that:
//reducers.js
import { ADD_ACTION } from "./actions";
const initialAppState = {
nums: {},
};
export const reducer = (state = initialAppState, action) => {
switch(action.type) {
case ADD_ACTION:
return Object.assign({}, state, { nums: action.data });
default:
return state
}
};
As you can see one of the ADD_ACTION is returning new object with updated nums value. This is because one of the essential Redux rules is "Do not mutate state".
Now, let's try our action in Test.js. First import addAction from actions.js. Then call dispatch with addAction as an argument.
//Test.js functional component version
import { addAction } from "./actions";
function Test({dispatch}) {
...
function add(data, count) {
dispatch(addAction(data));
}
}
export default connect(null, null)(Test);
Component receives dispatch from connect. Connect has (null, null) because in this case we are not using mapStateToProps, nor mapDispatchToProps.
Extra tip: For class component you'll use action in a little bit different way.
//Test.js Class component version
import React from 'react';
import { addAction } from "./actions";
import { connect } from 'react-redux';
class TestClass extends React.Component {
render() {
return(
<button onClick={() => this.props.addAction(2)}>Action</button>
)
}
}
const mapDispatchToProps = ({
addAction
});
export default connect(null, mapDispatchToProps)(TestClass);
In class component example you'll call action with value 2 when onClick event fires.
Working sandbox example: react-redux example

Component not updating after mapStateToProps fires

I am learning how to implement redux from the ground up, and have run into a problem with my components' re-rendering. My search for this issue on StackOverflow has produced a gazillion results, of which the answer to the question is always "you mutated your state." I have read the connect documentation, I've looked at a bunch of people with similar problems, and I just can't see where state mutation might be the problem here, so I'm going to try asking with my simple example.
Here's my container component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addPokemon } from '../../../redux/actions';
import ReduxTester from '../../components/redux_tester/redux_tester';
export class ReduxTesting extends Component {
constructor(props) {
super(props);
}
addPokemon(name, pokeType) {
addPokemon(name, pokeType);
}
render() {
return (
<div>
<ReduxTester
pokemon={this.props.pokemon}
addPokemon={this.addPokemon}
/>
</div>
);
}
}
const MapStateToProps = function(state) {
return {
pokemon: state.pokemon,
};
};
export default connect(MapStateToProps)(ReduxTesting);
Here's my reducer:
const defaultState = {
pokemon: {},
};
export default function pokemonReducer(state = defaultState, action) {
const newState = Object.assign({}, state);
switch (action.type) {
case 'ADD_POKEMON':
newState.pokemon[action.name] = action.pokeType;
break;
default:
break;
}
return newState;
}
My specific issue is simply that ReactTesting's componentWillReceiveProps method is not firing, and so the component is not being updated and re-rendered. Note that the mapStateToProps method is firing after the action is dispatched. I know this is such a repetitive issue and it's unlikely that my problem is something different, but I just can't find the answer. Any assistance is appreciated.
Edit: For additional clarification, here is my actions.js, where I've imported the dispatch function directly:
import { dispatch } from './store';
// Returns an action that fits into the reducer
export function addPokemon(name, pokeType) {
dispatch({
type: 'ADD_POKEMON',
name,
pokeType,
});
}
Edit 2: I've found some additional information, but I don't understand it. It seems that in MapStateToProps, a re-render is triggered when I assign the entire Redux state to one prop - but not when I assign just a portion of the Redux state to prop.
This triggers a re-render:
const MapStateToProps = function(state) {
return {
pokemon: state,
};
};
This does not:
const MapStateToProps = function(state) {
return {
pokemon: state.pokemon,
};
};
Yet I have confirmed my redux store does have the pokemon property and that is where the updates are occurring in the state. Any insight?
Calling your action creator without going through redux won't dispatch the action:
export class ReduxTesting extends Component {
constructor(props) {
super(props);
}
addPokemon(name, pokeType) {
this.props.addPokemon(name, pokeType);
}
.
.
.
function mapDispatchToProps(dispatch) {
return({
addPokemon: (name, pokeType) => {dispatch(addPokemon(name, pokeType))}
})
}
export default connect(MapStateToProps, mapDispatchToProps)(ReduxTesting);
EDIT 2
You're probably mutating your state, try with something like this:
Reducer
switch (action.type) {
case 'ADD_POKEMON':
return {
...state,
pokemon[action.name]: action.pokeType;
};

Retrieving a Redux state, but also change another Redux state in same React Native component?

I have a React component that currently just retrieves a state from Redux. Here is the general layout:
const mapStateToProps = state => {
return { stuff: state.stuff };
};
class MyComponent extends React.Component {
// use 'stuff' from redux to build the Views
}
export default connect(mapStateToProps)(MyComponent);
But now, what if I want to add a button that changes another Redux state called other?
To save the new Redux state, I know we have to create a dispatch to the action. ie,
const mapDispatchToProps = dispatch => {
....
};
Then finally connect them:
connect(null, mapDispatchToProps)(MyComponent);
But my confusion is if I am already connecting with mapStateToProps, how can I also map it to mapDispatchToProps so that I can update the Redux state in the same component?
You can use both ;-)
For example :
Action.js
export const textChanged = (newText) => {
return { type: "TEXT_CHANGED", newText }
};
HomeScene.js :
import { textChanged } from "../actions;
...
render () {
const { myText } = this.props;
<TextInput
value={myText}
onChangeText={(newText) => this.props.textChanged(newText)}
/>
}
function mapStateToProps(state) {
return {
myText: state.appContent.myText
};
}
export default connect(mapStateToProps, { textChanged })(HomeScene);
Reducer.js
case "TEXT_CHANGED":
return {
...state,
myText: action.newText
};
Hope it helps !
Hm, looks like I asked too early. I did a bit of reading and the parameters in connect() actually accepts both.
So like this:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)

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.

How to Call selector function from React Component?

This is my Selector , I can able to get data with in the selector but don't know how to call this into view (Component) ,
import {todos} from '../reducers/todos';
import { createSelector } from 'reselect'
var visibilityFilter='SHOW_ALL';
var getVisibilityFilter = (state) => visibilityFilter;
var getTodos = (state) => todos;
export const getVisibleTodos = createSelector(
[ getVisibilityFilter, getTodos ],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
)
export default getVisibleTodos;
I have Tried in Component
<button onClick={()=>props.getVisibleTodos(props.SHOW_ALL , props.experimentData.lights)}> SHOW_COMPLETED</button>
Error
Uncaught Error: Actions must be plain objects. Use custom middleware
for async actions.
Blockquote
Help me Out ...
You should call the selector on the connect function like this:
import { connect } from 'react-redux';
import getVisibleTodos from 'your/selector/file';
function YourComponent({ visibleTodos }) {
// You can access visibleTodos inside your component
// because now it's on the props
return (
<div>
//...
</div>
);
}
const mapping = (state, props) => ({
visibleTodos: getVisibleTodos(state, props),
});
connect(mapping)(YourComponent);
Inside the mapping function, you have access to the state and props for the current component. Keep in mind that all selectors must receive the redux store in order to query the data.
Good luck!
I expect that your Redux store state looks something like this:
{
todos: [
{
id: 1,
text: 'Buy milk',
completed: false
},
...
],
visibilityFilter: 'SHOW_ALL'
}
If it is so, then you have to rewrite your getVisibilityFilter and getTodos selectors.
const getVisibilityFilter = (state) => state.visibilityFilter;
const getTodos = (state) => state.todos;
Previously you weren't accessing the values from your state, using this edited functions you are. See how I am using dot notation to access the keys of state, which is nothing more than a JavaScript Object.
Also, when u want to use a selector, you should use it in a container component, where u can access the store's state using mapStateToProps function.
The container could look something like this:
import React from 'react';
import { connect } from 'react-redux';
import { getVisibleTodos } from './selectors.js';
import TodosList from './TodosList.jsx';
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state)
}
}
const VisibleTodosList = connect(
mapStateToProps
)(TodosList);
export default VisibleTodosList;
Where the TodosList component is your own component that displays the todos. It will receive all visible todos using props (this.props.todos).
In my opinion, selectors aren't called from your view (presentational) components, they are meant to be used in containers, where you can access the application's data.
If you want to learn more about containers and presentational components, take a look at this article, it's worth reading.

Resources