How can I add a charging indicator based on the Redux status?
I need to place a loading screen while sending the data.
charging indicator component
import React from 'react';
import { StyleSheet } from 'react-native';
import AnimatedLoader from "react-native-animated-loader";
import {connect} from 'react-redux'
class Loader extends React.Component {
constructor(props) {
super(props);
this.state = {visible: false };
}
// componentDidMount() {
// setInterval(() => {
// this.setState({
// visible: !this.state.visible
// });
// }, 2000);
// }
render() {
const { visible } = this.props;
if (!visible) return outVisible();
return (
<AnimatedLoader
visible={visible}
overlayColor="rgba(255,255,255,0.75)"
source={require("./loader.json")}
animationStyle={styles.lottie}
speed={1}
>
<Text>Carregando...</Text>
</AnimatedLoader>
);
}
}
const styles = StyleSheet.create({
lottie: {
width: 200,
height: 200
}
});
const mapStateToProps = (state) => ({visible: state.visible});
const mapDispatchToProps = dispatch => {
return {outVisible: () => dispatch(setVisible({visible: false}))}
}
export default connect(mapStateToProps,mapDispatchToProps)(Loader)
Action
import { SET_VISIBLE } from './actionsTypes'
export const setVisible = visible => {
return {
type: SET_VISIBLE,
payload: visible
}
}
Reducer
import { SET_VISIBLE } from '../actions/actionsTypes'
const initialState = {
visible: false
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case SET_VISIBLE:
return {
...state,
visible: action.payload.visible
};
default:
return state;
}
}
export default reducer
Store Config
import {
createStore,
combineReducers,
compose,
applyMiddleware
} from 'redux'
import thunk from 'redux-thunk'
import postReducer from './reducers/post'
import userReducer from './reducers/user'
import messageReducer from './reducers/message'
import loadingReducer from './reducers/loading'
const reducers = combineReducers({
user: userReducer,
post: postReducer,
message: messageReducer,
visible: loadingReducer
})
const storeConfig = () => {
return createStore(reducers, compose(applyMiddleware(thunk)))
}
export default storeConfig
actions types
export const USER_LOGGED_IN = 'USER_LOGGED_IN'
export const USER_LOGGED_OUT = 'USER_LOGGED_OUT'
export const SET_MESSAGE = 'SET_MESSAGE'
export const LOADING_USER = 'LOADING_USER'
export const USER_LOADED = 'USER_LOADED'
export const CREATING_POST = 'CREATING_POST'
export const POST_CREATED = 'POST_CREATED'
export const SET_POSTS = 'SET_POSTS'
export const SET_VISIBLE = 'SET_VISIBLE'
app.js
import React, { Component } from 'react'
import { Alert } from 'react-native'
import { connect } from 'react-redux'
import Routes from "./routes";
import { setMessage } from './store/actions/message'
class App extends Component {
componentDidUpdate = () => {
if(this.props.text && this.props.text.toString().trim())
{
Alert.alert(this.props.title || 'Mensagem',this.props.text.toString())
this.props.clearMessage()
}
}
render() {
return (
<Routes />
)
}
}
const mapStateToProps = ({ message}) => {
return {
title: message.title,
text: message.text,
}
}
const mapDispatchToProps = dispatch => {
return {
clearMessage: () => dispatch(setMessage({ title: '', text: '' }))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Right after the data is sent to the API, I need to return a load indicator to the user, until that data is stored.
Related
I'm trying to migrate our codebase to use react-navigation: 3.11 from v1 and having issues. I created a small gist here to run through the issue and provided codebase.
I am using react-navigation-redux-helpers with the new react-navigation to use the createReduxContainer function in other to maintain my previous redux setup.
I'm getting this error - Cannot read property 'routes' of undefined
https://gist.github.com/bwoodlt/773c62d4ba3dbe0cea150a9d956bec3f
// #flow
// root navigator
import React from 'react';
import { Platform } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';
import TabNavigation from './TabNavigation';
import FCLaunch from '../../Components/FCLaunch';
import { FCLAUNCH } from '../constants';
import { HeaderText } from '../Components';
import * as actionsOnboarding from '../../Screens/Onboarding/actions';
import StagingArea from '../StagingArea';
const RouteConfigs = {
StagingArea: {
screen: StagingArea,
defaultNavigationOptions: {
header: null
}
},
[FCLAUNCH]: {
screen: FCLaunch,
navigationOption: () => ({
header: null
})
}
};
const StackNavigatorConfig = {
initialRouteName: 'StagingArea',
mode: Platform.OS === 'ios' ? 'modal' : 'card'
};
export default createAppContainer(
createStackNavigator(RouteConfigs, StackNavigatorConfig)
);
// #flow
// AppWithNavigationState
import * as React from 'react';
import codePush from 'react-native-code-push';
import OneSignal from 'react-native-onesignal';
import { connect } from 'react-redux';
import DeviceInfo from 'react-native-device-info';
import PropTypes from 'prop-types';
import {
createReactNavigationReduxMiddleware,
createReduxContainer
} from 'react-navigation-redux-helpers';
import { createAppContainer } from 'react-navigation';
import { AppNavigation } from './Navigation';
import { Storage } from '../Utils';
import { NAME } from './constants';
import type { Dispatch } from '../Model';
type IAppWithNavigationProps = {
dispatch: Dispatch
};
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME
};
const middleware = createReactNavigationReduxMiddleware(state => state[NAME]);
class AppWithNavigationStateObject extends React.PureComponent<
{},
IAppWithNavigationProps
> {
async componentDidMount() {
codePush.notifyAppReady();
codePush.sync({
updateDialog: true,
installMode: codePush.InstallMode.IMMEDIATE
});
const deviceName = await Storage.get('deviceName');
if (!deviceName) {
await Storage.set('deviceName', DeviceInfo.getDeviceName());
}
}
componentWillMount() {
OneSignal.setLocationShared(true);
OneSignal.inFocusDisplaying(2);
}
render(): React.Node {
const { dispatch, [NAME]: state } = this.props;
console.log(this.props);
return <AppNavigation navigation={{ dispatch, state }} />;
}
}
AppWithNavigationStateObject.propTypes = {
dispatch: PropTypes.func.isRequired,
[NAME]: PropTypes.object.isRequired
};
const AppWithNavigationStateInfo = createReduxContainer(AppNavigation);
const AppWithNavigationState = connect(state => ({
[NAME]: state[NAME]
}))(codePush(codePushOptions)(AppWithNavigationStateInfo));
export { AppWithNavigationState, middleware };
// #flow
// navReducer
import { handleActions } from 'redux-actions';
import { NavigationActions, StackActions } from 'react-navigation';
import { REHYDRATE } from 'redux-persist/constants';
import { AppNavigation } from './Navigation';
import {
CHAT_MAIN,
CHAT_MESSAGE_AREA,
NEW_MESSAGE,
FCLAUNCH,
} from './constants';
const { getStateForAction } = AppNavigation.router;
const initialState = getStateForAction(NavigationActions.init);
const getCurrentRouteName = navState => {
if (Object.prototype.hasOwnProperty.call(navState, 'index')) {
console.log(navState);
return getCurrentRouteName(navState.routes[navState.index]);
}
return navState.routeName;
};
const generateNavigationAction = (state, routeName, params = {}) => {
console.log(routeName);
// Don't navigate to the same screen if is already there.
if (getCurrentRouteName(state) === routeName) {
return state;
}
const nextState = getStateForAction(
NavigationActions.navigate({
routeName,
params
}),
state
);
return nextState || state;
};
export default handleActions(
{
// Custom actions types
[CHAT_MAIN]: state => generateNavigationAction(state, CHAT_MAIN),
[FCLAUNCH]: state => generateNavigationAction(state, FCLAUNCH),
[CHAT_MESSAGE_AREA]: state =>
generateNavigationAction(state, CHAT_MESSAGE_AREA),
[NEW_MESSAGE]: state => generateNavigationAction(state, NEW_MESSAGE),
// overwritten action types from react-navigation
[NavigationActions.NAVIGATE]: (state, { routeName }) =>
generateNavigationAction(state, routeName),
[NavigationActions.BACK]: state => {
const nextState = getStateForAction(NavigationActions.back(), state);
return nextState || state;
},
[NavigationActions.INIT]: state => {
console.error(NavigationActions.INIT);
return state;
},
[NavigationActions.RESET]: (state, { routeName }) => {
console.error(NavigationActions.RESET);
StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName })]
});
return state;
},
[NavigationActions.SET_PARAMS]: (state, { key, params }) => {
const nextState = getStateForAction(
NavigationActions.setParams({
params,
key
}),
state
);
return nextState || state;
},
[NavigationActions.URI]: state => {
console.error(NavigationActions.URI);
return state;
},
// Default initialRouteName
[REHYDRATE]: (state, { payload: { auth } }) => {
const isLogged =
auth &&
auth.token &&
auth.token.length !== 0 &&
auth.refreshToken &&
auth.status &&
auth.refreshToken.length !== 0;
const nextState = isLogged
? state
: generateNavigationAction(state, FCLAUNCH);
return nextState;
}
},
initialState
);
```[enter image description here][2]
[1]: https://i.stack.imgur.com/j5dm8.png
So I fixed my issue:
The createReduxContainer helper from react-navigation-redux-helpers expects three key params: { dispatch, state, ...props }, I wasn't passing the dispatch and state directly but as part of the navigation object passed. So I fixed this by passing missing parameters.
CounterContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import PropType from 'prop-types';
import Counter from '../components/Counter';
import * as counterActions from '../store/modules/counter';
import * as postActions from '../store/modules/post';
class CounterContainer extends Component {
handleIncrement = () => {
const { CounterActions } = this.props;
CounterActions.increment();
}
handleDecrement = () => {
const { CounterActions } = this.props;
CounterActions.decrement();
}
addDummy = () => {
const { PostActions } = this.props;
console.log(PostActions);
PostActions.addDummy({
content: 'dummy',
userUID: 123,
});
}
render() {
const { handleIncrement, handleDecrement, addDummy } = this;
const { number } = this.props;
return (
<Counter
onIncrement={handleIncrement}
onDecrement={handleDecrement}
addDummy={addDummy}
number={number}
/>
);
}
}
CounterContainer.propTypes = {
number: PropType.number.isRequired,
CounterActions: PropType.shape({
increment: PropType.func,
decrement: PropType.func,
}).isRequired,
};
export default connect(
state => ({
number: state.counter.number,
}),
dispatch => ({
CounterActions: bindActionCreators(counterActions, dispatch),
PostActions: bindActionCreators(postActions, dispatch),
}),
)(CounterContainer);
PostContainer
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// import { ListItem } from 'react-native-elements';
import { Text } from 'react-native';
import PropType from 'prop-types';
import Post from '../components/Post';
import * as postActions from '../store/modules/post';
class PostContainer extends Component {
refreshing = () => {}
onRefresh = () => {}
renderItem = ({ item }) => (<Text>{item.content}</Text>)
render() {
const { renderItem } = this;
const { postList } = this.props;
return (
<Post
postList={postList}
renderItem={renderItem}
/>
);
}
}
export default connect(
state => ({
postList: state.post.postList,
}),
dispatch => ({
CounterActions: bindActionCreators(postActions, dispatch),
}),
)(PostContainer);
modules/post
import { createAction, handleActions } from 'redux-actions';
const initialState = {
postList: [{
content: 'test',
userUID: 123,
},
{
content: '123123',
userUID: 123123,
},
],
};
const ADD_DUMMY = 'ADD_DUMMY';
export const addDummy = createAction(ADD_DUMMY, ({ content, userUID }) => ({ content, userUID }));
const reducer = handleActions({
[ADD_DUMMY]: (state, action) => ({
...state,
postList: [action.data, ...state.postList],
}),
}, initialState);
export default reducer;
Clicking the button adds a dummy to the postList.
However, when I click the button, I get
TypeError: Can not read property 'content' of undefined error.
I thought I made it the same as the count-up down tutorial.
But Count Up Down works.
Adding a dummy I made does not work.
Where did it go wrong?
Until I click the Add Dummy Data button
The list is worked.
i change action.data -> actions.payload
const reducer = handleActions({
[ADD_DUMMY]: (state, action) => ({
...state,
postList: [action.payload, ...state.postList],
}),
}, initialState);
It is simply a mistake.
When a component is rendered, I'm trying to fetch a list of games and print them on the page in an unordered list. The API call works correctly, and Redux Dev Tools shows the store gets updated, but the component isn't updating to reflect the changes.
Component
import React from 'react';
import { connect } from 'react-redux'
import {fetchAllGames} from "../actions";
class Games extends React.Component {
componentDidMount() {
this.props.dispatch(fetchAllGames());
}
render() {
const { games } = this.props;
return(
<ul>
{ games.map(game => <li key={game.id} >{game.name}</li>) }
</ul>
)
}
}
const mapStateToProps = state => (
{
games: state.games
}
)
const GamesList = connect(
mapStateToProps
)(Games)
export default GamesList;
Actions
import axios from 'axios';
export const fetchGames = (games) => {
return {
type: 'FETCH_GAMES',
games
}
};
export const fetchAllGames = () => {
return (dispatch) => {
return axios.get('/api/games').then(res=> {
dispatch(fetchGames(res.data))
})
.catch(error => {
throw(error);
});
};
};
Store
import {combineReducers, createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import GamesList from '../games-list/reducers';
import UsersList from "../users/reducers";
const rootReducer = combineReducers({
'friends' : UsersList,
'games': GamesList
})
const store = createStore(rootReducer, applyMiddleware(thunk));
console.log(store.getState())
export default store
Reducer
const initialState = [
{
id: 0,
name: 'Test Game',
publisher: 'Test Co.'
}
];
const GamesList = (state = initialState, action) => {
switch(action.type){
case 'ADD_GAME':
return [
...state,
{
id: action.id,
name: action.name,
publisher: action.publisher
}
]
case 'DELETE_GAME':
return state.splice(state.indexOf(action.id), 1);
case 'FETCH_GAMES':
return [
...state,
action.games
]
default:
return state
}
}
export default GamesList;
you need to spread your results:
do it like this:
case 'FETCH_GAMES':
return [
...state,
...action.games
]
I am trying to refresh a react component state based on the props.
I have this file which is the main a child component for a screen:
RoomsList.js
import React from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import {connect} from "react-redux";
import {getRooms} from "../../store/actions";
import RoomIcon from "../RoomIcon/RoomIcon";
class RoomList extends React.Component {
componentDidMount() {
this.props.onGetRooms();
}
renderRooms() {
return this.props.rooms.map(room => {
return (
<RoomIcon key={room.id} room={room} />
)
});
}
render() {
return (
<View style={styles.container}>
{ this.props.rooms.length ? this.renderRooms() : <ActivityIndicator /> }
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
width: '100%',
justifyContent: 'space-between',
flexWrap: 'wrap',
}
});
const mapStateToProps = state => {
return {
rooms: state.rooms.rooms
}
};
const mapDispatchToProps = dispatch => {
return {
onGetRooms: () => dispatch(getRooms())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(RoomList);
Rooms Reducer
import { SET_ROOMS } from '../actions/actionTypes';
const initialState = {
rooms: []
};
const roomsReducer = (state = initialState, action) => {
switch (action.type) {
case SET_ROOMS:
return {
...state,
rooms: action.rooms
};
default:
return state;
}
};
export default roomsReducer;
When the state is getting updated within the mapStateToProps function, which I can confirm it is doing as I put a console log inside of there to get the rooms and the object is the updated object.
However, it appears the the render isn't actually getting updated although the state is getting updated. I have tried to do a componentWillReceiveProps and assign the state but the state is never actually updated within here.
Rooms Action
import {SET_ROOMS} from './actionTypes';
import store from "../index";
export const getRooms = () => {
return dispatch => {
fetch("http://localhost/rooms").catch(err => {
console.log(err)
}).then(res => {
res.json();
}).then(parsedRes => {
dispatch(setRooms(parsedRes));
})
}
};
export const addRoom = (roomName, roomDescription) => {
const rooms = store.getState().rooms.rooms;
const room = {
room_name: roomName,
room_description: roomDescription
};
return dispatch => {
fetch("http://localhost/rooms", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(room)
}).catch(err => {
console.log(err)
}).then(res => res.json())
.then(parsedRes => {
rooms.push(parsedRes);
dispatch(setRooms(rooms));
})
}
};
export const setRooms = rooms => {
return {
type: SET_ROOMS,
rooms: rooms
}
};
Initialising Redux Store
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
const composeEnhancers = compose;
const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk)));
export default store;
Initializing Reducers
import {combineReducers} from "redux";
import lightsReducer from "./lights";
import roomsReducer from "./rooms";
import modalsReducer from "./modals";
export default combineReducers({
lights: lightsReducer,
rooms: roomsReducer,
modals: modalsReducer
});
I am aware there are a lot of threads referring to this error message but I cannot find one that explains why I am getting this error.
While I am relatively new to React and Redux, I think I understand the concept of Promises and asynch functions but I have to be missing something here. So I have my index.js Modal container, Modal component and a modal reducer.
index.js: -
import React from 'react'
import ReactDOM from 'react-dom'
import routes from './config/routes'
import {createStore, applyMiddleware, compose, combineReducers} from 'redux'
import {Provider} from 'react-redux'
import * as reducers from '_redux/modules/'
import thunk from 'redux-thunk'
import { checkIfAuthed } from '_helpers/auth'
const store = createStore(
combineReducers(reducers),
compose(applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
))
// CSS
import "_css/main.scss";
ReactDOM.render(
<Provider store={store}>{routes}</Provider>,
document.getElementById('app'))
ModalContainer.js: -
import { Modal } from '_components'
import { bindActionCreators } from 'redux'
import * as modalActionCreators from '_redux/modules/Modal/Modal'
import { connect } from 'react-redux'
const mapStateToProps = ({users, modal}) => {
const duckTextLength = modal.duckText.length
return {
user: users[users.authedId] ? users[users.authedId].info : {},
duckText: modal.duckText,
isOpen: modal.isOpen,
isSubmitDisabled: duckTextLength <= 0 || duckTextLength > 140,
}
}
const mapDispatchToProps = (dispatch) => {
return bindActionCreators(modalActionCreators, dispatch)
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Modal)
Modal.js
import React from 'react'
import PropTypes from 'prop-types';
import { default as ReactModal } from 'react-modal'
const modalStyle = {
content: {
width: 350,
margin: '0px auto',
height: 220,
borderRadius: 5,
background: '#EBEBEB',
padding: 0,
}
}
const Modal = (props) => {
const submitDuck = () => {
console.log('Duck', props.duckText)
console.log('user', props.user)
}
return(
<span className='darkBtn' onClick={props.openModal}>
{'Duck'}
</span>
)
}
Modal.PropTypes = {
duckText: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired,
isSubmitDisabled: PropTypes.bool.isRequired,
openModal: PropTypes.func.isRequired,
closeModal: PropTypes.func.isRequired,
updateDuckText: PropTypes.func.isRequired,
}
export default Modal
modal reducer: -
const OPEN_MODAL = 'OPEN_MODAL'
const CLOSE_MODAL = 'CLOSE_MODAL'
const UPDATE_DUCK_TEXT = 'UPDATE_DUCK_TEXT'
export const openModal = () => {
return
{
type: OPEN_MODAL
}
}
export const closeModal = () => {
return
{
type: CLOSE_MODAL
}
}
export const newDuckText = () => {
return
{
type: UPDATE_DUCK_TEXT,
newDuckText
}
}
const initialState = {
duckText: '',
isOpen: false,
}
export const modal = (state = initialState, action) => {
switch (action.type) {
case OPEN_MODAL :
return {
...state,
isOpen: true,
}
case CLOSE_MODAL :
return {
duckText: '',
isOpen: false,
}
case UPDATE_DUCK_TEXT :
return {
...state,
duckText: action.newDuckText,
}
default :
return state
}
}
The problem arises from clicking on: -
<span className='darkBtn' onClick={props.openModal}>
It successfully invokes the reducer action function but also gives me 'Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.' error. I don't understand this because
1) I am using Thunk
2) As this reducer action does not return a promise it is therefore not an asynch function?
I would really appreciate help in resolving this. I've been trying to solve this for a couple hours now and I feel like my eyes are going to start bleeding soon.
It's a quirk in JavaScript. The value that you are going to return should be on the same line with the return keyword.
instead of:
// (this will return `undefined`)
export const openModal = () => {
return
{
type: OPEN_MODAL
}
}
You should write:
//(this will return the action object)
export const openModal = () => {
return {
type: OPEN_MODAL
};
}