Need to access a piece of state inside of action creator - react-native

I'm trying to access a piece of state from a different reducer in my action creator. I know there's a ton of questions out there like this already, but so far, I'm still hung up on why this isn't working even after reading them. My action creator takes a JSON from Firebase and filters it according to another piece of state called 'search' from the searching reducer. Here's that code:
Action Creator:
export const searchResult = () => {
const { currentUser } = firebase.auth();
return (dispatch, getState) => {
firebase.database().ref(`/users/${currentUser.uid}/entries`)
.orderByChild('uid')
.on('value', snapshot => {
const myObj = snapshot.val();
const { search } = getState().searching;
const list = _.pickBy(myObj, (((value) =>
value.make.indexOf(search) !== -1 ||
value.model.indexOf(search) !== -1));
dispatch({ type: SEARCH_RESULT_SUCCESS, payload: list });
});
};
};
And here's the Action Creator for the text input of the search filter:
export const searchChanged = (text) => {
return {
type: SEARCH_CHANGED,
payload: text
};
};
And here's the reducer for searchResult called entryReducer:
import {
SEARCH_RESULT_SUCCESS,
ENTRY_FETCH_SUCCESS,
SOLD_RESULT_SUCCESS
} from '../actions/types';
const INITIAL_STATE = [];
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case ENTRY_FETCH_SUCCESS:
return action.payload;
case SEARCH_RESULT_SUCCESS:
return action.payload;
case SOLD_RESULT_SUCCESS:
return action.payload;
default:
return state;
}
};
And here's the searchReducer:
import {
SEARCH_CHANGED,
} from '../actions/types';
const INITIAL_STATE = {
search: '',
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case SEARCH_CHANGED:
return { ...state, search: action.payload };
default:
return state;
}
};
And here's the combineReducers function:
import { combineReducers } from 'redux';
import AuthReducer from './AuthReducer';
import EntryFormReducer from './EntryFormReducer';
import EntryReducer from './EntryReducer';
import SearchReducer from './SearchReducer';
import PasswordReducer from './PasswordReducer';
export default combineReducers({
auth: AuthReducer,
entryForm: EntryFormReducer,
employees: EntryReducer,
searching: SearchReducer,
pw: PasswordReducer
});
Here's where I call searchResult():
class EmployeeList extends Component {
componentWillMount() {
this.props.searchResult();
And my mapStateToProps():
const mapStateToProps = (state) => {
const employees = _.map(state.employees, (val, uid) => {
return { ...val, uid };
});
return { employees };
};
export default connect(mapStateToProps, { searchResult, entryClear,
logoutUser })(EmployeeList);
And here's where searchChanged() is called inside the component Search:
import React, { Component } from 'react';
import { View } from 'react-native';
import { connect } from 'react-redux';
import { Icon } from 'react-native-vector-icons';
import { searchChanged, searchResult } from '../actions';
import Card from './common/Card';
import CardSection from './common/CardSection';
import Input from './common/Input';
class Search extends Component {
onSearchChange(text) {
this.props.searchChanged(text);
searchResult();
}
The code runs just fine, but nothing changes when I type into the search filter text input. I can see that my piece of state called 'search' from the 'searching' reducer gets updated. But my action creator 'searchResult' isn't accessing it to filter by it. Any help would be appreciated...I'm new to redux.

Related

React Native - Redux combineReducers not working

I try to save three values in redux for counters. This is my file with the combineReducers:
import { combineReducers } from 'redux';
const SET_FREUNDE = 'SET_FREUNDE';
const SET_CHATS = 'SET_CHATS';
const SET_VOTES = 'SET_VOTES';
export function setFreunde(value) {
return {
type: SET_FREUNDE,
value,
}
}
export function setChats(value) {
return {
type: SET_CHATS,
value,
}
}
export function setVotes(value) {
return {
type: SET_VOTES,
value,
}
}
const defaults = [
{
countervotes: 1,
counterchats: 1,
counterfreunde: 1
}
];
function counter(state=defaults, action) {
switch (action.type) {
case SET_FREUNDE:
return [
...state,
{
...state.counterfreunde = action.value
}
];
case SET_CHATS:
return [
...state,
{
...state.counterchats = action.value
}
];
case SET_VOTES:
return [
...state,
{
...state.countervotes = action.value
}
];
default:
return state;
}
}
const counters = combineReducers({
counter
});
export default counters;
In my App.tsx I create the store and want to get the values:
import { Provider, useSelector } from "react-redux";
import { createStore} from "redux";
import counters from './src/redux/counter';
const store = createStore(counters);
const AppStack = () => {
const counterfreunde = useSelector((state)=>state.counterfreunde);
const counterchats = useSelector((state)=>state.counterchats);
const countervotes = useSelector((state)=>state.countervotes);
return(
...
)
}
const App = () => {
...
return(
<Provider store={store} >
<AppStack/>
</Provider>
);
}
export default App;
But counterfreunde, ... are undefined. If I set the values directly in the store like this:
const store = createStore(() => ({
counterfreunde: 1,
counterchats: 1,
counterfreunde: 1
}));
all works fine. I think the problem is with the definition of the combineReducer.
When you use combineReducers, you provide it with a "Reducers Map Object" that maps the property names of the state to reducer that controls that property.
const counters = combineReducers({
counter
});
Calling combineReducers like above says that your root state has a property counter which is controlled by your counter reducer.
This is fine in isolation, but it changes your selectors because the properties like counterfreunde are not properties of the top-level state, they are properties of state.counter. So your selectors would need to be like this:
const counterfreunde = useSelector((state) => state.counter.counterfreunde);
If counter is the only reducer that you have then you don't need to use combineReducers. You can keep your selectors the same as they are now by providing your counter reducer as the reducer for the root state.
function counter(state=defaults, action) {
...
}
// remove the combineReducers
export default counter;
const store = createStore(counter);
Yes, the issue is in your combineReducers invocation. Attached is a working, nested reducer formulated by combining other reducers.
import { combineReducers } from 'redux';
import {
RESETPLAN,
SELECTEDPLAN,
selectPlanActionType,
} from './selectedPlan/types';
import { planType } from './plans/types';
import { PROTECTEDPLANS, plansActionType, plansType } from './plans/types';
const defaultSelectedState: planType = {};
const defaultPlanState: plansType = {
list: [],
};
function plansReducer(
state: plansType = defaultPlanState,
action: plansActionType
): plansType {
switch (action.type) {
case PROTECTEDPLANS: {
return {
...state,
...action.payload,
};
}
default: {
return state;
}
}
}
function selectedReducer(
state: planType = defaultSelectedState,
action: selectPlanActionType
): planType {
switch (action.type) {
case SELECTEDPLAN: {
return {
...state,
...action.payload,
};
}
case RESETPLAN: {
return defaultSelectedState;
}
default: {
return state;
}
}
}
export default combineReducers({
myPlans: plansReducer,
selected: selectedReducer,
});

Fetching data with redux doesn't work for me, initialState don't change

I started my RN project without redux and when I fetch data (using formData) it works fine without redux, but I've decided to inculude Redux in my project. that's what I did:
I created two new folder , one for actions (constants and ations files) and the other for store witch contains a folder for reducer
after that I created 3 actions like this: getScoreList, getScoreListSuccess, getScoreListFailure
function getScoreListSuccess(data) {
return {
type: FETCHING_SCORE_LIST_SUCCESS,
data: data
}
}
in the same file I created function for fetching data using async await and formData:
import FormData from 'FormData';
const formData = new FormData()
formData.append('userId', '1');
formData.append('key', 'XXXXXXXXX');
export const getScoreFromAPI = () => {
return async dispatch => {
dispatch(getScoreList());
try {
let res = await fetch('https://www.***.com/***/***/***.php', {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
},
body: formData,
})
let json = await res.json();
dispatch(getScoreListSuccess(json));
} catch (err) {
console.log("the error is: " + error)
}
};
};
Then I set my reducer for 3 cases:
import {FETCHING_SCORE_LIST, FETCHING_SCORE_LIST_SUCCESS, FETCHING_SCORE_LIST_FAILURE} from '../../actions/constants'
const initialState = {
scoreList: [],
isFetching: false,
error: false,
}
export default function getScoreList(state = initialState, action) {
switch (action.type) {
case FETCHING_SCORE_LIST:
return {
...state,
isFetching: true,
scoreList: [],
};
case FETCHING_SCORE_LIST_SUCCESS:
return {
...state,
isFetching: false,
scoreList: action.data,
};
case FETCHING_SCORE_LIST_SUCCESS:
return {
...state,
isFetching: false,
error: true,
};
default:
return state;
}
};
and in another file (index.js) that's what I did:
import {combineReducers} from 'redux'
import getScoreList from './scoreListReducer'
const rootReducer = combineReducers({
getScoreListS: getScoreList,
})
export default rootReducer
then I created my store with thunk middleware in configStore.js file
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './Reducers/index'
export default function configureStore() {
let store = createStore(rootReducer, applyMiddleware(thunk))
return store
}
app.js file:
...
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './Store/Reducers/index'
const createStoreWithMW = applyMiddleware(thunk)(createStore);
const store = createStoreWithMW(rootReducer);
export default class App extends Component<Props> {
render() {
return (
<Provider store={store}>
<Navigation/>
</Provider>
);
}
}
And finally I connected my component with redux :
...
import { connect } from 'react-redux';
import {getScoreFromAPI} from '../../actions/actions';
import { bindActionCreators } from 'redux';
...
function mapStateToProps(state) {
return{
scoreListState: state.getScoreListS,
};
}
const mapDispatchToProps = dispatch => bindActionCreators({
fetchScoreList: getScoreFromAPI
}, dispatch)
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreList);
to test it I console.warn scoreListState:
componentDidMount() {
console.warn(this.props.scoreListState);
}
this is what I have
I hope it was clear enough even if it's a little bit long

Persisted value won't affect the code down the chain

This is the filterAction action that emits a value to to the persisted reducer. This action is called with dispatch() in a dropdown in a component.
import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from './types';
// sort by title
export const sortByTitle = () => ({
type: SORT_BY_TITLE
});
// sort by released at
export const sortByReleasedAt = () => ({
type: SORT_BY_RELEASED_AT
});
The corresponding filterReducer
import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from '../actions/types';
const initialState = {
sortBy: 'title'
};
export default function(state = initialState, action) {
switch(action.type) {
case SORT_BY_TITLE:
return {
...state,
sortBy: 'title'
};
case SORT_BY_RELEASED_AT:
return {
...state,
sortBy: 'releasedAt'
};
default:
return state;
}
};
The filterReducer value is the one persisted in the main combined reducer.
export default combineReducers({
books: booksReducer,
book: bookReducer,
form: addBookFormReducer,
filter: filterReducer
});
The app's store
import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const middleware = [thunk];
const persistConfig = {
key: 'root',
storage,
whitelist: ['filter']
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer, {}, applyMiddleware(...middleware));
export const persistor = persistStore(store);
The value persisted on the filter part of the main reducer gets displayed in the dropdown changes on which call the filterAction with dispatch().
Here's the booksReducer that creates the books part of the app's store so books are displayed in a component.
import {
GET_BOOKS,
BOOKS_LOADING,
DELETE_BOOK,
SORT_BY_TITLE,
SORT_BY_RELEASED_AT
} from '../actions/types';
const initialState = {
books: [],
loading: false
};
const booksReducer = (state = initialState, action) => {
switch(action.type) {
case BOOKS_LOADING:
return {
...state,
loading: true
};
case GET_BOOKS:
return {
...state,
books: action.payload,
loading: false
};
case DELETE_BOOK:
return {
books: [...state.books.filter(book => book._id !== action.payload.id)]
};
case SORT_BY_TITLE:
return {
...state,
books: [...state.books.sort((a, b) => a.title < b.title ? -1 : 1 )]
};
case SORT_BY_RELEASED_AT:
return {
...state,
books: [...state.books.sort((a, b) => a.releasedAt < b.releasedAt ? -1 : 1 )]
};
default:
return state;
}
};
export default booksReducer;
The filter part of the main reducer persists Ok on the page reload however the books list is displayed with the default by title sort.
How do I get the app to persist the sort on the page reload? The complete repo is on https://github.com/ElAnonimo/booklister
On the BookList load the getBooks() action return reset the sorted books list to its default sorting so it was easier to persist only the filter prop of the store then sort the books list on each load of the BookList component.
The changes were made
to booksReducer
import {
GET_BOOKS,
BOOKS_LOADING,
DELETE_BOOK
} from '../actions/types';
const initialState = {
books: [],
loading: false
};
const booksReducer = (state = initialState, action) => {
switch(action.type) {
case BOOKS_LOADING:
return {
...state,
loading: true
};
case GET_BOOKS:
return {
...state,
books: action.payload,
loading: false
};
case DELETE_BOOK:
return {
books: [...state.books.filter(book => book._id !== action.payload.id)]
};
default:
return state;
}
};
export default booksReducer;
to BookList
class BookList extends Component {
componentDidMount() {
this.props.getBooks();
}
applySorting(books) {
const sortBy = this.props.filter.sortBy;
if (!sortBy) {
return books;
}
return books.sort((a, b) => a[sortBy] < b[sortBy] ? -1 : 1);
}
render() {
const { books, loading } = this.props.books;
let booksContent;
if (!books || loading) {
booksContent = <Spinner />;
} else {
if (books.length > 0) {
booksContent = this.applySorting(books).map(book => <BookItem book={book} key={book._id} />);
} else {
booksContent = <h4>No books found</h4>;
}
}
...
...

How to fix redux authentication in react-native-navigation?

I am creating redux authentication with react-native-navigation. It has two layouts which is switching by store auth state. When I am starting application in xCode simulator and trying to authenticate, it does not change a Auth layout to Main. But when I'm start Safari console to see messages from application, auth works fine and layout is changing. Please explain to me why this is happening?
index.js
import React, { Component, } from 'react';
import { Navigation, } from 'react-native-navigation';
import { Provider, } from 'react-redux';
import store from './src/store';
import registerScreens from './src/screens';
registerScreens(store, Provider);
export default class App {
constructor () {
this.auth = false;
store.subscribe(this.onStoreUpdate.bind(this));
this.start();
}
onStoreUpdate () {
const state = store.getState();
// if I'am write alert(state.auth) here, it show me false
if (this.auth != state.auth) {
this.auth = state.auth;
this.start();
}
}
start () {
switch (this.auth) {
case false:
Navigation.startTabBasedApp({
tabs: [{
screen: 'navigation.AuthScreen',
}, {
screen: 'navigation.RegisterScreen',
},],
});
break;
case true:
Navigation.startSingleScreenApp({
screen: {
screen: 'navigation.MainScreen',
},
});
break;
}
}
}
const application = new App();
auth.screen.js
import React, { PropTypes, Component, } from 'react';
import { ... } from 'react-native';
import { bindActionCreators, } from 'redux';
import { connect, } from 'react-redux';
import * as actions from './../actions';
class AuthScreen extends Component {
constructor (props) {
super(props);
this.handlePressEnter = this.handlePressEnter.bind(this);
}
handlePressEnter () {
// if I'am commenting line bellow and line in last then((), auth works fine
this.props.actions.request(true);
jsonFetch(url, {}).then((value) => {
return this.props.actions.auth(true);
}).catch((errors) => {
console.log(errors);
}).then(() => {
/* * */
this.props.actions.request();
});
}
render () {
....
}
}
function mapStateToProps (state, ownProps) {
return {
loading: state.loading,
};
}
function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps) (AuthScreen);
actions/index.js
import * as constants from './../constants';
export const request = (state = false) => {
return {
type: constants.REQUEST,
payload: {
state,
},
};
}
export const auth = (state = false) => {
return {
type: constants.AUTH,
payload: {
state,
},
};
}
reducers/index.js
import { combineReducers, } from 'redux';
import * as constants from './../constants';
const loading = (state = false, action) => {
switch (action.type) {
case constants.REQUEST:
return action.payload.state;
default:
return state;
}
}
const auth = (state = false, action) => {
switch (action.type) {
case constants.AUTH:
return action.payload.state;
default:
return state;
}
}
const reducers = combineReducers({
loading,
auth,
});
export default reducers;
store.index.js
import { createStore, applyMiddleware, } from 'redux';
import thunkMiddleware from 'redux-thunk';
import { createLogger, } from 'redux-logger';
import reducers from './../reducers';
const loggerMiddleware = createLogger();
const store = createStore(reducers, applyMiddleware(thunkMiddleware, loggerMiddleware));
export default store;

react-native with redux error

I follow the demo to create a project. I used react-native with redux, then I get this error "undefined is not a function(evaluating '(0, _redux.createStore)(_todoListRedux.reducer)')`".
This is my code:
index.android.js
import {AppRegistry, View} from 'react-native'
import {createStore} from 'redux'
import {reducer} from './todoListRedux'
const store = createStore(reducer)
import App from './App'
const AppWithStore = () => <App store={store} />
AppRegistry.registerComponent('redux', () => AppWithStore)
todoListRedux.js
export const types = {
ADD: 'ADD',
REMOVE: 'REMOVE',
}
export const actionCreators = {
add: (item) => {
return {type: types.ADD, payload: item}
},
remove: (index) => {
return {type: types.REMOVE, payload: index}
}
}
const initialState = {
todos: ['Click to remove', 'Learn React Native','Write Code','Ship App'],
}
export const reducer = (state = initialState, action) => {
const {todos} = state
const {type, payload} = action
switch(type) {
case types.ADD: {
return {
...state,
todos: [payload, ...todos],
}
}
case types.REMOVE: {
return {
...state,
todos: todos.filter((todo,i) => i !== payload),
}
}
}
return state
}
any help will be appreciated!
The store added in createStore. Should be a store with combineReducers.
And as far as I know, the middleware should be passed as second parameter in the createStore.
Something like.
import { applyMiddleware, createStore } from 'redux';
import rootReducer from '../store';
const middleware = applyMiddleware({you can pass here (logger or thunk, or the available options)});
//example
const middleware = applyMiddleware(thunk);
const store = createStore(rootReducer, middleware);