I am trying to create a sample app using React Native & Redux. What I am not able to understand is that why my state object is getting wrapped into another object.
I have initial state as {email: 'test'}. I have to access email as this.props.email.email
Why do I have to do this.props.email.email instead of this.props.email
Any help will be appreciated.
Welcome.js
class Welcome extends Component {
render() {
return (
<View style={ styles.container }>
<View style = { styles.inputContainer }>
<Text>{JSON.stringify(this.props.email)}</Text>
<Button title = 'Update Email'
style = { styles.placeButton }
onPress={() => this.props.onChangeEmail('hello')}
/>
</View>
</View>
);
}
}
const mapStateToProps = state => {
return {
email: state.email
}
}
const mapDispatchToProps = dispatch => {
return {
onChangeEmail: (email) => { dispatch({type: 'CHANGE_EMAIL_INPUT', email: email}) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Welcome)
EmailReducer.js
const initialState = {
email: 'test',
};
const emailReducer = (state = initialState, action) => {
switch(action.type) {
case 'CHANGE_EMAIL_INPUT':
return Object.assign({}, state,
{ email: action.email }
);
default:
return state;
}
}
export default emailReducer;
Store.js
import { createStore, combineReducers } from 'redux';
import emailReducer from '../reducers/EmailReducer';
const rootReducer = combineReducers({
email: emailReducer
});
const configureStore = () => {
return createStore(rootReducer);
}
export default configureStore;
When you call combineReducers you are creating a store with the following shape
{
email: {
email: 'test'
}
}
That is, the keys in the object passed to combineReducers are the root keys of the state object. The initial state for the email reducer is inserted in the key "email" of the state object.
This is the reason why you need to write this.props.email.email: the former is the key in the root state object (that deduced from combineReducers), the latter is the prop of the state part managed by emailReducer
Related
I have just for several reasons migrated from the old style of redux (pre 2019) which used case, switch etc. The redux store gets updated as it should which i can see in the TextInput for example but when trying to use the selected value from redux store elsewhere inside the screen function it seems to initial state and not the updated one. Shortened code below and any help would be greatly appriciated.
redux/slices/auth.js
import { createSlice } from "#reduxjs/toolkit"
const initialState = {
email: "",
}
const authSlice = createSlice({
name: 'userAuth',
initialState,
reducers: {
setEmail: (state, action) => {
state.email = action.payload.email;
}
}
});
export const { setEmail } = authSlice.actions;
export const selectEmail = (state) => state.userAuth.email;
export default authSlice.reducer;
redux/store.js
import { configureStore } from '#reduxjs/toolkit'
import authSlice from './slices/auth'
export const store = configureStore({
reducer: {
userAuth: authSlice,
},
})
screens/LoginScreen.js
import {useSelector, useDispatch} from 'react-redux';
import { selectEmail, setEmail } from '../../redux/slices/auth';
function LoginScreen({navigation}) {
const _email = useSelector(selectEmail);
const onEmailButtonPress = async () => {
console.log("Begin sign in: Email");
// GETS INITIAL STATE AND NOT UPDATED ONE
if (_email == null || _email == 0 || _email == ""){
console.log(_email);
return;
}
}
return (
<View>
<Text>Login with e-mail</Text>
<TextInput
placeholder="Enter e-mail address"
placeholderTextColor={'#000'}
keyboardType="email-address"
onChangeText={(value) => dispatch(setEmail(value))}
maxLength={128}
value={_email} // SHOWS UPDATED STATE
/>
<TouchableOpacity
onPress={() => onEmailButtonPress()}
>
<Text>Continue</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
});
export default LoginScreen;
App.js
class App extends React.Component {
render() {
LogBox.ignoreAllLogs(true);
return (
<Provider store={store}>
{/*<PersistGate persistor={persistor}>*/}
<LoadingScreen />
{/*</PersistGate>*/}
</Provider>
);
}
}
In the reducer you are reading action.payload.email but in the component you are dispatching setEmail(value).
That's not consistent, or you read in the reducer action.payload or you dispatch in the component setEmail({email: value})
I am trying to map an action to props however however I'm getting an error:
TypeError: _this2.props.updateUsername is not a function
How does one successfully map redux actions to props and call the function successfully? I havnt seen this error pop up in any other stackoverflow question/answers is it a simple mistake? Could it be a wrong setup of redux in .index or .app?
I have tried:
- importing without using default export
- having different formats of mapDispatchToProps (eg without using bindactioncreators)
- fixing typos
Component:
import { updateUsername } from "../../actions/user-actions";
import React, { Component } from "react";
import { InputText } from "primereact/inputtext";
import { Button } from "primereact/button";
import { Password } from "primereact/password";
import "./UserLogin.css";
import { connect } from "react-redux";
import { bindActionCreators } from 'redux'
export class UserLoginPage extends Component {
constructor(props) {
super(props);
this.state = { //used to be using states so ill leave these here for now
username: "",
password: "",
renderTryAgain: false
};
this.checkLoginDetails.bind(this.checkLoginDetails);
}
async checkLoginDetails() {
...
}
render() {
const usernameBox = (
<InputText
...
value={this.props.username}
onChange={e => this.props.updateUsername(e.target.value)}
/>
);
const passwordBox = (
<Password
...
/>
);
const loginButton = (
<Button
...
/>
);
return (
<header className="User-login">
<p>Dashboard User Login</p>
<div className="p-grid">
<div className="p-col">{usernameBox}</div>
<div className="p-col">{passwordBox}</div>
<div className="p-col">{loginButton}</div>
</div>
</header>
);
}
}
const mapStateToProps = state => ({
username: state.username
});
const mapDispatchToProps = dispatch => bindActionCreators(
{
updateUsername,
},
dispatch,
)
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserLoginPage);
Reducers:
import { UPDATE_USERNAME} from '../actions/user-actions'
export function passReducer(state = "", {type, payload}) {
switch (type) {
case true:
return payload
default:
return state
}
}
export function usernameReducer(state = '', {type, payload}) {
switch (type) {
case UPDATE_USERNAME:
return payload.username
default:
return state
}
}
export default { passReducer, usernameReducer };
Action:
export const UPDATE_USERNAME = 'username:updateUsername'
export function updateUsername(newUsername){
return {
type: UPDATE_USERNAME,
payload: {
username: newUsername
}
}
}
export default {UPDATE_USERNAME, updateUsername}
Many Thanks
Can you check once after updating your constructor as below?
constructor(props) {
super(props);
//...
}
Don't use mapDispatchToProps. Instead just wrap all the actions you want to map inside an object and pass them as the second argument to the connect helper method.
Like this connect(mapStateToProps, { updateUsername })(UserLoginPage)
Hope this helps!
I'm trying to manage a single state without any a sync actions but facing this error.
this is my store.js:
import { createStore } from "redux";
import reducer from "./../reducers";
const initialState = { todolist: [] };
export const store = createStore(reducer, initialState);
this is my action.js:
export const setTodolist = listitem => {
({ type: "SET_TODOLIST", todolist: listitem })
};
this is my reducer.js:
export default (state, action) => {
console.log(state , "reducer")
switch (action.type) {
case "SET_TODOLIST":
return {
...state,
todolist: [...state, action.todolist]
};
default:
return state;
}
};
and finally this is my component:
import { store } from "./../redux/store";
import { setTodolist } from '../redux/actions';
export default class Mylist extends React.Component {
render() {
return (
<input placeholder="username"
name="inputval" />
<button className="mt-4" color="success" onClick={dispatchBtnAction} >Add Todo</button>
)
}
}
function dispatchBtnAction(e) {
const val = document.getElementsByName("inputval").value;
store.dispatch(setTodolist({val}))
}
do you have any idea what is the problem ?
The syntax of your action is incorrect. With your current syntax the object specified is just a block of code which isn't returned. You should either write it like
export const setTodolist = listitem => (
{ type: "SET_TODOLIST", todolist: listitem })
);
or
export const setTodolist = listitem => {
return { type: "SET_TODOLIST", todolist: listitem }
};
I am learning react native and i am building an application. For some concepts, I am not able to understand where the magic happens. I am using redux store for the managing the data.
I have a stateless login component.
export class Login extends Component {
onChangeText = (key, value) => {
this.props.user[key] = value
}
render() {
const { user, fetchUserDetails } = this.props
return (
<View style={styles.container}>
<Text style={styles.heading}>Login</Text>
<TextInput
placeholder='Email'
onChangeText={val => this.onChangeText('email', val)}
value={user.email}
/>
<TextInput
placeholder='Password'
onChangeText={val => this.onChangeText('password', val)}
value={user.password}
/>
<TouchableOpacity onPress={this.fetchUserDetails(user)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Login</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
This is my Login Container
class LoginContainer extends Component {
render () {
return (
<Login/>
)
}
}
const mapStateToProps = (state) => ({
user: state.loginReducer.user,
})
const mapDispatchToProps = {
...fetchUserDetails,
}
export default connect(mapStateToProps, mapDispatchToProps)(Login)
my reducer looks like this:
const initialState = {
user: {
email: '',
password: '',
}
}
const loginReducer = (state = initialState, action) => {
switch(action.type) {
case GET_USER:
return Object.assign({}, state, {
user: action.user
})
default:
return state
}
return state
}
export default loginReducer
My actions look something like this:
export const GET_USER = 'GET_USER'
export function fetchUserDetails (user) {
console.log("executing fetch user action")
if (user === '')
{
alert('please complete form')
}
return {
type: GET_USER,
user
}
}
My root reducer:
import { combineReducers } from 'redux';
import loginReducer from './loginReducer'
const rootReducer = combineReducers({
loginReducer
})
export default rootReducer
My configure Store:
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import rootReducer from './reducers'
const persistConfig = {
key: 'mykey',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer)
const persistedStore = persistStore(store)
export default store
I need to have a stateless component which updates directly the state of the user attributes in the redux store. I am not able to follow how the state or actions will be passed to the Login component. Any Explanation will be appreciated.
Managing redux store in react-native is basically same as you do in react.
From what I understand you are trying to store user details in redux store on every onChangeText event and reflect the updated state in Login component.
Firstly you should use a separate action reducer pair for setting user details in redux. Also You most probably want to call some API on form submission and store the response in redux, For that you might need another pair of action and reducer. I'll leave that to you
Here's how u can manage user-details in redux...
Your stateless login component.
export class Login extends Component {
onChangeText = (value, key) => {
this.props.setUserDetails({
...this.props.user,
[key]: value
})
}
render() {
const { user, onSubmitForm } = this.props
console.log('user===>', this.props);
return (
<View style={styles.container}>
<Text style={styles.heading}>Login</Text>
<TextInput
placeholder='Email'
onChangeText={val => this.onChangeText(val, 'email')}
placeholderTextColor={'rgba(0,40,70,0.5)'}
value={user.email}
/>
<TextInput
placeholder='Password'
onChangeText={val => this.onChangeText(val, 'password')}
placeholderTextColor={'rgba(0,40,70,0.5)'}
value={user.password}
/>
<TouchableOpacity onPress={() => onSubmitForm(user)}>
<View style={styles.button}>
<Text style={styles.buttonText}>Login</Text>
</View>
</TouchableOpacity>
</View>
)
}
}
...
Your Login Container.
class LoginContainer extends Component {
onSubmitForm = () => {
// Checking Validations
const { name, email } = this.props;
if (!name || !email) {
alert('Please fill the form')
return;
}
// call some API for verification and handle the response here
}
render () {
return (
<Login
user={this.props.user}
setUserDetails={this.props.setUserDetails}
onSubmitForm={this.onSubmitForm}
/>
)
}
}
const mapStateToProps = (state) => ({
user: state.userReducer.user,
})
const mapDispatchToProps = dispatch => ({
setUserDetails: payload => dispatch(setUserDetails(payload)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
...
Your Reducer for setting user details
const initialState = {
user: {
email: '',
password: '',
}
}
const userReducer = (state = initialState, action) => {
switch(action.type) {
case 'SET_USER_DETAILS':
return Object.assign({}, state, {
user: action.user
})
default:
return state
}
return state
}
export default userReducer
...
Your store will remain same and rootReducer should be
import { combineReducers } from 'redux';
import userReducer from './reducer'
const rootReducer = combineReducers({
userReducer
})
export default rootReducer
...
Finally your Action
export const SET_USER_DETAILS = 'SET_USER_DETAILS'
export function setUserDetails (user) {
return {
type: 'SET_USER_DETAILS',
user
}
}
...
Hope it helps.
Hope that helps:
Login:
You must NEVER update a component props inside the said component.
From the React documentation:
Props are Read-Only
If you want your state (the truth) to be stored in the login component, then store it as a proper state and send this local state on submit:
onChangeText = (key, value) => {
this.setState((state) => ({ ...state, [key] => value}))
}
However, if you want to store your state in redux, you will need to create an action that can be triggered to update the redux state. This action needs to be passed to your component props and called like this onChangeText={val => this.props.onChangeText('email', val)}
Also, your calling the fetchUserDetails function on render, where you should be passing a callback. this.fetchUserDetails does not exists, this.props.fetchUserDetails does. The login code becomes
<TouchableOpacity onPress={() => fetchUserDetails(user)}>
Login Container:
mapDispatchToProps must be a function that takes dispatch as first parameter OR an object where each function is an action creator. From the Redux documentation:
If an object is passed, each function inside it is assumed to be a Redux action creator. An object with the same function names, but with every action creator wrapped into a dispatch call so they may be invoked directly, will be merged into the component’s props.
So the code you wrote:
const mapDispatchToProps = {
...fetchUserDetails,
}
Is equivalent to this code
function mapDispatchToProps(dispatch) {
return {
fetchUserDetails: (user) => dispatch(fetchUserDetails(user))
},
}
The dispatch function is where the magic happens, every action that is dispatched is passed down to your reducers where you can create a new state based on the action.
I'm a beginner of react & react-native.
I'm using react 16, react-thunk, react-redux.
I'm trying to fetch categories that I already made from firestore.
At first, I called action using connect(), and then, I typed action using thunk also fetched data from firestore.
Finally, I returned new states in reducer.
Definitely, I'm not aware of redux process, so please give some tips.
Here's my code. Thank you.
CategoryImageList.js (Component)
...
class CategoryImageList extends Component {
componentWillMount() {
this.props.getCategory();
}
renderImages() {
return this.state.categories.map(category =>
<CategoryImageCard key={category.imgName} category={category}/>
);
}
render() {
return (
<ScrollView>
{/*{this.renderImages()}*/}
</ScrollView>
);
}
}
export default connect(null, {getCategory})(CategoryImageList);
category.js (action)
...
export const getCategory = () => {
return (dispatch) => { //using redux-thunk here... do check it out
getCategories()
.then(querySnapshot => {
const test = [];
querySnapshot.forEach((doc) => {
test.push(
{
imgName : doc.data()['imgName'],
name : doc.data()['name']
});
});
dispatch({ type: GET_CATEGORY, payload: test} );
});
};
};
CategoryReducers.js (reducer)
...
const categoryInitialState = {
name: [],
imgName: []
}
export const CategoryReducer = (state = categoryInitialState, action) => {
switch (action.type) {
case GET_CATEGORY:
console.log(action);
return { ...state, categoryImg: {
name: action.payload.name,
imgName: action.payload.imgName
}};
default:
return state;
}
}
App.js
...
type Props = {};
export default class App extends Component<Props> {
render() {
const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
return (
<Provider store={store}>
<View style={{flex:1}}>
<Header headerText={'FoodUp'}/>
<CategoryImageList />
</View>
</Provider>
);
}
}
reducers/index.js
import { combineReducers } from 'redux';
import { CategoryReducer } from './CategoryReducer';
export default combineReducers({
categories: CategoryReducer
});
UPDATED
Firebase.js
const config = {
...
};
firebase.initializeApp(config);
const db = firebase.firestore();
const storage = firebase.storage();
const settings = {timestampsInSnapshots: true};
db.settings(settings);
export const getCategories = () => {
return db.collection('categories').get();
}
export const getCategoryImg = (categoryName, imgName) => {
const ref = storage.ref(`category/${categoryName}/${imgName}`);
return ref.getDownloadURL();
}
You have to add mapstateToProps to your connect like,
const mapStateToProps = (state: any) => {
return {
name: state.categories.name,
imageName:state.categories.imageName
};
}
export default connect(mapStateToProps)(CategoryImageList)
And then, you will be able to access the name and image name like,
this.props.name and this.props.imageName
Edit: To dispatch GET_CATEGORY you can either use mapDispatchToProps or do the getCategory and dispatch from within your component like,
import {getCategory} from './category'
componentWillMount() {
this.props.getCategory(this.props.dispatch);
}
and change the getCategory function as,
export const getCategory = (dispatch) => {
...
dispatch({ type: GET_CATEGORY, payload: test} );
...
}
mapStateToProps has the Store state as an argument/param (provided by react-redux::connect) and its used to link the component with the certain part of the store state. in your case, you can use like this. and you can use name, imgName as a props in your component
const mapStateToProps = ({categories}) => {
const { name, imgName } = categories;
return {name, imgName};
};
export default connect(mapStateToProps, {getCategory})(CategoryImageList);