Redux store property gets nested into itself on update - javascript

I'm having this issue where my props are ending up looking like this (on console.log):
{
fetchRoles: f(),
roles:
roles: ["Admin", "Manager"],
}
As you can see, somewhere in my code I'm making a mistake that causes the roles prop to get nested into itself, which would force me into doing const { roles } = this.props.roles; in order to retrieve my data (which works BTW).
I've looked around for help but not many people seem to have run into this issue (I'm just getting started with redux).
Below you can see my files:
rolesReducer.js:
import { FETCH_ROLES } from "../actions/types";
const initialState = {
roles: [],
};
export default function (state = initialState, action) {
const { roles } = action;
switch (action.type) {
case FETCH_ROLES:
return {
...state, //also tried ...state.roles and same issue.
roles,
};
default:
return state;
}
}
rolesActions.js:
import { FETCH_ROLES } from "./types";
const roles = ["SuperAdmin"];
export function fetchRoles() {
return function (dispatch) {
dispatch({
type: FETCH_ROLES,
roles,
});
};
}
reducers/index.js (root reducer):
import { combineReducers } from "redux";
import rolesReducer from "./rolesReducer";
import roleMembersReducer from "./roleMembersReducer";
export default combineReducers({
roles: rolesReducer,
roleMembers: roleMembersReducer,
});
PermissionsManager.jsx:
import React, { Component } from "react";
import { connect } from "react-redux";
import { Container } from "react-bootstrap";
import { fetchRoles } from "../redux/actions/rolesActions";
import RoleContainer from "./RoleContainer";
class PermissionsManager extends Component {
componentDidMount() {
this.props.fetchRoles();
}
render() {
console.log(this.props);
const { roles } = this.props.roles;
return (
<Container>
{roles.map((role) => {
return <RoleContainer key={role} role={role} />;
})}
</Container>
);
}
}
const mapStateToProps = (state) => {
return {
roles: state.roles,
};
};
export default connect(mapStateToProps, { fetchRoles })(PermissionsManager);
Edit 1 - Adding reducer log:
As suggested, I logged the reducer, specifically state and action:
state:
{
roles: [],
}
action:
{
roles: ["Admin", "Manager"],
type: "FETCH_ROLES",
}
No duplication or abnormal structures I believe.

One way to shape the store like you're asking is to flatten roles in rolesReducer.js,
you can do so storing the received array directly in the partial state:
initialState would need to look like
const initialState = []
and in the switch statement
case FETCH_ROLES:
return roles

Related

How do I export a redux reducer that uses a prepare function

I am currently working on a anecdotes application, and anytime a user votes on a certain anecdote, it should display a notification then disappear 10 seconds later, however, I am struggling to export my reducer that uses the prepare function that is suppose to, I think, get my multiple arguments ready for the actual reducer. Here is my code in question:
import { createSlice } from "#reduxjs/toolkit";
const notificationSlice = createSlice({
name: 'notification',
initialState: '',
reducers: {
test: {
createNotification(state, action) {
console.log(action)
},
prepare(...args) {
return {
payload: args
}
}
}
}})
export const { createNotification } = notificationSlice.actions
export default notificationSlice.reducer
I thought I could just export const { test.createNotification } = notificationSlice.actions but that does not work due to the dot in the variable name.
How would I then export my createNotification reducer since test is the first property of the reducers object?
I found the solution, I just name the reducer createNotification with two key values of reducer and prepare
import { createSlice } from "#reduxjs/toolkit";
const notificationSlice = createSlice({
name: 'notification',
initialState: '',
reducers: {
createNotification: {
reducer(state, action) {
console.log(action)
},
prepare(...args) {
return {
payload: args
}
}
}
}})
export const { createNotification } = notificationSlice.actions
export default notificationSlice.reducer

React-Native Redux connected component not dispatching actions

