This is my configureStore.js file
import {createStore, applyMiddleware, compose, combineReducers} from 'redux';
import thunk from 'redux-thunk';
import {persistStore, persistReducer} from 'redux-persist';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {stateReducer, themeReducer, authReducer} from './index';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['themeReducer'],
};
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers({stateReducer, themeReducer, authReducer});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export default () => {
let store = createStore(
persistedReducer,
composeEnhancer(applyMiddleware(thunk)),
);
let persistor = persistStore(store);
return {store, persistor};
};
I added redux-persist to this file because I want the theme to persist when it is changed. There hasn't been an error when this setup but when I try to change the theme, it doesn't switch. I accessed the theme's state using
const theme = useSelector(state => state.themeReducer.theme)
This is the themeReducer
import {lightTheme, darkTheme, SWITCH_THEME} from '../../components/index';
const initialState = {
theme: lightTheme,
};
const themeReducer = (state = initialState, action) => {
switch (action.type) {
case SWITCH_THEME:
return {
theme: action.theme,
};
default:
return state;
}
};
export default themeReducer;
And this is the switchTheme action
import {SWITCH_THEME} from './../../redux';
export const switchTheme = theme => {
try {
return dispatch => {
dispatch({
type: SWITCH_THEME,
theme: theme,
});
};
} catch (error) {
console.log(error);
}
};
The theme switch is in the DrawerContent file as below. the theme.state has a boolean value.
<Drawer.Section>
<Preferences>Preferences</Preferences>
<TouchableRipple onPress={() => {
theme.mode === 'light'
? dispatch(switchTheme(darkTheme))
: dispatch(switchTheme(lightTheme));
console.log('Theme state: ', theme.state);
console.log('Theme mode: ', theme.mode);
}}>
<View style={styles.preference}>
<Text style={{color: theme.text}}>Dark Theme</Text>
<View pointerEvents="none">
<Switch value={theme.state} />
</View>
</View>
</TouchableRipple>
</Drawer.Section>
I found my solution. I was importing SWITCH_THEME from the wrong place in the themeReducer.
Related
I'm trying to use redux with useEffect to update/get the redux state but useEffect is totally not running at all but I have no idea what is going on. I can't even get the "hi"
import { useSelector, useDispatch } from 'react-redux';
import { setDisplayLogsheet, getLogsheets } from '../redux/actions';
...
const { displayLogsheet, logsheets } = useSelector(state => state.logsheetReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log("hi")
dispatch(getLogsheets());
dispatch(setDisplayLogsheet(logsheets));
}, []);
Any help please? Thanks
UPDATE: here's more code to understand
App.js:
I have added the store inside the provider
const Stack = createStackNavigator();
export default function App() {
return(
<Provider store={Store}>
<NavigationContainer>
...
<Provider />
}
home.js:
tried to useSelector to get the logsheets and displayLogsheets and useEffect to dispatch, but the the useEffect is totally not running
export default function Home({navigation}) {
const { displayLogsheet, logsheets } = useSelector(state => state.logsheetReducer);
const dispatch = useDispatch();
useEffect(() => {
console.log('getting logsheets...')
dispatch(getLogsheets())
}, [dispatch])
useEffect(() => {
console.log('setting displayLogsheet...')
if(logsheets){
dispatch(setDisplayLogsheet(logsheets))
}
}, [dispatch, logsheets])
console.log(logsheets)
console.log(displayLogsheet)
return (
<>
<SafeAreaView>
<ScrollView>
<HomeTopStack logsheet={displayLogsheets} iterateDocket={iterateDocket} />
<ScanBarcodeButton navigation={navigation} />
{displayLogsheets.data.DO.map(logsheet => (
<TouchableOpacity onPress={() => navigation.navigate('Details', logsheet)}>
<DOCards logsheet={displayLogsheets} />
</TouchableOpacity>
))}
</ScrollView>
</SafeAreaView>
</>
)
}
store.js:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logsheetReducer from './reducers';
const rootReducer = combineReducers({ logsheetReducer });
export const Store = createStore(rootReducer, applyMiddleware(thunk));
reducer.js:
this is the reducer to set display logsheet and also to get the dummy logsheet data
import { SET_DISPLAY_LOGSHEET, GET_LOGSHEETS } from "./actions";
const initialState = {
logsheets: {},
displayLogsheet: {},
}
function logsheetReducer(state = initialState, action) {
switch (action.type) {
case SET_DISPLAY_LOGSHEET:
console.log("inside logsheetReducer, SET_DISPLAY_LOGSHEET")
return { ...state, displayLogsheet: action.payload };
case GET_LOGSHEETS:
console.log("inside logsheetReducer, GET_LOGSHEET")
return { ...state, logsheets: action.payload };
default:
return state;
}
}
export default logsheetReducer;
actions.js:
import CreateFakeLogsheets from "../data/logsheet";
export const SET_DISPLAY_LOGSHEET = 'SET_DISPLAY_LOGSHEET';
export const GET_LOGSHEETS = 'GET_LOGSHEETS';
const logsheets = CreateFakeLogsheets(2,3)
export const getLogsheets = () => {
console.log("inside getLogsheets")
try {
return dispatch => {
dispatch({
type: GET_LOGSHEETS,
payload: logsheets
})
}
} catch (error) {
console.log(error)
}
}
export const setDisplayLogsheet = displayLogsheet => {
console.log("inside setDisplayLogsheets")
return dispatch => {
dispatch({
type: SET_DISPLAY_LOGSHEET,
payload: displayLogsheet
});
}
};
here's most of the code with redux and also the useEffect. Any help please
Without knowing how the rest of the code is structured, I would split the effect in two, like this:
useEffect(() => {
console.log('getting logsheets...')
dispatch(getLogsheets())
}, [dispatch])
useEffect(() => {
console.log('setting displayLogsheet...')
if(logsheets){ // only dispatch this if logsheets have been fetched
dispatch(setDisplayLogsheets(logsheets))
}
}, [dispatch, logsheets])
I am new to react-native and I am building an app which has an authentication module. The login works with the jwt token and sets the state of the user. I want to save the state of the user such that the next time the user launches the application, it retrieves the last state of the application and skips the login module. Note that I am not talking about the app going to background. I am storing the jwt in the async storage once the login is true in the api function.
Can anyone advise me to correct pointer to look for the same.
Below is my login auth code -
Reducer -
import { combineReducers } from 'redux';
const initialAuthState = { isLoggedIn: false };
const Login = 'Login';
const Logout = 'Logout';
export const login = data => ({
type: Login,
data
});
export const logout = () => ({
type: Logout,
});
function auth(state = initialAuthState, action) {
switch (action.type) {
case Login:
console.log("reducer called for Login");
console.log(action.data.user)
return { ...state, isLoggedIn: true, user: action.data.user};
case Logout:
console.log("reducer called for logout");
return { ...state, isLoggedIn: false, user: {} };
default:
return state;
}
}
const AppReducer = combineReducers({
auth,
});
export default AppReducer;
login.js
import React from 'react';
import { StyleSheet, Text, TextInput, View } from 'react-native';
import Button from 'react-native-button';
import PropTypes from 'prop-types';
import AppStyles from '../AppStyles';
import Api from '../Api';
import { connect } from 'react-redux';
import { login } from '../reducers';
class LoginScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
// loading: true,
email: 'username',
password: 'password'
};
}
onPressLogin = () => {
Api.login(this.state.email, this.state.password, (success, data) => {
if (success) {
this.props.login({ user: data.username});
} else {
alert(data);
}
});
};
render() {
return (
<View style={styles.container}>
<Text style={[styles.title, styles.leftTitle]}>Sign In</Text>
<View style={styles.InputContainer}>
<TextInput
style={styles.body}
placeholder="E-mail or phone number"
onChangeText={text => this.setState({ email: text })}
value={this.state.email}
underlineColorAndroid="transparent"
/>
</View>
<View style={styles.InputContainer}>
<TextInput
style={styles.body}
secureTextEntry
placeholder="Password"
onChangeText={text => this.setState({ password: text })}
value={this.state.password}
underlineColorAndroid="transparent"
/>
</View>
<Button
containerStyle={styles.loginContainer}
style={styles.loginText}
onPress={() => this.onPressLogin()}
>
Log in
</Button>
</View>
);
}
}
Thanks
You could you redux-persist. I see you're already managing your state with Redux, it is pretty simple to setup and will persist your reducers through sessions. You could also hold on a Splash Screen while it is loading, so the user interaction is seamless. You'd then check for auth info in the reducer before sending to the Login Screen or Main Screen.
You could also use some kind of local database, such as Realmjs, and then store whatever info you need in there.
An example how to use redux-persist:
store.js
// Imports: Dependencies
import AsyncStorage from '#react-native-community/async-storage';
import { createStore, applyMiddleware, compose } from 'redux';
import { createLogger } from 'redux-logger';
import { persistStore, persistReducer } from 'redux-persist';
import rootReducer from '../reducers/index';
import thunk from 'redux-thunk';
// Middleware: Redux Persist Config
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: [
'authReducer',
],
};
// Middleware: Redux Persist Persisted Reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
let composeEnhancers = compose;
if(__DEV__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
// Redux: Store
const store = createStore(
persistedReducer,
composeEnhancers(
applyMiddleware(
thunk,
createLogger()
)
)
);
// Middleware: Redux Persist Persister
let persistor = persistStore(store);
export {
store,
persistor,
};
I have Problem in dispatching an action and is suppose to update my state in redux.
This is my Homepage component. I am able to get the current state of the calculator balance.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { Text, ScrollView, Button } from "react-native";
import * as reducerActions from "../../store/actions/calculator";
function Homepage() {
const balance = useSelector((state) => state.calculator.balance);
const dispatch = useDispatch();
console.log(balance);
return (
<ScrollView>
<Text>Balance:{balance}</Text>
<Button
title="Add to Cart"
onPress={() => {
dispatch(reducerActions.DepositMoney(10));
}}
/>
</ScrollView>
);
}
export default Homepage;
This is my action component: The issue is that it doesn't call my reducer. It logs the value of 10 when I press on the button.
export const DEPOSIT = "DEPOSIT";
export const DepositMoney = (amount) => {
return { type: DEPOSIT, payload: amount };
};
This is my reducer component:
import { DEPOSIT } from "../actions/calculator";
const initialState = {
balance: 0,
};
export default (state = initialState, action) => {
switch (action.Type) {
case DEPOSIT:
console.log("reducer");
console.log(action.payload);
return { balance: state.balance + action.payload };
case "WITHDRAW":
return { balance: state.balance - action.payload };
}
return state;
};
And this is how i set up the redux in my app.js
import React, { useState } from "react";
import { createStore, combineReducers, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { AppLoading } from "expo";
import * as Font from "expo-font";
import ReduxThunk from "redux-thunk";
import productsReducer from "./store/reducers/products";
import ShopNavigator from "./navigation/ShopNavigator";
import calculatorReducer from "./store/reducers/calculator";
import { composeWithDevTools } from "redux-devtools-extension";
const rootReducer = combineReducers({
products: productsReducer,
calculator: calculatorReducer,
});
//composewithdevtools should be taken out for production
//const store = createStore(rootReducer, composeWithDevTools());
const store = createStore(
rootReducer,
composeWithDevTools(),
applyMiddleware(ReduxThunk)
);
const fetchFonts = () => {
return Font.loadAsync({
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
"open-sans-bold": require("./assets/fonts/OpenSans-Bold.ttf"),
});
};
export default function App() {
const [fontLoaded, setFontLoaded] = useState(false);
if (!fontLoaded) {
return (
<AppLoading
startAsync={fetchFonts}
onFinish={() => {
setFontLoaded(true);
}}
/>
);
}
return (
<Provider store={store}>
<ShopNavigator />
</Provider>
);
}
#Christian is right but one thing is missing.
Must copy state before making change in it.
You can also do it like.
export default (state = initialState, {type,payload}) => {
switch (type) {
case DEPOSIT:
console.log("reducer");
console.log(payload);
return { ...state, balance: state.balance + payload };
case "WITHDRAW":
return {...state, balance: state.balance - payload };
}
return state;
};
It was something silly. The following line was wrong:
switch (action.Type) {
It should be:
switch (action.type) {
I am new to react native and context Api so any help would be really appreciated. When I start the app I see undefined is not an object _useContext.appUser error. Below is my code.
App.js
import { AsyncStorage } from 'react-native';
import { NavigationContainer } from '#react-navigation/native'
import AuthStackNavigator from './src/navigators/AuthStackNavigator'
import { LightTheme } from './src/themes/light'
import UserTabsNavigator from './src/navigators/UserTabsNavigator'
import AuthProvider from './src/auth/AuthProvider'
import { AuthContext } from './src/auth/AuthProvider';
export default function App() {
const [loggedIn, setLoggedIn] = useState(false);
const { appUser } = useContext(AuthContext);
console.log('context object' + appUser);
useEffect(() => {
AsyncStorage.getItem('user').then(userString => {
if (userString) {
setLoggedIn(true)
}
}).catch(error => {
console.log(error);
})
})
return (
<AuthProvider>
<NavigationContainer theme={LightTheme}>
{loggedIn ? <UserTabsNavigator /> :
<AuthStackNavigator />}
</NavigationContainer>
</AuthProvider>
);
};
AuthProvider.js
import React, { useState, createContext } from 'react';
import { AsyncStorage } from 'react-native';
export const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const loginUser = () => {
const fakeUser = { username: 'Test' }
AsyncStorage.setItem('user', JSON.stringify(fakeUser));
setUser(fakeUser);
}
const logoutUser = () => {
AsyncStorage.removeItem('user');
setUser(null);
}
return (
<AuthContext.Provider value={{
appUser: user,
login: loginUser,
logout: logoutUser
}}>
{children}
</AuthContext.Provider>
)
}
export default AuthProvider;
I would really appreciate any help here. I have been struggling with this issue for a while now. I am kinda stuck here.
I have combined my react redux.
Here is my App.js
import React from 'react';
import ReduxThunk from 'redux-thunk';
import { Provider } from 'react-redux';
import { compose, createStore, applyMiddleware } from 'redux';
import reducers from './src/reducers';
import AppContainer from './src/navigator'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const App: () => React$Node = () => {
const store = createStore(reducers, {}, composeEnhancers(applyMiddleware(ReduxThunk)));
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
};
export default App;
src/reducers/index.js
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
LoginRedux: LoginReducer
});
If I use my action login(), I can see login action start, but I can't see dispatch start
import React from 'react';
import {
Text,
View,
TouchableOpacity,
} from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions';
const LoginScreen = ({ navigation }) => {
// console.log('see my test value', testValue)
return (
<View>
<TouchableOpacity
onPress={() => {
login();
}
}>
<View>
<Text>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
const mapStateToProps = (state) => {
const { testValue } = state.LoginRedux;
console.log('mapStateToProps testValue =>', testValue);
return { testValue };
};
export default connect(mapStateToProps, { login })(LoginScreen);
If I console.log(dispatch), it will show dispatch is not defined.
import { LOGIN } from './types';
export const login = () => {
console.log('login action start')
return (dispatch) => {
console.log('dispatch start');
// console.log(dispatch);
dispatch({ type: LOGIN, testValue: 'I am test' });
};
};
src/reducers/LoginReducer.js
import { LOGIN } from '../actions/types';
const INITIAL_STATE = {
testValue: ''
};
export default (state = INITIAL_STATE, action) => {
console.log('reducer =>', action); // I can't see the console.log
switch (action.type) {
case LOGIN:
return {
...state,
testValue: action.testValue
};
default:
return state;
}
};
I have no idea why my action dispatch is not working. Do I set something wrong ?
Any help would be appreciated.
According to Zaki Obeid help, I update like this:
the action code:
export const login = () => {
console.log('login !');
return { type: LOGIN };
};
the function component code:
import { login } from '../../actions';
export const SettingScreen = ({ navigation, login }) => {
// return view code
}
const mapDispatchToProps = dispatch => ({
// you will use this to pass it to the props of your component
login: () => dispatch(login),
});
connect(null, mapDispatchToProps)(SettingScreen);
In LoginScreen component
you will need to add mapDispatchToProps
const mapDispatchToProps = dispatch => ({
// you will use this to pass it to the props of your component
login: () => dispatch(login()),
});
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
Then
you will need to destructure from the props as:
const LoginScreen = ({ navigation, login }) => {
// your code
}
In actions.js
the way you use dispatch here requires a library redux-thunk and it's used for async calls.
and the normal action should do the job for you:
export const login = () => ({
type: LOGIN,
testValue: 'I am test'
})
I hope this is useful and will solve your problem,
Have a good day.
In a react-redux app, you obtain the dispatch function either from getting a hold of the store object directly (store.dispatch), or via the react-redux connect function, which will provide dispatch as an argument to a function you write and then later hook up to a component
import { connect } from 'react-redux';
const mapStateToProps = ...
const mapDispatchToProps = (dispatch) => {
return {
someHandle: () => dispatch(myActionCreator())
}
}
export const connect(mapStateToProps, mapDispatchToProps)(MyComponent)
You can't just call dispatch out of thin air -- it's not a global function.
It seems you are using the login function directly. you will have to use the props. Just change the name for confusing and use through props.
import { combineReducers } from 'redux';
import LoginReducer from './LoginReducer';
export default combineReducers({
LoginRedux: LoginReducer
});
If I use my action login(), I can see login action start, but I can't see dispatch start
import React from 'react';
import {
Text,
View,
TouchableOpacity,
} from 'react-native';
import { connect } from 'react-redux';
import { login } from '../actions';
const LoginScreen = ({ navigation, userLogin }) => {
// console.log('see my test value', testValue)
return (
<View>
<TouchableOpacity
onPress={() => {
userLogin();
}
}>
<View>
<Text>LOGIN</Text>
</View>
</TouchableOpacity>
</View>
</View>
);
}
const mapStateToProps = (state) => {
const { testValue } = state.LoginRedux;
console.log('mapStateToProps testValue =>', testValue);
return { testValue };
};
export default connect(mapStateToProps, { userLogin:login })(LoginScreen);