I have connected my component to redux, defined mapStateToProps and mapDispatchToProps and still dispatch doesn't seem to be doing anything.
When I run this, 'console.log("Setting token")' is printed out, but nothing else happens. So this.props.setToken is firing, because that's where that console.log is set, but the store isn't updating and this.props.auth_token isn't showing on the screen like it should if dispatch(setToken) had fired correctly...
AppScreen
import React from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import { setToken } from './reducer';
class AppScreen extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.setToken("token_abc");
}
render() {
if (this.props.loading) {
return (
<View style={{ flex: 1 }}>
<ActivityIndicator />
</View>
)
} else {
return (
<View>
<Text>You're in! '{this.props.auth_token}'</Text>
</View>
)
}
}
}
function mapStateToProps(state) {
return {
user: state.user,
auth_token: state.auth_token,
loading: state.loading,
error: state.error
};
}
const mapDispatchToProps = dispatch => {
return {
setToken: token => {
console.log("Setting token")
dispatch(setToken(token));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AppScreen);
Reducer
import { createSlice } from "#reduxjs/toolkit";
const appSlice = createSlice({
name: "app",
initialState: {
loading: true,
auth_token: "",
error: "",
user: {}
},
reducers: {
setToken: (state, action) => {
state.auth_token = action.payload;
state.loading = false;
},
},
extraReducers: {
}
});
export const { setToken } = appSlice.actions;
export const appReducer = appSlice.reducer;
Store
import { appReducer } from "./App/reducer";
import { configureStore, getDefaultMiddleware } from "#reduxjs/toolkit";
const middleware = [
...getDefaultMiddleware(),
]
const store = configureStore({
reducer: {
app: appReducer
},
middleware,
});
export default store;
You've got a mismatch between the combined store reducer setup, and what your mapState is trying to do.
Your component expects that state.auth_token will exist. The appSlice.reducer has an auth_token field inside. Therefore, state.auth_token will only exist if appSlice.reducer is the root reducer for the entire store.
However, you're currently passing appSlice.reducer as a "slice reducer" for state.app, which will result in state.app.auth_token.
You need to either pass reducer: appReducer to make it be the root reducer by itself, or update your mapState to read state.app.auth_token.
Given that you're likely going to have other slices, I'd recommend the latter approach.

React Native: TypeError: undefined is not an object (evaluating '_this.props.data.map')

I wonder if React Native has a bug that needs fixing that gives the following error:
React Native: TypeError: undefined is not an object (evaluating
'_this.props.data.map')
I am pretty good at this and yet I cannot seem to resolve why I am getting this error when I put together this component:
import React, { Component } from "react";
import { View, Animated } from "react-native";
class Swipe extends Component {
renderCards() {
return this.props.data.map(item => {
return this.props.renderCard(item);
});
}
render() {
return <View>{this.renderCards()}</View>;
}
}
export default Swipe;
I have checked and double checked through various debugging practices that the problem is not with my action creator or reducer and after various refactors I got those working correctly.
I decided to do the above component from scratch whereas before I was reusing another component and yet I still get the above error.
I ask if it's a bug with RN because someone else posted a similar problem but they did not get the answer they needed.
It is not a scope issue with this because if I refactor it like so:
renderCards = () => {
return this.props.data.map(item => {
return this.props.renderCard(item);
});
};
It does absolutely nothing for me, same error message. The message saying is not an object is confusing too, it's an array and map() can only iterate through arrays, so not sure what not being an object has to do with it.
The above component is being called in this screen:
import React, { Component } from "react";
import { View, Text } from "react-native";
import { connect } from "react-redux";
import Swipe from "../components/Swipe";
class DeckScreen extends Component {
renderCard(job) {
return (
<Card title={job.title}>
<View style={styles.detailWrapper}>
<Text>{job.company}</Text>
<Text>{job.post_date}</Text>
</View>
<Text>
{job.description.replace(/<span>/g, "").replace(/<\/span>/g, "")}
</Text>
</Card>
);
}
render() {
return (
<View>
<Swipe data={this.props.jobs} renderCard={this.renderCard} />
</View>
);
}
}
const styles = {
detailWrapper: {
flexDirection: "row",
justifyContent: "space-around",
marginBottom: 10
}
};
function mapStateToProps({ jobs }) {
return { jobs: jobs.listing };
}
export default connect(mapStateToProps)(DeckScreen);
This is what the action creator looks like:
import axios from "axios";
// import { Location } from "expo";
import qs from "qs";
import { FETCH_JOBS, LIKE_JOB } from "./types";
// import locationify from "../tools/locationify";
const JOB_ROOT_URL = "https://authenticjobs.com/api/?";
const JOB_QUERY_PARAMS = {
api_key: "5634cc46389d0d872723b8c46fba672c",
method: "aj.jobs.search",
perpage: "10",
format: "json"
};
const buildJobsUrl = () => {
const query = qs.stringify({ ...JOB_QUERY_PARAMS });
return `${JOB_ROOT_URL}${query}`;
};
export const fetchJobs = (region, callback) => async dispatch => {
try {
const url = buildJobsUrl();
let { data } = await axios.get(url);
dispatch({ type: FETCH_JOBS, payload: data });
callback();
} catch (e) {
console.log(e);
}
};
export const likeJob = job => {
return {
payload: job,
type: LIKE_JOB
};
};
and reducer:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
return action.payload;
default:
return state;
}
}
and the combineReducer is setup correctly as well:
import { combineReducers } from "redux";
import auth from "./auth_reducer";
import jobs from "./jobs_reducer";
import likedJobs from "./likes_reducer";
export default combineReducers({
auth,
jobs,
likedJobs
});
The listing: [] is based off the structure of the response I get back. When I console.log(data);, the actual data I care about is inside of listing property. So I set up the INITIAL_STATE to default listing to be an empty array with the intent to ensure I could map over the array and not worry about the case where I have not yet fetched the list of jobs. When I go to the API endpoint directly you can see it below:
I think the problem is simply that this.props.jobs is undefined. Your initial state is defined as { listing: [] }, however you mapStateToProps do { jobs: ... }.
Try changing initialState to { jobs: [] }, so that it always work on your first rendering.
I think your mapStateToProps should be:
mapStateToProps = (state) => {
return { jobs: listings.listing }
}
EDIT
Actually, it could be even better if you 'name' your state correctly in your reducer, like:
const INITIAL_STATE = { jobs: [] }
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
const jobs = action.payload.listings.listing
return { ...state, jobs };
default:
return state;
}
}
Then in your mapStateToProps:
mapStateToProps = ({ jobs }) => {
return { jobs }
}
The issue is in your reducer. Please refer the below changes:
import { FETCH_JOBS } from "../actions/types";
const INITIAL_STATE = {
listing: []
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case FETCH_JOBS:
const { listings } = action.payload
return {...state, listing: listings.listing}
default:
return state;
}
}
Hope this will help.
function mapStateToProps({ jobs }) {
return { jobs: jobs.listing };
}
the above is making confusion for you try the below one
try to put
function mapStateToProps( state ) {
return { jobs: state.jobs.listing };
}
as you have defined your reducer as follow
export default combineReducers({
auth,
jobs,
likedJobs
});
jobs is your variable to access jobs reducer

Generic Reducers/Actions in React/Redux

I am trying to determine how to pull in multiple pieces of data to use in the same component.
Every example I see with React/Redux requests very specific data and has reducers and actions to handle that exact type of data. However, I have not been able to find information about handling more generic data.
For example, I have a few different components (or categories) on my site. One of those components is Cards. So, if a user clicks on the link for /cards/hockey it should request the hockey data from the API (if it isn't in the store already), and display it in the Cards page. If a user clicks the link for /cards/football, it should follow the same procedure, checking to see if it has the data in store yet, if not pulling it from the API, and displaying the Cards page with that data.
Another component type might be stats with stats about different sports teams.
I will not always know what types of cards are available ahead of time, so I cannot hardcode the specific sports types in my application.
So in this case, I'd like to only create two components: cards and stats, but have dynamically loaded data to populate those components.
Right now I have too much repetition going on and it is hard coded. This means that I cannot dynamically add new types in the future without creating new code to handle each of these types.
So, for example, right now I have /actions/footballCardActions.js and /actions/hockeyCardActions.js. I then have /reducers/footballCardReducers.js and /reducers/hockeyCardReducers.js. I might have similar components for the Stats component as well.
I'm also specifying status such as FETCH_HOCKEY_CARDS_SUCCESS or FETCH_FOOTBALL_CARDS_SUCCESS.
Again these are all hard coded, which makes scalability difficult.
One example I am trying to follow is https://scotch.io/tutorials/bookshop-with-react-redux-ii-async-requests-with-thunks - but again it uses very specific data requests, rather than generic ones.
What can I do to make my code work more generically so that I do not need to hard code specific datasets. Are there any good tutorials out there that deal with a similar situation?
More clarification
One of my components (screens) is a sports card screen. The menu system (with links) is automatically generated on site load from an API so I do not always know what links are available. So, there may be links for hockey, football, as well as a number of other sports that I have not thought of. When the menu link is clicked, it will call the API for that sport type and display the data on the sports card screen.
Based on the above link (and other similar sites) I've figured out how to hard-code each request for a specific sport in the actions and reducers section, but I have not been able to figure out how to do this generically if I do not know the sports ahead of time.
Further clarification based on current answers
If someone adds a new sport to the API database called MuffiBall, my application needs to be able to handle it. So, I cannot be expected to add new JavaScript code for each new sport that is added to the API.
All sports cards retrieved from the database follow the same structure.
An outline of my current code
index.js
//index.js
//Other imports here (not shown)
import Cards from './components/CardsPage'
import * as cardActions from './actions/cardActions';
import * as statsActions from './actions/statsActions';
import configureStore from './store/configureStore';
const store = configureStore();
/* Bad place to put these, and currently I am expected to know what every sport is*/
store.dispatch(hockeyActions.fetchHockey());
store.dispatch(footballActions.fetchFootball());
store.dispatch(muffiballActions.fetchMuffiball());
render(
<Provider store={store}>
<Router>
<div>
/* Navigation menu here (not shown) */
/* Currently it is manually coded, */
/* but I will be automatically generating it based on API */
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/cards/:val" component={Cards} />
<Route path="/stats/:val" component={Stats} />
</div>
</Router>
</Provider>,
document.getElementById('app')
);
store/configureStore.js
// store/configureStore.js
import {createStore, compose, applyMiddleware} from 'redux';
// Import thunk middleware
import thunk from 'redux-thunk';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
return createStore(rootReducer, initialState,
// Apply to store
applyMiddleware(thunk)
);
}
actions/actionTypes
// actions/actionTypes
export const FETCH_HOCKEY_SUCCESS = 'FETCH_HOCKEY_SUCCESS';
export const FETCH_FOOTBALL_SUCCESS = 'FETCH_FOOTBALL_SUCCESS';
export const FETCH_MUFFIBALL_SUCCESS = 'FETCH_MUFFIBALL_SUCCESS';
actions/hockeyActions.js (one such file for every sport - need to make this one generic file):
// hockeyActions.js (one such file for every sport - need to make this one generic file):
import Axios from 'axios';
const apiUrl = '/api/hockey/';
// Sync Action
export const fetchHockeySuccess = (hockey) => {
return {
type: 'FETCH_HOCKEY_SUCCESS',
hockey
}
};
//Async Action
export const fetchHockey = () => {
// Returns a dispatcher function
// that dispatches an action at a later time
return (dispatch) => {
// Returns a promise
return Axios.get(apiUrl)
.then(response => {
// Dispatch another action
// to consume data
dispatch(fetchHockeySuccess(response.data))
})
.catch(error => {
console.log(error)
throw(error);
});
};
};
reducers/hockeyReducers.js (one such file for every sport - need to make this one generic file)
// reducers/hockeyReducers.js (one such file for every sport - need to make this one generic file)
import * as actionTypes from '../actions/actionTypes'
export const hockeyReducer = (state = [], action) => {
switch (action.type) {
case actionTypes.FETCH_HOCKEY_SUCCESS:
return action.hockey;
default:
return state;
}
};
reducers/index.js
// reducers/index.js
import { combineReducers } from 'redux';
import {hockeyReducer} from './hockeyReducers'
import {footballReducer} from './footballReducers'
import {muffiballReducer} from './muffiballReducers'
export default combineReducers({
hockey: hockeyReducer,
football: footballReducer,
muffiball: muffiballReducer,
// More reducers for each sport here
});
components/CardsPage.js:
//components/CardsPage.js
import React from 'react';
import { connect } from 'react-redux';
class Cards extends React.Component{
constructor(props){
super(props);
this.state = {
data: this.props.data,
}
}
componentWillReceiveProps(nextProps){
this.setState({
data: nextProps.data,
})
}
render(){
return(
{/* cards displayed from this.state.data */}
)
}
}
const mapStateToProps = (state, ownProps) => {
return {
data: state[ownProps.match.params.val]
}
};
export default connect(mapStateToProps)(Cards);
take a step back and identify the data types that have unique shapes, eg cards and stats. You will build a store slice for each of these with it's own actions, reducers, and selectors. The sport should just be a variable you use as an argument to your actions and selectors.
eg
Async Action
export const fetchCards = (sport) => {
return (dispatch) => {
return Axios.get(`/api/${sport}/`)
.then(response =>
dispatch(fetchCardSuccess({ sport, data: response.data }))
)
.catch(error => {
console.log(error)
throw(error);
});
};
};
Reducer
export const cardReducer = (state = {}, action) => {
switch (action.type) {
case actionTypes.FETCH_CARD_SUCCESS:
return { ...state, [action.sport]: action.data };
default:
return state;
}
};
Card Selector
export const getSport(state, sport) {
return state.cards[sport];
}
You'll probably want another slice for managing a list of the available sports, fetched from the server, and other global data.
Soo this assumes your "generic data" always will have the same shape.
You could have a generic <Results /> component. Not sure how you are doing routing, but you can use the path name of the URL to determine which data to fetch and display.
The route component (React Router 4) could look like this:
<Route path="/cards/:id" render={props => <Results {...props} />}
Then in your <Results/> component you can use react-redux to map your redux state to the component props. In componentDidMount you could see if you have the appropriate data. If you do not have the appropriate data then dispatch an action from componentDidMount to fetch it. Something like this
import { connect } from 'react-redux';
import React from 'react';
import { fetchDataAction } from './actions';
class Results extends React.Component {
componentDidMount() {
// check if results exists, if not then fire off an action to get
// data. Use whatever async redux pattern you want
if (!this.props.results) {
this.props.fetchData();
}
}
render() { /* DO SOMETHING WITH RESULTS, OR LACK OF */ }
}
const mapStateToProps = (state, ownProps) => ({
results: state.results[ownProps.match.params.id],
});
const mapDispatchToProps = (dispatch, ownProps) => ({
fetchData() {
// send path parameter via action to kick off async fetch
dispatch(fetchDataAction(ownProps.match.params.id));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Results);
You could have a results reducer that would just be an object that maps category to results. Here is what the results reducer could look like:
export default (state = {}, action) => {
switch(action.type) {
case 'FETCH_LOADED':
const { payload: { type, results } } = action;
return {
...state,
[type]: results,
};
default:
return state;
};
};
A methodology that is picking up popularity for reusable redux actions/reducers is Redux Ducks. Here's a good helper library and example to implement this in your codebase.
Building off the example in the above link that would look something like this for you:
// remoteObjDuck.js
import Duck from 'extensible-duck'
import axios from 'axios'
export default function createDuck({ namespace, store, path, initialState={} }) {
return new Duck({
namespace, store,
consts: { statuses: [ 'NEW', 'LOADING', 'READY', 'SAVING', 'SAVED' ] },
types: [
'UPDATE',
'FETCH', 'FETCH_PENDING', 'FETCH_FULFILLED',
'POST', 'POST_PENDING', 'POST_FULFILLED',
],
reducer: (state, action, { types, statuses, initialState }) => {
switch(action.type) {
case types.UPDATE:
return { ...state, obj: { ...state.obj, ...action.payload } }
case types.FETCH_PENDING:
return { ...state, status: statuses.LOADING }
case types.FETCH_FULFILLED:
return { ...state, obj: action.payload.data, status: statuses.READY }
case types.POST_PENDING:
case types.PATCH_PENDING:
return { ...state, status: statuses.SAVING }
case types.POST_FULFILLED:
case types.PATCH_FULFILLED:
return { ...state, status: statuses.SAVED }
default:
return state
}
},
creators: ({ types }) => ({
update: (fields) => ({ type: types.UPDATE, payload: fields }),
get: (id) => ({ type: types.FETCH, payload: axios.get(`${path}/${id}`),
post: () => ({ type: types.POST, payload: axios.post(path, obj) }),
patch: () => ({ type: types.PATCH, payload: axios.patch(`${path}/${id}`, obj) })
}),
initialState: ({ statuses }) => ({ obj: initialState || {}, status: statuses.NEW, entities: [] })
})
}
and each sport would create a single duck that will reuse the same functionality.
Hockey:
// hockeyDuck.js
import createDuck from './remoteObjDuck'
export default createDuck({ namespace: 'my-app', store: 'hockeyCards', path: '/cards/hockey' })
Football:
// footballDuck.js
import createDuck from './remoteObjDuck'
export default createDuck({ namespace: 'my-app', store: 'footballCards', path: '/cards/football' })
Then combine the reducers in the store:
// reducers.js
import { combineReducers } from 'redux'
import footballDuck from './footballDuck'
import hockeyDuck from './hockeyDuck'
export default combineReducers({ [footballDuck.store]: footballDuck.reducer, [hockeyDuck.store]: hockeyDuck.reducer })
If you want to dynamically add reducers to redux on the fly you will have to use something like: https://github.com/ioof-holdings/redux-dynamic-reducer. Then you can create the duck on the fly depending on your API call response:
//get from API
var sport = "football";
var footballDuck = createDuck({ namespace: 'my-app', store: 'cards', path: `/cards/${sport}` });
store.attachReducer({ [footballDuck.store]: footballDuck.reducer });
// structure (something like...)
/*
./components
./redux
./redux/actions
./redux/reducers
./redux/sagas
./redux/types
./util
*/
/* ------------------------------------------------- */
/* package.json */
{
(...)
"proxy": "http://localhost:3000",
(...)
}
/* ------------------------------------------------- */
/* index.js or otherComponent.js */
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import reducers from './redux/reducers/index'
import logger from 'redux-logger'
import createSagaMiddleware from 'redux-saga'
import indexSagas from './redux/sagas/indexSagas'
import { environment } from './util/baseUrl'
const sagaMiddleware = createSagaMiddleware()
const store =
environment === 'DEV' ?
createStore(
reducers,
window.__REDUX_DEVTOOLS_EXTENSION__ &&
window.__REDUX_DEVTOOLS_EXTENSION__(),
applyMiddleware(sagaMiddleware, logger)
) :
createStore(
reducers,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(indexSagas)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app'))
/* ------------------------------------------------- */
/* baseURL.js */
const DEV = 'DEV'
const PROD = 'PROD'
/*-----------------------------------------*/
/*------*/ export const environment = DEV /* <------- */
/*-----------------------------------------*/
export const baseURL =
environment === DEV ?
'/api/v1/' :
'https://abcde.website.net/api/v1/'
/* ------------------------------------------------- */
/* genericTypes.js */
export const GET_REGISTERS_REQUEST = 'GET_REGISTERS_REQUEST'
export const GET_REGISTERS_SUCCESS = 'GET_REGISTERS_SUCCESS'
export const GENERIC_ERROR_MSG = 'GENERIC_ERROR_MSG'
/* ------------------------------------------------- */
/* actions.js */
export const getRegistersRequest = ( route ) => {
return {
type: GET_REGISTERS_REQUEST,
route,
}
}
export const getRegistersSuccess = ( data ) => {
return {
type: GET_REGISTERS_SUCCESS,
data,
}
}
export const genericErrorMsg = ( errorMsg ) => {
return {
type: GENERIC_ERROR_MSG,
errorMsg,
}
}
/* ------------------------------------------------- */
/* genericReducer.js */
import { GET_REGISTERS_REQUEST, GET_REGISTERS_SUCCESS, GENERIC_ERROR_MSG } from '../types/genericTypes'
const INITIAL_STATE = {
data: [],
isFetching: false,
isLoaded: false,
error: false,
errorMsg: '',
}
const genericReducer = (state = INITIAL_STATE, action) => {
switch(action.type){
case GET_REGISTERS_REQUEST:
return {
...state,
data: [],
isFetching: true,
isLoaded: false,
error: false,
errorMsg: '',
}
case GET_REGISTERS_SUCCESS:
return {
...state,
data: action.data,
isFetching: false,
isLoaded: true,
}
case GENERIC_ERROR_MSG:
return {
...state,
isFetching: false,
error: true,
errorMsg: action.errorMsg,
}
default:
return state
}
}
export default genericReducer
/* ------------------------------------------------- */
/* yourComponent.js */
import React, { Component } from "react"
import { connect } from 'react-redux'
import { getRegistersRequest } from '../../redux/actions'
//(...)
// this.props.getRegistersRequest('cards/hockey')
// this.props.getRegistersRequest('cards/football')
//(...)
const mapStateToProps = (state) => {
return {
data: state.genericReducer.data,
isFetching: state.genericReducer.isFetching,
isLoaded: state.genericReducer.isLoaded,
error: state.genericReducer.error,
errorMsg: state.genericReducer.errorMsg,
}
}
const mapDispatchToProps = (dispatch) => {
return {
getRegistersRequest: ( route ) => dispatch(getRegistersRequest( route )),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(yourComponent)
/* ------------------------------------------------- */
/* indexSagas.js */
import { takeLatest } from 'redux-saga/effects'
import axios from 'axios'
import { GET_REGISTERS_REQUEST } from '../types/genericTypes'
import { getRegistersRequest } from './genericSagas'
function* indexSagas() {
try {
yield (takeLatest(GET_REGISTERS_REQUEST, getRegistersRequest, axios))
}
catch (e) {
// (...)
}
}
export default indexSagas
/* ------------------------------------------------- */
/* genericSagas.js */
import { put } from 'redux-saga/effects'
import { getRegistersSuccess, genericErrorMsg } from '../actions'
export function* getRegistrosRequest(axios, action) {
const rest = createRest(axios)
try {
let route = ''
switch (action.route) {
case 'cards/hockey':
case 'cards/football':
route = action.route
break
default: {
yield put(genericErrorMsg('Route [ ' + action.route + ' ] not implemented yet!'))
return
}
}
const data = yield rest.get(route)
yield put(getRegistersSuccess(data))
}
catch (e) {
yield put(genericErrorMsg(e))
}
}
/* ------------------------------------------------- */
/* createRest */
import { baseURL } from '../../util/baseUrl'
function createRest(axios){
const token = localStorage.getItem('yourToken')
const rest = axios.create({
baseURL: baseURL,
headers:{
Authorization: 'Bearer ' + token
}
})
return rest
}
export default createRest
/* ------------------------------------------------- */

Re render Component when props updates (w/ Redux)

Ok so i have 2 components, Map and App. App component basically holds the map.
This is the code of my App:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { StackNavigator, TabNavigator } from 'react-navigation';
//Redux
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
//Screen Imports
import CenterNew from './screens/CenterNew';
import Map from './components/Map';
import Profile from './containers/Profile';
import Review from './containers/Review';
import { connect } from 'react-redux';
var store = createStore(rootReducer, {}, applyMiddleware(thunk));
class App extends Component {
render () {
{ console.ignoredYellowBox = ['Remote debugger']; }
const MainScreenNavigator = TabNavigator({
Centers: {screen: Map},
Review: {screen: Review},
Profile: {screen: Profile}
});
MainScreenNavigator.navigationOptions = {
title: this.props.map.title
};
const Screens = StackNavigator({
Home: {screen: MainScreenNavigator},
CenterNew: {screen: CenterNew}
});
return (
<Provider store={store}>
<Screens />
</Provider>
);
}
}
const connectWithStore = (store, WrappedComponent, mapStateToProps) => {
let ConnectedWrappedComponent = connect(mapStateToProps)(WrappedComponent);
return (props) => {
return <ConnectedWrappedComponent {...props} store={store} />
}
}
const mapStateToProps = (state) => {
return {
map: state.map
};
};
App = connectWithStore(createStore(rootReducer, {}, applyMiddleware(thunk)), App, mapStateToProps);
export default App;
In the snippet, i set the title using
MainScreenNavigator.navigationOptions = {
title: this.props.map.title
};
So whenever App is rendered, I log the initial state as you could see in componentWillMount() and this is the result. A syou can see, the initial title is 'Home'
now, in my Map I have a button which triggers this action creator just to change the header text:
this.props.changeHeaderTitle('Test');
console.log(this.props.map);
code:
export const changeHeaderTitle = (title) => {
return {
type: 'CHANGE_HEADER_TITLE',
payload: title
};
};
and this is my reducer:
const INITIAL_STATE = {
isPinnable: false,
pinnedCoordinateLatitude: 0,
pinnedCoordinateLongitude: 0,
region: {
latitude: 14.582524,
longitude: 121.061547,
latitudeDelta: 0.007,
longitudeDelta: 0.007
},
title: '',
searched: false
};
export default (state = INITIAL_STATE, action) => {
switch(action.type) {
case 'ENABLE_PINNING':
return { ... state, isPinnable: true }
case 'DISABLE_PINNING':
return { ... state, isPinnable: false,
pinnedCoordinateLatitude: action.payload.latitude,
pinnedCoordinateLongitude: action.payload.longitude }
case 'GET_PINNED_COORDINATE':
let test = action.payload.longitude;
return {
...state,
isPinnable: false,
pinnedCoordinateLatitude: action.payload.latitude,
pinnedCoordinateLongitude: action.payload.longitude
}
case 'GET_CURRENT_REGION':
return { ...state, region: region }
case 'CHANGE_CURRENT_REGION':
return { ...state, region: action.payload}
case 'CHANGE_HEADER_TITLE':
return { ...state, title: action.payload }
case 'SEARCHED_VIEW':
return { ...state, searched: action.payload }
default:
return state;
}
};
whenever the action is triggered, i know the title is updated because i log it as you can see in
this.props.changeHeaderTitle('Test');
console.log(this.props.map);
but in my view, the title is still 'Home'.. I referred to this: rerender react component when prop changes and tried to use componentWillRecieveProps() but it is not logging when this.props.changeHeaderTitle('Test'); action is triggered from Map component.
You need to connect your Redux store with your component. Therefore you should add this snippet:
mapStateToProps(store){
const { title } = store;
return { title };
}
export default connect(mapStateToProps, { changeHeaderTitle })(App)
The mapStateToProps function will subscribe to redux store updates. Every time your state changes, your component will rerender automatically. You can find more information about connecting components to the redux store here: react-redux api docs
You can't update the screen title like in the code you provide.
//This is only needed if you want to set the title from another screen
static navigationOptions = ({ navigation }) => {
const {state} = navigation;
return { title: `${state.params.title}`,
};
};
ChangeThisTitle = (titleText) => {
const {setParams} = this.props.navigation; setParams({ title: titleText })
}
//Your render method
render(){
//Views goes here
}
To change the title you have call the change title method from the onPress.
You may please refer this link for more details.

Categories

Resources